evilution 1.2.0 → 1.4.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 +100 -0
- data/.rubocop_todo.yml +14 -0
- data/CHANGELOG.md +124 -0
- data/README.md +142 -28
- data/docs/architecture.md +123 -9
- data/docs/ast_pattern_syntax.md +50 -5
- data/docs/isolation.md +39 -2
- data/docs/migration-from-mutant.md +1 -1
- data/lib/evilution/ast/aasm_declaration.rb +133 -0
- data/lib/evilution/ast/callback_declaration.rb +91 -0
- data/lib/evilution/ast/included_block.rb +27 -0
- data/lib/evilution/ast/literal_callable.rb +21 -0
- data/lib/evilution/ast/parser.rb +120 -16
- data/lib/evilution/ast/pattern/filter.rb +1 -0
- data/lib/evilution/ast/pattern/method_name.rb +63 -0
- data/lib/evilution/ast/pattern/parser.rb +15 -15
- data/lib/evilution/ast/pattern.rb +2 -0
- data/lib/evilution/ast/regexp_pattern.rb +104 -0
- data/lib/evilution/ast/scope_declaration.rb +45 -0
- data/lib/evilution/ast/sorbet_sig_detector.rb +1 -0
- data/lib/evilution/ast/uncovered_code.rb +88 -0
- data/lib/evilution/ast/value_object_definition.rb +25 -0
- data/lib/evilution/baseline/failure_formatter.rb +27 -0
- data/lib/evilution/baseline/report.rb +66 -0
- data/lib/evilution/baseline/spec_failure.rb +38 -0
- data/lib/evilution/baseline.rb +101 -42
- 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 +2 -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 +4 -1
- data/lib/evilution/cli/parser/options_builder.rb +4 -1
- 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 +8 -2
- 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 +3 -1
- 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 +1 -1
- data/lib/evilution/equivalent/detector.rb +3 -1
- data/lib/evilution/equivalent/heuristic/guarded_index_fetch/condition.rb +66 -0
- data/lib/evilution/equivalent/heuristic/guarded_index_fetch/disturbance.rb +54 -0
- data/lib/evilution/equivalent/heuristic/guarded_index_fetch/early_exit.rb +73 -0
- data/lib/evilution/equivalent/heuristic/guarded_index_fetch/guard.rb +36 -0
- data/lib/evilution/equivalent/heuristic/guarded_index_fetch/index_read.rb +82 -0
- data/lib/evilution/equivalent/heuristic/guarded_index_fetch/node_path.rb +43 -0
- data/lib/evilution/equivalent/heuristic/guarded_index_fetch.rb +65 -0
- data/lib/evilution/example_filter.rb +46 -1
- data/lib/evilution/hooks/registry.rb +2 -1
- data/lib/evilution/integration/base.rb +2 -0
- data/lib/evilution/integration/known_failures.rb +26 -0
- data/lib/evilution/integration/loading/body_call_neutralizer.rb +139 -12
- data/lib/evilution/integration/loading/callback_redeclaration.rb +186 -0
- data/lib/evilution/integration/loading/concern_redeclaration.rb +61 -0
- data/lib/evilution/integration/loading/concern_state_cleaner.rb +19 -12
- data/lib/evilution/integration/loading/mutation_applier.rb +16 -0
- data/lib/evilution/integration/loading/test_class_cache.rb +46 -0
- data/lib/evilution/integration/loading/test_load_path.rb +21 -13
- data/lib/evilution/integration/minitest/test_ids.rb +21 -0
- data/lib/evilution/integration/minitest.rb +45 -6
- data/lib/evilution/integration/rspec/baseline_runner.rb +47 -3
- data/lib/evilution/integration/rspec/example_ids.rb +35 -0
- data/lib/evilution/integration/rspec/result_builder.rb +6 -1
- data/lib/evilution/integration/rspec/state_guard/anonymous_example_group_examples.rb +36 -0
- data/lib/evilution/integration/rspec/state_guard/configuration_state.rb +1 -0
- data/lib/evilution/integration/rspec/state_guard/configuration_streams.rb +1 -0
- 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 +10 -1
- data/lib/evilution/integration/rspec.rb +22 -4
- data/lib/evilution/integration/test_unit/result_builder.rb +6 -0
- data/lib/evilution/integration/test_unit/test_ids.rb +18 -0
- data/lib/evilution/integration/test_unit.rb +37 -9
- data/lib/evilution/isolation/fork.rb +2 -1
- data/lib/evilution/isolation/in_process.rb +21 -4
- 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/mcp/info_tool/status_glossary.rb +2 -2
- data/lib/evilution/mcp/mutate_tool/progress_streamer.rb +2 -1
- data/lib/evilution/memory/leak_check.rb +27 -3
- data/lib/evilution/mutation.rb +8 -3
- data/lib/evilution/mutator/base.rb +49 -5
- data/lib/evilution/mutator/operator/alias_removal.rb +126 -0
- data/lib/evilution/mutator/operator/argument_list_removal.rb +52 -0
- data/lib/evilution/mutator/operator/argument_order_permutation.rb +101 -0
- data/lib/evilution/mutator/operator/argument_propagation.rb +54 -0
- data/lib/evilution/mutator/operator/argument_removal.rb +9 -1
- 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/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_operand_swap.rb +54 -0
- 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/data_struct_member.rb +82 -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/exception_swallow.rb +84 -0
- data/lib/evilution/mutator/operator/format_specifier_swap.rb +100 -0
- data/lib/evilution/mutator/operator/forwarded_argument_drop.rb +142 -0
- data/lib/evilution/mutator/operator/inequality_to_negated_identity.rb +43 -0
- data/lib/evilution/mutator/operator/integer_division_to_fdiv.rb +62 -0
- data/lib/evilution/mutator/operator/keyword_argument.rb +25 -2
- data/lib/evilution/mutator/operator/keyword_argument_removal.rb +47 -0
- data/lib/evilution/mutator/operator/keyword_value_swap.rb +75 -0
- data/lib/evilution/mutator/operator/no_matching_pattern_else.rb +47 -0
- data/lib/evilution/mutator/operator/numbered_parameter_swap.rb +78 -0
- data/lib/evilution/mutator/operator/off_by_one_boundary.rb +68 -0
- data/lib/evilution/mutator/operator/optional_parameter_to_required.rb +14 -3
- data/lib/evilution/mutator/operator/pattern_matching_array.rb +12 -1
- data/lib/evilution/mutator/operator/pattern_wildcard_widening.rb +117 -0
- data/lib/evilution/mutator/operator/pin_operator_removal.rb +48 -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/regex_simplification.rb +53 -132
- data/lib/evilution/mutator/operator/regexp_alternation_branch_deletion.rb +54 -0
- data/lib/evilution/mutator/operator/regexp_anchor_promotion.rb +43 -0
- data/lib/evilution/mutator/operator/regexp_anchor_to_predicate.rb +136 -0
- data/lib/evilution/mutator/operator/regexp_capture_to_passive.rb +61 -0
- data/lib/evilution/mutator/operator/regexp_character_type_complement.rb +54 -0
- data/lib/evilution/mutator/operator/regexp_named_group_rename.rb +106 -0
- data/lib/evilution/mutator/operator/regexp_option_removal.rb +51 -0
- data/lib/evilution/mutator/operator/regexp_quantifier_minimum_swap.rb +45 -0
- data/lib/evilution/mutator/operator/rescue_else_concatenation.rb +62 -0
- data/lib/evilution/mutator/operator/rescue_handler_concatenation.rb +69 -0
- data/lib/evilution/mutator/operator/rescue_handler_promotion.rb +65 -0
- data/lib/evilution/mutator/operator/return_keyword_removal.rb +79 -0
- data/lib/evilution/mutator/operator/rightward_assignment.rb +46 -0
- data/lib/evilution/mutator/operator/safe_navigation_removal.rb +67 -0
- data/lib/evilution/mutator/operator/send_mutation.rb +44 -6
- data/lib/evilution/mutator/operator/splat_operator.rb +38 -13
- data/lib/evilution/mutator/operator/statement_reorder.rb +153 -0
- 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/primitives.rb +25 -0
- data/lib/evilution/mutator/registry.rb +54 -2
- data/lib/evilution/mutator/rescue_handlers.rb +81 -0
- data/lib/evilution/parallel/pool.rb +1 -0
- data/lib/evilution/parallel_db_warning.rb +1 -1
- data/lib/evilution/process_supervisor.rb +3 -2
- data/lib/evilution/rails_warmup.rb +61 -0
- data/lib/evilution/reporter/cli/line_formatters/baseline_neutralized_notice.rb +55 -0
- data/lib/evilution/reporter/cli/metrics_block.rb +2 -0
- data/lib/evilution/reporter/html/report.rb +1 -0
- data/lib/evilution/reporter/json/baseline.rb +15 -0
- data/lib/evilution/reporter/json.rb +7 -2
- data/lib/evilution/result/baseline_neutralization.rb +10 -0
- data/lib/evilution/result/coverage_gap_grouper.rb +1 -0
- data/lib/evilution/result/mutation_result.rb +9 -1
- data/lib/evilution/result/subject_scorer.rb +1 -0
- data/lib/evilution/result/summary.rb +42 -2
- data/lib/evilution/runner/baseline_runner.rb +26 -11
- data/lib/evilution/runner/canary.rb +2 -57
- data/lib/evilution/runner/canary_failure_message.rb +101 -0
- data/lib/evilution/runner/isolation_resolver.rb +23 -2
- data/lib/evilution/runner/mutation_executor/neutralizer/baseline_failed.rb +35 -14
- data/lib/evilution/runner/mutation_executor/result_cache.rb +3 -0
- data/lib/evilution/runner/mutation_executor/result_packer.rb +4 -2
- data/lib/evilution/runner/mutation_executor.rb +1 -1
- data/lib/evilution/runner/mutation_planner.rb +11 -2
- data/lib/evilution/runner/report_publisher.rb +3 -2
- data/lib/evilution/runner/subject_pipeline.rb +46 -1
- data/lib/evilution/runner.rb +4 -1
- data/lib/evilution/source_ast_cache.rb +1 -1
- data/lib/evilution/spec_ast_cache.rb +1 -1
- data/lib/evilution/subject.rb +8 -2
- data/lib/evilution/version.rb +1 -1
- data/lib/evilution.rb +51 -0
- data/script/memory_check +62 -16
- metadata +108 -4
data/lib/evilution/baseline.rb
CHANGED
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "spec_resolver"
|
|
4
4
|
require_relative "process_cleanup"
|
|
5
|
+
require_relative "diagnostic"
|
|
5
6
|
|
|
6
7
|
class Evilution::Baseline
|
|
7
|
-
Result = Struct.new(:failed_spec_files, :duration) do
|
|
8
|
-
def initialize(
|
|
8
|
+
Result = Struct.new(:failed_spec_files, :duration, :failures) do
|
|
9
|
+
def initialize(failed_spec_files:, duration:, failures: [])
|
|
9
10
|
super
|
|
10
11
|
freeze
|
|
11
12
|
end
|
|
@@ -13,44 +14,62 @@ class Evilution::Baseline
|
|
|
13
14
|
def failed?
|
|
14
15
|
!failed_spec_files.empty?
|
|
15
16
|
end
|
|
17
|
+
|
|
18
|
+
# Every example known to have failed before any mutation ran.
|
|
19
|
+
def failed_example_ids
|
|
20
|
+
failures.flat_map(&:failed_ids).to_set
|
|
21
|
+
end
|
|
16
22
|
end
|
|
17
23
|
|
|
24
|
+
# spec_selector: the object `run` resolves with (integration layout plus
|
|
25
|
+
# spec_mappings / spec_pattern), returning every spec covering a source.
|
|
26
|
+
# Without one, the bare spec_resolver is used. spec_resolver also supplies
|
|
27
|
+
# the best-guess hint for an unresolved source.
|
|
28
|
+
#
|
|
29
|
+
# fallback_to_full_suite: whether an unresolved source runs the whole
|
|
30
|
+
# fallback_dir. When false its mutations are reported unresolved, so the
|
|
31
|
+
# baseline skips it rather than running -- and often timing out on -- the
|
|
32
|
+
# entire test directory.
|
|
18
33
|
def initialize(spec_resolver: Evilution::SpecResolver.new, timeout: 30, runner: nil,
|
|
19
|
-
fallback_dir: "spec", test_files: nil)
|
|
34
|
+
fallback_dir: "spec", test_files: nil, spec_selector: nil, fallback_to_full_suite: true)
|
|
20
35
|
@spec_resolver = spec_resolver
|
|
21
36
|
@timeout = timeout
|
|
22
37
|
@runner = runner
|
|
23
38
|
@fallback_dir = fallback_dir
|
|
24
39
|
@test_files = test_files
|
|
40
|
+
@spec_selector = spec_selector
|
|
41
|
+
@fallback_to_full_suite = fallback_to_full_suite
|
|
25
42
|
end
|
|
26
43
|
|
|
27
44
|
def call(subjects)
|
|
28
45
|
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
29
|
-
|
|
30
|
-
failed = Set.new
|
|
31
|
-
|
|
32
|
-
spec_files.each do |spec_file|
|
|
33
|
-
failed.add(spec_file) unless run_spec_file(spec_file)
|
|
34
|
-
end
|
|
46
|
+
failures = baseline_spec_files(subjects).filter_map { |spec_file| check_spec_file(spec_file) }
|
|
35
47
|
|
|
36
48
|
duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time
|
|
37
|
-
Result.new(failed_spec_files:
|
|
49
|
+
Result.new(failed_spec_files: failures.to_set(&:spec_file), duration: duration, failures: failures)
|
|
38
50
|
end
|
|
39
51
|
|
|
40
52
|
def run_spec_file(spec_file)
|
|
53
|
+
check_spec_file(spec_file).nil?
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# nil when the spec file passed, otherwise why it did not. Anything that
|
|
57
|
+
# stops the baseline from getting an answer counts as failing, and is
|
|
58
|
+
# recorded as the reason.
|
|
59
|
+
def check_spec_file(spec_file)
|
|
41
60
|
raise Evilution::Error, "no baseline runner configured" unless @runner
|
|
42
61
|
|
|
43
62
|
read_io, write_io = IO.pipe
|
|
44
63
|
pid = fork_spec_runner(spec_file, read_io, write_io)
|
|
45
64
|
write_io.close
|
|
46
|
-
read_result(read_io, pid)
|
|
65
|
+
read_result(read_io, pid, spec_file)
|
|
47
66
|
rescue Evilution::Error
|
|
48
67
|
raise
|
|
49
|
-
rescue StandardError
|
|
50
|
-
|
|
68
|
+
rescue StandardError => e
|
|
69
|
+
warned(SpecFailure.new(spec_file: spec_file, error: "#{e.class}: #{e.message}"))
|
|
51
70
|
ensure
|
|
52
|
-
read_io
|
|
53
|
-
write_io
|
|
71
|
+
read_io.close if read_io
|
|
72
|
+
write_io.close if write_io
|
|
54
73
|
end
|
|
55
74
|
|
|
56
75
|
def fork_spec_runner(spec_file, read_io, write_io)
|
|
@@ -60,27 +79,28 @@ class Evilution::Baseline
|
|
|
60
79
|
$stdout.reopen(File::NULL, "w")
|
|
61
80
|
$stderr.reopen(File::NULL, "w")
|
|
62
81
|
|
|
63
|
-
|
|
64
|
-
Marshal.dump(
|
|
82
|
+
report = child_report(runner, spec_file)
|
|
83
|
+
Marshal.dump(report, write_io)
|
|
65
84
|
write_io.close
|
|
66
|
-
exit!(passed ? 0 : 1)
|
|
85
|
+
exit!(report[:passed] ? 0 : 1)
|
|
67
86
|
end
|
|
68
87
|
end
|
|
69
88
|
|
|
70
89
|
GRACE_PERIOD = 0.5
|
|
71
90
|
|
|
72
|
-
def read_result(read_io, pid)
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
91
|
+
def read_result(read_io, pid, spec_file)
|
|
92
|
+
return timed_out(pid, spec_file) unless read_io.wait_readable(@timeout)
|
|
93
|
+
|
|
94
|
+
data = read_io.read
|
|
95
|
+
_, status = Process.wait2(pid)
|
|
96
|
+
return warned(SpecFailure.new(spec_file: spec_file, error: unreported_error(status))) if data.empty?
|
|
97
|
+
|
|
98
|
+
report = Marshal.load(data)
|
|
99
|
+
report[:passed] ? nil : warned(SpecFailure.from_report(spec_file, report))
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def warn_timeout(spec_file)
|
|
103
|
+
Evilution::Diagnostic.warn("[evilution] Baseline for #{spec_file} timed out after #{@timeout}s; treating it as failing.")
|
|
84
104
|
end
|
|
85
105
|
|
|
86
106
|
def terminate_child(pid)
|
|
@@ -98,11 +118,35 @@ class Evilution::Baseline
|
|
|
98
118
|
|
|
99
119
|
private
|
|
100
120
|
|
|
101
|
-
#
|
|
102
|
-
#
|
|
103
|
-
#
|
|
104
|
-
|
|
105
|
-
|
|
121
|
+
# Runs in the child. A runner that raises -- a spec helper that cannot load,
|
|
122
|
+
# a framework that is not there -- is a failure with a reason, not a child
|
|
123
|
+
# that dies without a word.
|
|
124
|
+
def child_report(runner, spec_file)
|
|
125
|
+
Report.from(runner.call(spec_file))
|
|
126
|
+
rescue StandardError, ScriptError => e
|
|
127
|
+
Report.build(passed: false, error: "#{e.class}: #{e.message}")
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def timed_out(pid, spec_file)
|
|
131
|
+
terminate_child(pid)
|
|
132
|
+
warn_timeout(spec_file)
|
|
133
|
+
SpecFailure.new(spec_file: spec_file, error: "timed out after #{@timeout}s")
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def unreported_error(status)
|
|
137
|
+
cause = status.signaled? ? "signal #{status.termsig}" : "exit status #{status.exitstatus}"
|
|
138
|
+
"baseline process ended without reporting (#{cause})"
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def warned(failure)
|
|
142
|
+
detail = FailureFormatter.new.call(failure).map { |line| " #{line}" }
|
|
143
|
+
Evilution::Diagnostic.warn(
|
|
144
|
+
["[evilution] Baseline failed for #{failure.spec_file}; " \
|
|
145
|
+
"a mutation that fails only its already-failing examples will be reported neutral.", *detail].join("\n")
|
|
146
|
+
)
|
|
147
|
+
failure
|
|
148
|
+
end
|
|
149
|
+
|
|
106
150
|
def baseline_spec_files(subjects)
|
|
107
151
|
return Array(@test_files).uniq if @test_files && !@test_files.empty?
|
|
108
152
|
|
|
@@ -111,20 +155,35 @@ class Evilution::Baseline
|
|
|
111
155
|
|
|
112
156
|
def resolve_unique_spec_files(subjects)
|
|
113
157
|
warned = Set.new
|
|
114
|
-
subjects.
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
158
|
+
subjects.flat_map do |s|
|
|
159
|
+
specs = specs_for(s.file_path)
|
|
160
|
+
next specs unless specs.empty?
|
|
161
|
+
|
|
162
|
+
warn_no_matching_test(s.file_path) if warned.add?(s.file_path)
|
|
163
|
+
@fallback_to_full_suite ? [@fallback_dir] : []
|
|
118
164
|
end.uniq
|
|
119
165
|
end
|
|
120
166
|
|
|
167
|
+
def specs_for(file_path)
|
|
168
|
+
Array(@spec_selector ? @spec_selector.call(file_path) : @spec_resolver.call(file_path))
|
|
169
|
+
end
|
|
170
|
+
|
|
121
171
|
def warn_no_matching_test(file_path)
|
|
172
|
+
action = @fallback_to_full_suite ? "running full suite" : "marking its mutations unresolved"
|
|
173
|
+
Evilution::Diagnostic.warn("[evilution] No matching test found for #{file_path}, #{action}. #{no_match_hint(file_path)}")
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def no_match_hint(file_path)
|
|
122
177
|
suggestion = @spec_resolver.suggest(file_path)
|
|
123
178
|
hint = if suggestion
|
|
124
|
-
"Pass --spec #{suggestion} (best guess) or the correct test file
|
|
179
|
+
"Pass --spec #{suggestion} (best guess) or the correct test file"
|
|
125
180
|
else
|
|
126
|
-
"Use --spec to specify the test file
|
|
181
|
+
"Use --spec to specify the test file"
|
|
127
182
|
end
|
|
128
|
-
|
|
183
|
+
@fallback_to_full_suite ? "#{hint}." : "#{hint}, or --fallback-full-suite to run the whole suite."
|
|
129
184
|
end
|
|
130
185
|
end
|
|
186
|
+
|
|
187
|
+
require_relative "baseline/report"
|
|
188
|
+
require_relative "baseline/spec_failure"
|
|
189
|
+
require_relative "baseline/failure_formatter"
|
|
@@ -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
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "../parser"
|
|
4
|
+
require_relative "../../diagnostic"
|
|
5
|
+
|
|
3
6
|
module Evilution::CLI::Parser::FileArgs
|
|
4
7
|
ParsedPaths = Data.define(:files, :ranges)
|
|
5
8
|
|
|
@@ -22,7 +25,7 @@ module Evilution::CLI::Parser::FileArgs
|
|
|
22
25
|
|
|
23
26
|
def expand_spec_dir(dir)
|
|
24
27
|
unless File.directory?(dir)
|
|
25
|
-
warn("Error: #{dir} is not a directory")
|
|
28
|
+
Evilution::Diagnostic.warn("Error: #{dir} is not a directory")
|
|
26
29
|
return []
|
|
27
30
|
end
|
|
28
31
|
|
|
@@ -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
|
|
|
@@ -119,6 +120,8 @@ class Evilution::CLI::Parser::OptionsBuilder
|
|
|
119
120
|
"(default: auto-detect spec/rails_helper.rb -> spec/spec_helper.rb -> " \
|
|
120
121
|
"test/test_helper.rb for Rails projects)") { |f| @options[:preload] = f }
|
|
121
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 }
|
|
122
125
|
end
|
|
123
126
|
|
|
124
127
|
def add_output_options(opts)
|
|
@@ -137,7 +140,7 @@ class Evilution::CLI::Parser::OptionsBuilder
|
|
|
137
140
|
|
|
138
141
|
def add_profile_options(opts)
|
|
139
142
|
opts.on("--profile NAME", "Operator profile: default, strict (default: default). " \
|
|
140
|
-
"strict adds aggressive
|
|
143
|
+
"strict adds aggressive mutators for pre-merge audits.") { |p| @options[:profile] = p }
|
|
141
144
|
opts.on("--strict", "Shortcut for --profile=strict") { @options[:profile] = "strict" }
|
|
142
145
|
end
|
|
143
146
|
|
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"
|
|
@@ -19,6 +24,7 @@ require_relative "cli/commands/session_gc"
|
|
|
19
24
|
require_relative "cli/commands/compare"
|
|
20
25
|
require_relative "cli/commands/run"
|
|
21
26
|
require_relative "cli/exit_guard"
|
|
27
|
+
require_relative "diagnostic"
|
|
22
28
|
|
|
23
29
|
# @api private
|
|
24
30
|
class Evilution::CLI
|
|
@@ -37,14 +43,14 @@ class Evilution::CLI
|
|
|
37
43
|
return run_subcommand_error(@parse_error) if @command == :parse_error
|
|
38
44
|
|
|
39
45
|
result = Dispatcher.lookup(@command).new(@parsed, stdout: $stdout, stderr: $stderr).call
|
|
40
|
-
warn("Error: #{result.error.message}") if result.error && !result.error_rendered
|
|
46
|
+
Evilution::Diagnostic.warn("Error: #{result.error.message}") if result.error && !result.error_rendered
|
|
41
47
|
result.exit_code
|
|
42
48
|
end
|
|
43
49
|
|
|
44
50
|
private
|
|
45
51
|
|
|
46
52
|
def run_subcommand_error(message)
|
|
47
|
-
warn("Error: #{message}")
|
|
53
|
+
Evilution::Diagnostic.warn("Error: #{message}")
|
|
48
54
|
2
|
|
49
55
|
end
|
|
50
56
|
end
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "../validators"
|
|
3
4
|
require_relative "base"
|
|
5
|
+
require_relative "../../diagnostic"
|
|
4
6
|
|
|
5
7
|
class Evilution::Config::Validators::SpecMappings < Evilution::Config::Validators::Base
|
|
6
8
|
def self.call(value)
|
|
@@ -48,7 +50,7 @@ class Evilution::Config::Validators::SpecMappings < Evilution::Config::Validator
|
|
|
48
50
|
specs.each do |spec_path|
|
|
49
51
|
next if File.exist?(spec_path)
|
|
50
52
|
|
|
51
|
-
warn
|
|
53
|
+
Evilution::Diagnostic.warn("[evilution] spec_mappings[#{source.inspect}]: #{spec_path} not found, skipping")
|
|
52
54
|
end
|
|
53
55
|
end
|
|
54
56
|
end
|
|
@@ -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
|