evilution 0.26.0 → 0.28.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 +23 -0
- data/.rubocop_todo.yml +6 -0
- data/CHANGELOG.md +54 -0
- data/README.md +76 -3
- data/lib/evilution/baseline.rb +5 -4
- data/lib/evilution/cache.rb +2 -0
- data/lib/evilution/child_output.rb +24 -0
- data/lib/evilution/cli/commands/run.rb +9 -0
- data/lib/evilution/cli/commands/version.rb +2 -0
- data/lib/evilution/cli/parser/options_builder.rb +23 -2
- data/lib/evilution/compare/diff_extractor/evilution.rb +22 -0
- data/lib/evilution/compare/diff_extractor/mutant.rb +30 -0
- data/lib/evilution/compare/diff_extractor.rb +6 -0
- data/lib/evilution/compare/fingerprint.rb +15 -72
- data/lib/evilution/compare/line_normalizer.rb +72 -0
- data/lib/evilution/compare/normalizer.rb +17 -4
- data/lib/evilution/config/builders/spec_resolver.rb +15 -0
- data/lib/evilution/config/builders/spec_selector.rb +16 -0
- data/lib/evilution/config/builders.rb +4 -0
- data/lib/evilution/config/env_loader.rb +12 -0
- data/lib/evilution/config/file_loader.rb +22 -0
- data/lib/evilution/config/sources.rb +14 -0
- data/lib/evilution/config/validators/base.rb +37 -0
- data/lib/evilution/config/validators/example_targeting_cache.rb +37 -0
- data/lib/evilution/config/validators/example_targeting_fallback.rb +22 -0
- data/lib/evilution/config/validators/fail_fast.rb +11 -0
- data/lib/evilution/config/validators/hooks.rb +12 -0
- data/lib/evilution/config/validators/ignore_patterns.rb +16 -0
- data/lib/evilution/config/validators/integration.rb +11 -0
- data/lib/evilution/config/validators/isolation.rb +19 -0
- data/lib/evilution/config/validators/jobs.rb +9 -0
- data/lib/evilution/config/validators/preload.rb +13 -0
- data/lib/evilution/config/validators/profile.rb +11 -0
- data/lib/evilution/config/validators/spec_mappings.rb +56 -0
- data/lib/evilution/config/validators/spec_pattern.rb +12 -0
- data/lib/evilution/config/validators.rb +4 -0
- data/lib/evilution/config.rb +93 -266
- data/lib/evilution/feedback/detector.rb +15 -0
- data/lib/evilution/feedback/messages.rb +42 -0
- data/lib/evilution/feedback.rb +5 -0
- data/lib/evilution/integration/crash_detector.rb +2 -2
- data/lib/evilution/integration/loading/source_evaluator.rb +6 -2
- data/lib/evilution/integration/minitest_crash_detector.rb +2 -2
- data/lib/evilution/integration/rspec/baseline_runner.rb +16 -0
- data/lib/evilution/integration/rspec/crash_detector_lifecycle.rb +17 -0
- data/lib/evilution/integration/rspec/example_filter_applier.rb +21 -0
- data/lib/evilution/integration/rspec/framework_loader.rb +28 -0
- data/lib/evilution/integration/rspec/result_builder.rb +40 -0
- data/lib/evilution/integration/rspec/state_guard/example_groups_constants.rb +28 -0
- data/lib/evilution/integration/rspec/state_guard/internals.rb +19 -0
- data/lib/evilution/integration/rspec/state_guard/object_space_example_groups.rb +43 -0
- data/lib/evilution/integration/rspec/state_guard/reporter_arrays.rb +32 -0
- data/lib/evilution/integration/rspec/state_guard/world_example_groups.rb +20 -0
- data/lib/evilution/integration/rspec/state_guard/world_filtered_examples.rb +20 -0
- data/lib/evilution/integration/rspec/state_guard/world_sources_by_path.rb +20 -0
- data/lib/evilution/integration/rspec/state_guard.rb +40 -0
- data/lib/evilution/integration/rspec/test_file_resolver.rb +30 -0
- data/lib/evilution/integration/rspec/unresolved_spec_warner.rb +18 -0
- data/lib/evilution/integration/rspec.rb +61 -232
- data/lib/evilution/isolation/fork.rb +23 -13
- data/lib/evilution/isolation/in_process.rb +10 -6
- data/lib/evilution/mcp/info_tool/actions/base.rb +22 -0
- data/lib/evilution/mcp/info_tool/actions/environment.rb +42 -0
- data/lib/evilution/mcp/info_tool/actions/feedback.rb +16 -0
- data/lib/evilution/mcp/info_tool/actions/statuses.rb +10 -0
- data/lib/evilution/mcp/info_tool/actions/subjects.rb +47 -0
- data/lib/evilution/mcp/info_tool/actions/tests.rb +60 -0
- data/lib/evilution/mcp/info_tool/actions.rb +16 -0
- data/lib/evilution/mcp/info_tool/config_factory.rb +24 -0
- data/lib/evilution/mcp/info_tool/error_mapper.rb +15 -0
- data/lib/evilution/mcp/info_tool/request_parser.rb +34 -0
- data/lib/evilution/mcp/info_tool/response_formatter.rb +24 -0
- data/lib/evilution/mcp/info_tool/status_glossary.rb +75 -0
- data/lib/evilution/mcp/info_tool.rb +43 -263
- data/lib/evilution/mcp/mutate_tool/error_payload.rb +8 -1
- data/lib/evilution/mcp/mutate_tool/progress_streamer.rb +5 -1
- data/lib/evilution/mcp/mutate_tool/report_trimmer.rb +13 -1
- data/lib/evilution/mcp/mutate_tool.rb +5 -2
- data/lib/evilution/mcp/session_tool.rb +0 -2
- data/lib/evilution/mutation.rb +47 -27
- data/lib/evilution/mutator/base.rb +8 -8
- data/lib/evilution/mutator/operator/block_removal.rb +1 -1
- data/lib/evilution/mutator/operator/method_body_replacement.rb +18 -2
- data/lib/evilution/mutator/operator/predicate_to_nil.rb +20 -0
- data/lib/evilution/mutator/registry.rb +20 -0
- data/lib/evilution/parallel/work_queue/channel/frame.rb +25 -0
- data/lib/evilution/parallel/work_queue/channel.rb +23 -0
- data/lib/evilution/parallel/work_queue/collection_state.rb +14 -0
- data/lib/evilution/parallel/work_queue/dispatcher.rb +133 -0
- data/lib/evilution/parallel/work_queue/validators/optional_positive_int.rb +11 -0
- data/lib/evilution/parallel/work_queue/validators/optional_positive_number.rb +11 -0
- data/lib/evilution/parallel/work_queue/validators/positive_int.rb +11 -0
- data/lib/evilution/parallel/work_queue/validators.rb +6 -0
- data/lib/evilution/parallel/work_queue/worker/loop.rb +45 -0
- data/lib/evilution/parallel/work_queue/worker.rb +114 -0
- data/lib/evilution/parallel/work_queue/worker_stat.rb +17 -0
- data/lib/evilution/parallel/work_queue.rb +42 -327
- data/lib/evilution/process_cleanup.rb +19 -0
- data/lib/evilution/reporter/cli/item_formatters/coverage_gap.rb +18 -0
- data/lib/evilution/reporter/cli/item_formatters/disabled.rb +9 -0
- data/lib/evilution/reporter/cli/item_formatters/error.rb +14 -0
- data/lib/evilution/reporter/cli/item_formatters/result_location.rb +10 -0
- data/lib/evilution/reporter/cli/item_formatters.rb +6 -0
- data/lib/evilution/reporter/cli/line_formatters/duration.rb +9 -0
- data/lib/evilution/reporter/cli/line_formatters/efficiency.rb +18 -0
- data/lib/evilution/reporter/cli/line_formatters/feedback_footer.rb +13 -0
- data/lib/evilution/reporter/cli/line_formatters/header.rb +10 -0
- data/lib/evilution/reporter/cli/line_formatters/mutations.rb +16 -0
- data/lib/evilution/reporter/cli/line_formatters/peak_memory.rb +12 -0
- data/lib/evilution/reporter/cli/line_formatters/result_line.rb +20 -0
- data/lib/evilution/reporter/cli/line_formatters/score.rb +14 -0
- data/lib/evilution/reporter/cli/line_formatters/truncation_notice.rb +11 -0
- data/lib/evilution/reporter/cli/line_formatters.rb +6 -0
- data/lib/evilution/reporter/cli/metrics_block.rb +26 -0
- data/lib/evilution/reporter/cli/pct.rb +9 -0
- data/lib/evilution/reporter/cli/section.rb +13 -0
- data/lib/evilution/reporter/cli/section_renderer.rb +15 -0
- data/lib/evilution/reporter/cli/trailer.rb +22 -0
- data/lib/evilution/reporter/cli.rb +79 -162
- data/lib/evilution/reporter/html/baseline_keys.rb +1 -1
- data/lib/evilution/reporter/html/diff_formatter.rb +1 -1
- data/lib/evilution/reporter/html/escape.rb +1 -1
- data/lib/evilution/reporter/html/section.rb +1 -1
- data/lib/evilution/reporter/html/sections.rb +4 -2
- data/lib/evilution/reporter/html/stylesheet.rb +1 -1
- data/lib/evilution/reporter/html.rb +8 -3
- data/lib/evilution/reporter/suggestion/registry.rb +1 -5
- data/lib/evilution/reporter/suggestion/templates/generic.rb +1 -1
- data/lib/evilution/reporter/suggestion/templates/minitest.rb +349 -643
- data/lib/evilution/reporter/suggestion/templates/rspec.rb +351 -598
- data/lib/evilution/reporter/suggestion/templates.rb +6 -0
- data/lib/evilution/result/error_info.rb +20 -0
- data/lib/evilution/result/memory_stats.rb +20 -0
- data/lib/evilution/result/mutation_result.rb +30 -14
- data/lib/evilution/runner/baseline_runner.rb +1 -2
- data/lib/evilution/runner/diagnostics.rb +1 -2
- data/lib/evilution/runner/isolation_resolver.rb +10 -4
- data/lib/evilution/runner/mutation_executor/mutation_runner.rb +30 -0
- data/lib/evilution/runner/mutation_executor/neutralization_pipeline.rb +15 -0
- data/lib/evilution/runner/mutation_executor/neutralizer/baseline_failed.rb +39 -0
- data/lib/evilution/runner/mutation_executor/neutralizer/infra_error.rb +68 -0
- data/lib/evilution/runner/mutation_executor/neutralizer.rb +11 -0
- data/lib/evilution/runner/mutation_executor/result_cache.rb +67 -0
- data/lib/evilution/runner/mutation_executor/result_notifier.rb +46 -0
- data/lib/evilution/runner/mutation_executor/result_packer.rb +41 -0
- data/lib/evilution/runner/mutation_executor/strategy/parallel.rb +78 -0
- data/lib/evilution/runner/mutation_executor/strategy/sequential.rb +32 -0
- data/lib/evilution/runner/mutation_executor/strategy.rb +11 -0
- data/lib/evilution/runner/mutation_executor.rb +53 -292
- data/lib/evilution/runner/mutation_planner.rb +1 -2
- data/lib/evilution/runner/report_publisher.rb +1 -2
- data/lib/evilution/runner/subject_pipeline.rb +1 -2
- data/lib/evilution/runner.rb +53 -30
- data/lib/evilution/version.rb +1 -1
- data/lib/evilution.rb +1 -0
- data/script/memory_check +3 -1
- metadata +125 -3
- data/lib/evilution/reporter/html/namespace.rb +0 -11
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../compare"
|
|
4
|
+
|
|
5
|
+
# Collapses whitespace runs in source-code text while preserving the contents
|
|
6
|
+
# of "..." and '...' string literals. Used for fingerprinting mutation diffs
|
|
7
|
+
# so that whitespace-only differences do not cause false fingerprint mismatches
|
|
8
|
+
# across tooling (evilution vs mutant).
|
|
9
|
+
#
|
|
10
|
+
# v1 limitation: only " and ' literals are preserved. Regex literals (/.../),
|
|
11
|
+
# heredocs, %w[], %q{} forms are treated as ordinary code — whitespace runs
|
|
12
|
+
# inside them collapse. A mutation touching whitespace inside a regex may
|
|
13
|
+
# false-match across tools.
|
|
14
|
+
class Evilution::Compare::LineNormalizer
|
|
15
|
+
QUOTES = ['"', "'"].freeze
|
|
16
|
+
WHITESPACE = [" ", "\t"].freeze
|
|
17
|
+
private_constant :QUOTES, :WHITESPACE
|
|
18
|
+
|
|
19
|
+
def call(line)
|
|
20
|
+
@chars = line.chars
|
|
21
|
+
@i = 0
|
|
22
|
+
@out = +""
|
|
23
|
+
@in_literal = nil
|
|
24
|
+
@last_was_space = false
|
|
25
|
+
|
|
26
|
+
@i += step while @i < @chars.length
|
|
27
|
+
result = @out.rstrip
|
|
28
|
+
@chars = nil
|
|
29
|
+
@out = nil
|
|
30
|
+
result
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def step
|
|
36
|
+
ch = @chars[@i]
|
|
37
|
+
return step_in_literal(ch) if @in_literal
|
|
38
|
+
return step_open_quote(ch) if QUOTES.include?(ch)
|
|
39
|
+
return step_whitespace if WHITESPACE.include?(ch)
|
|
40
|
+
|
|
41
|
+
append_regular(ch)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def step_in_literal(ch)
|
|
45
|
+
@out << ch
|
|
46
|
+
if ch == "\\" && @i + 1 < @chars.length
|
|
47
|
+
@out << @chars[@i + 1]
|
|
48
|
+
return 2
|
|
49
|
+
end
|
|
50
|
+
@in_literal = nil if ch == @in_literal
|
|
51
|
+
1
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def step_open_quote(ch)
|
|
55
|
+
@in_literal = ch
|
|
56
|
+
@out << ch
|
|
57
|
+
@last_was_space = false
|
|
58
|
+
1
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def step_whitespace
|
|
62
|
+
@out << " " unless @last_was_space || @out.empty?
|
|
63
|
+
@last_was_space = true
|
|
64
|
+
1
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def append_regular(ch)
|
|
68
|
+
@out << ch
|
|
69
|
+
@last_was_space = false
|
|
70
|
+
1
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
require_relative "../compare"
|
|
4
4
|
require_relative "record"
|
|
5
5
|
require_relative "fingerprint"
|
|
6
|
+
require_relative "line_normalizer"
|
|
7
|
+
require_relative "diff_extractor/evilution"
|
|
8
|
+
require_relative "diff_extractor/mutant"
|
|
6
9
|
|
|
7
10
|
class Evilution::Compare::Normalizer
|
|
8
11
|
EVILUTION_BUCKETS = %w[killed survived timed_out errors neutral equivalent unresolved unparseable].freeze
|
|
@@ -17,6 +20,18 @@ class Evilution::Compare::Normalizer
|
|
|
17
20
|
"unparseable" => :unparseable
|
|
18
21
|
}.freeze
|
|
19
22
|
|
|
23
|
+
def initialize
|
|
24
|
+
line_normalizer = Evilution::Compare::LineNormalizer.new
|
|
25
|
+
@evilution_fingerprint = Evilution::Compare::Fingerprint.new(
|
|
26
|
+
extractor: Evilution::Compare::DiffExtractor::Evilution.new,
|
|
27
|
+
normalizer: line_normalizer
|
|
28
|
+
)
|
|
29
|
+
@mutant_fingerprint = Evilution::Compare::Fingerprint.new(
|
|
30
|
+
extractor: Evilution::Compare::DiffExtractor::Mutant.new,
|
|
31
|
+
normalizer: line_normalizer
|
|
32
|
+
)
|
|
33
|
+
end
|
|
34
|
+
|
|
20
35
|
def from_evilution(json)
|
|
21
36
|
records = []
|
|
22
37
|
EVILUTION_BUCKETS.each do |bucket|
|
|
@@ -47,13 +62,12 @@ class Evilution::Compare::Normalizer
|
|
|
47
62
|
diff = entry["diff"].to_s
|
|
48
63
|
status = EVILUTION_STATUS_MAP[entry["status"]] ||
|
|
49
64
|
raise(Evilution::Compare::InvalidInput.new("unknown status #{entry["status"].inspect}", index: index))
|
|
50
|
-
body = Evilution::Compare::Fingerprint.extract_from_evilution_diff(diff)
|
|
51
65
|
Evilution::Compare::Record.new(
|
|
52
66
|
source: :evilution,
|
|
53
67
|
file_path: file_path,
|
|
54
68
|
line: line,
|
|
55
69
|
status: status,
|
|
56
|
-
fingerprint:
|
|
70
|
+
fingerprint: @evilution_fingerprint.call(diff: diff, file_path: file_path, line: line),
|
|
57
71
|
operator: entry["operator"],
|
|
58
72
|
diff_body: diff,
|
|
59
73
|
raw: entry
|
|
@@ -67,13 +81,12 @@ class Evilution::Compare::Normalizer
|
|
|
67
81
|
line = parse_mutant_line(ident, index)
|
|
68
82
|
diff = mr["mutation_diff"].to_s
|
|
69
83
|
status = derive_mutant_status(mr, cr, index)
|
|
70
|
-
body = Evilution::Compare::Fingerprint.extract_from_mutant_diff(diff)
|
|
71
84
|
Evilution::Compare::Record.new(
|
|
72
85
|
source: :mutant,
|
|
73
86
|
file_path: source_path,
|
|
74
87
|
line: line,
|
|
75
88
|
status: status,
|
|
76
|
-
fingerprint:
|
|
89
|
+
fingerprint: @mutant_fingerprint.call(diff: diff, file_path: source_path, line: line),
|
|
77
90
|
operator: nil,
|
|
78
91
|
diff_body: diff,
|
|
79
92
|
raw: { "mutation_result" => mr, "criteria_result" => cr, "source_path" => source_path }
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../builders"
|
|
4
|
+
require_relative "../../spec_resolver"
|
|
5
|
+
|
|
6
|
+
class Evilution::Config::Builders::SpecResolver
|
|
7
|
+
def self.call(integration:)
|
|
8
|
+
case integration
|
|
9
|
+
when :minitest
|
|
10
|
+
Evilution::SpecResolver.new(test_dir: "test", test_suffix: "_test.rb", request_dir: "integration")
|
|
11
|
+
else
|
|
12
|
+
Evilution::SpecResolver.new
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../builders"
|
|
4
|
+
require_relative "spec_resolver"
|
|
5
|
+
require_relative "../../spec_selector"
|
|
6
|
+
|
|
7
|
+
class Evilution::Config::Builders::SpecSelector
|
|
8
|
+
def self.call(spec_files:, spec_mappings:, spec_pattern:, integration:)
|
|
9
|
+
Evilution::SpecSelector.new(
|
|
10
|
+
spec_files: spec_files,
|
|
11
|
+
spec_mappings: spec_mappings,
|
|
12
|
+
spec_pattern: spec_pattern,
|
|
13
|
+
spec_resolver: Evilution::Config::Builders::SpecResolver.call(integration: integration)
|
|
14
|
+
)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Evilution::Config::EnvLoader
|
|
4
|
+
module_function
|
|
5
|
+
|
|
6
|
+
def load
|
|
7
|
+
opts = {}
|
|
8
|
+
val = ENV.fetch("EV_DISABLE_EXAMPLE_TARGETING", nil)
|
|
9
|
+
opts[:example_targeting] = false if val && !val.empty? && val != "0"
|
|
10
|
+
opts
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
|
|
5
|
+
module Evilution::Config::FileLoader
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
def load
|
|
9
|
+
Evilution::Config::CONFIG_FILES.each do |path|
|
|
10
|
+
next unless File.exist?(path)
|
|
11
|
+
|
|
12
|
+
data = YAML.safe_load_file(path, symbolize_names: true)
|
|
13
|
+
return data.is_a?(Hash) ? data : {}
|
|
14
|
+
rescue Psych::SyntaxError, Psych::DisallowedClass => e
|
|
15
|
+
raise Evilution::ConfigError.new("failed to parse config file #{path}: #{e.message}", file: path)
|
|
16
|
+
rescue SystemCallError => e
|
|
17
|
+
raise Evilution::ConfigError.new("cannot read config file #{path}: #{e.message}", file: path)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
{}
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "file_loader"
|
|
4
|
+
require_relative "env_loader"
|
|
5
|
+
|
|
6
|
+
module Evilution::Config::Sources
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
def merge(explicit:, skip_file:)
|
|
10
|
+
file = skip_file ? {} : Evilution::Config::FileLoader.load
|
|
11
|
+
env = Evilution::Config::EnvLoader.load
|
|
12
|
+
Evilution::Config::DEFAULTS.merge(file).merge(env).merge(explicit)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../validators"
|
|
4
|
+
|
|
5
|
+
class Evilution::Config::Validators::Base
|
|
6
|
+
def self.call(_value)
|
|
7
|
+
raise NotImplementedError
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
class << self
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def coerce_symbol!(value, allowed:, name:)
|
|
14
|
+
raise Evilution::ConfigError, "#{name} must be #{allowed.join(" or ")}, got nil" if value.nil?
|
|
15
|
+
|
|
16
|
+
unless value.is_a?(String) || value.is_a?(Symbol)
|
|
17
|
+
raise Evilution::ConfigError, "#{name} must be #{allowed.join(" or ")}, got #{value.inspect}"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
sym = value.to_sym
|
|
21
|
+
return sym if allowed.include?(sym)
|
|
22
|
+
|
|
23
|
+
raise Evilution::ConfigError, "#{name} must be #{allowed.join(" or ")}, got #{sym.inspect}"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def coerce_positive_int!(value, name:)
|
|
27
|
+
raise Evilution::ConfigError, "#{name} must be a positive integer, got #{value.inspect}" if value.is_a?(Float)
|
|
28
|
+
|
|
29
|
+
int = Integer(value)
|
|
30
|
+
raise Evilution::ConfigError, "#{name} must be a positive integer, got #{int}" unless int >= 1
|
|
31
|
+
|
|
32
|
+
int
|
|
33
|
+
rescue ::ArgumentError, ::TypeError
|
|
34
|
+
raise Evilution::ConfigError, "#{name} must be a positive integer, got #{value.inspect}"
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
|
|
5
|
+
class Evilution::Config::Validators::ExampleTargetingCache < Evilution::Config::Validators::Base
|
|
6
|
+
def self.call(value)
|
|
7
|
+
raise Evilution::ConfigError, "example_targeting_cache must be a Hash, got #{value.class}" unless value.is_a?(Hash)
|
|
8
|
+
|
|
9
|
+
normalized = normalize_keys(value)
|
|
10
|
+
merged = Evilution::Config::DEFAULTS[:example_targeting_cache].merge(normalized)
|
|
11
|
+
require_positive_int!(merged, :max_files)
|
|
12
|
+
require_positive_int!(merged, :max_blocks)
|
|
13
|
+
merged
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
class << self
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def normalize_keys(value)
|
|
20
|
+
value.each_with_object({}) do |(k, v), acc|
|
|
21
|
+
unless k.is_a?(String) || k.is_a?(Symbol)
|
|
22
|
+
raise Evilution::ConfigError,
|
|
23
|
+
"example_targeting_cache keys must be Strings or Symbols, got #{k.inspect}"
|
|
24
|
+
end
|
|
25
|
+
acc[k.to_sym] = v
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def require_positive_int!(cache, key)
|
|
30
|
+
v = cache[key]
|
|
31
|
+
return if v.is_a?(Integer) && v >= 1
|
|
32
|
+
|
|
33
|
+
raise Evilution::ConfigError,
|
|
34
|
+
"example_targeting_cache.#{key} must be a positive integer, got #{v.inspect}"
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
|
|
5
|
+
class Evilution::Config::Validators::ExampleTargetingFallback < Evilution::Config::Validators::Base
|
|
6
|
+
FALLBACKS = %i[full_file unresolved].freeze
|
|
7
|
+
|
|
8
|
+
def self.call(value)
|
|
9
|
+
unless value.is_a?(String) || value.is_a?(Symbol)
|
|
10
|
+
raise Evilution::ConfigError,
|
|
11
|
+
"example_targeting_fallback must be full_file or unresolved, got #{value.inspect}"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
sym = value.to_sym
|
|
15
|
+
unless FALLBACKS.include?(sym)
|
|
16
|
+
raise Evilution::ConfigError,
|
|
17
|
+
"example_targeting_fallback must be full_file or unresolved, got #{sym.inspect}"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
sym
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
|
|
5
|
+
class Evilution::Config::Validators::FailFast < Evilution::Config::Validators::Base
|
|
6
|
+
def self.call(value)
|
|
7
|
+
return nil if value.nil?
|
|
8
|
+
|
|
9
|
+
coerce_positive_int!(value, name: "fail_fast")
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
|
|
5
|
+
class Evilution::Config::Validators::Hooks < Evilution::Config::Validators::Base
|
|
6
|
+
def self.call(value)
|
|
7
|
+
return {} if value.nil?
|
|
8
|
+
raise Evilution::ConfigError, "hooks must be a mapping of event names to file paths, got #{value.class}" unless value.is_a?(Hash)
|
|
9
|
+
|
|
10
|
+
value
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
|
|
5
|
+
class Evilution::Config::Validators::IgnorePatterns < Evilution::Config::Validators::Base
|
|
6
|
+
def self.call(value)
|
|
7
|
+
patterns = Array(value)
|
|
8
|
+
patterns.each do |pattern|
|
|
9
|
+
unless pattern.is_a?(String)
|
|
10
|
+
raise Evilution::ConfigError,
|
|
11
|
+
"ignore_patterns must be an array of strings, got #{pattern.class} (#{pattern.inspect})"
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
patterns
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
|
|
5
|
+
class Evilution::Config::Validators::Integration < Evilution::Config::Validators::Base
|
|
6
|
+
ALLOWED = %i[rspec minitest].freeze
|
|
7
|
+
|
|
8
|
+
def self.call(value)
|
|
9
|
+
coerce_symbol!(value, allowed: ALLOWED, name: "integration")
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
|
|
5
|
+
class Evilution::Config::Validators::Isolation < Evilution::Config::Validators::Base
|
|
6
|
+
ALLOWED = %i[auto fork in_process].freeze
|
|
7
|
+
MESSAGE = "isolation must be auto, fork, or in_process"
|
|
8
|
+
|
|
9
|
+
def self.call(value)
|
|
10
|
+
raise Evilution::ConfigError, "#{MESSAGE}, got nil" if value.nil?
|
|
11
|
+
|
|
12
|
+
raise Evilution::ConfigError, "#{MESSAGE}, got #{value.inspect}" unless value.is_a?(String) || value.is_a?(Symbol)
|
|
13
|
+
|
|
14
|
+
sym = value.to_sym
|
|
15
|
+
return sym if ALLOWED.include?(sym)
|
|
16
|
+
|
|
17
|
+
raise Evilution::ConfigError, "#{MESSAGE}, got #{sym.inspect}"
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
|
|
5
|
+
class Evilution::Config::Validators::Preload < Evilution::Config::Validators::Base
|
|
6
|
+
def self.call(value)
|
|
7
|
+
return nil if value.nil?
|
|
8
|
+
return false if value == false
|
|
9
|
+
return value if value.is_a?(String)
|
|
10
|
+
|
|
11
|
+
raise Evilution::ConfigError, "preload must be nil, false, or a String path, got #{value.inspect}"
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
|
|
5
|
+
class Evilution::Config::Validators::Profile < Evilution::Config::Validators::Base
|
|
6
|
+
ALLOWED = %i[default strict].freeze
|
|
7
|
+
|
|
8
|
+
def self.call(value)
|
|
9
|
+
coerce_symbol!(value, allowed: ALLOWED, name: "profile")
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
|
|
5
|
+
class Evilution::Config::Validators::SpecMappings < Evilution::Config::Validators::Base
|
|
6
|
+
def self.call(value)
|
|
7
|
+
return {} if value.nil?
|
|
8
|
+
|
|
9
|
+
raise Evilution::ConfigError, "spec_mappings must be a Hash, got #{value.class}" unless value.is_a?(Hash)
|
|
10
|
+
|
|
11
|
+
normalized = value.each_with_object({}) do |(source, specs), acc|
|
|
12
|
+
key = normalize_key(source)
|
|
13
|
+
acc[key] = normalize_value(key, specs)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
warn_missing(normalized)
|
|
17
|
+
normalized
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
class << self
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def normalize_key(source)
|
|
24
|
+
key = source.to_s
|
|
25
|
+
key = key.delete_prefix("#{Dir.pwd}/") if key.start_with?("/")
|
|
26
|
+
key.delete_prefix("./")
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def normalize_value(source, specs)
|
|
30
|
+
case specs
|
|
31
|
+
when String then [specs]
|
|
32
|
+
when Array
|
|
33
|
+
specs.each do |entry|
|
|
34
|
+
unless entry.is_a?(String)
|
|
35
|
+
raise Evilution::ConfigError,
|
|
36
|
+
"spec_mappings[#{source.inspect}] entries must be string paths, got #{entry.class}"
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
specs
|
|
40
|
+
else
|
|
41
|
+
raise Evilution::ConfigError,
|
|
42
|
+
"spec_mappings[#{source.inspect}] must be a string or array of strings, got #{specs.class}"
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def warn_missing(mappings)
|
|
47
|
+
mappings.each do |source, specs|
|
|
48
|
+
specs.each do |spec_path|
|
|
49
|
+
next if File.exist?(spec_path)
|
|
50
|
+
|
|
51
|
+
warn "[evilution] spec_mappings[#{source.inspect}]: #{spec_path} not found, skipping"
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
|
|
5
|
+
class Evilution::Config::Validators::SpecPattern < Evilution::Config::Validators::Base
|
|
6
|
+
def self.call(value)
|
|
7
|
+
return nil if value.nil?
|
|
8
|
+
return value if value.is_a?(String)
|
|
9
|
+
|
|
10
|
+
raise Evilution::ConfigError, "spec_pattern must be nil or a String glob, got #{value.class}"
|
|
11
|
+
end
|
|
12
|
+
end
|