12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
|
# File 'lib/active_mutator/cli.rb', line 12
def self.parse(argv)
options = {
since: nil, subject_filter: nil, jobs: [Etc.nprocessors / 2, 1].max,
format: :terminal,
requires: [], timeout_factor: 8.0, timeout_floor: 10.0, force_baseline: false,
preload_helper: nil, serial_patterns: ["spec/system/", "spec/features/"],
browser_boot_seconds: 15.0, accept_survivors: false, exclude: [],
max_mutants: nil, debug_plan: false, fail_at: nil, adaptive_timeout: true,
operator_paths: []
}
options.merge!(ConfigFile.load(Dir.pwd))
paths = OptionParser.new do |o|
o.banner = "Usage: active_mutator [paths] [options]"
o.on("--since REF", "Mutate only methods changed since git REF") { |v| options[:since] = v }
o.on("--changed", "Mutate uncommitted work (alias for --since HEAD, plus untracked files)") { options[:since] = "HEAD" }
o.on("--subject NAME", "Mutate matching subjects: Foo::Bar#baz, Foo::Bar, Foo::Bar*, Foo::Bar#*") { |v| options[:subject_filter] = v }
o.on("--jobs N", Integer, "Concurrent workers (default: half the CPU count)") { |v| options[:jobs] = v }
o.on("--format FMT", ConfigFile::FORMATS, "Output format") { |v| options[:format] = v.tr("-", "_").to_sym }
o.on("--require FILE", "File to require before mutating (repeatable; adds to config-file requires)") { |v| options[:requires] << v }
o.on("--operator FILE", "Ruby file defining a custom operator, loaded before analysis (repeatable)") { |v| options[:operator_paths] << v }
o.on("--force-baseline", "Ignore cached coverage map") { options[:force_baseline] = true }
o.on("--timeout-factor F", Float, "Timeout = baseline time * F + floor") { |v| options[:timeout_factor] = v }
o.on("--timeout-floor S", Float, "Minimum timeout seconds") { |v| options[:timeout_floor] = v }
o.on("--preload-helper FILE", "Spec helper to preload in the parent (default: auto-detect)") { |v| options[:preload_helper] = v }
o.on("--no-preload-helper", "Skip spec-helper preload") { options[:preload_helper] = :none }
o.on("--serial-pattern PAT", "Covering-path prefix that forces the serial lane (repeatable; replaces defaults on first use)") do |v|
options[:serial_patterns] = [] unless options[:serial_patterns_replaced]
options[:serial_patterns_replaced] = true
options[:serial_patterns] << v
end
o.on("--browser-boot-seconds S", Float, "Extra timeout budget for serial-lane mutants") { |v| options[:browser_boot_seconds] = v }
o.on("--[no-]adaptive-timeout", "Scale timeout budgets from observed worker wall times (default: on)") { |v| options[:adaptive_timeout] = v }
o.on("--accept-survivors", "Record surviving mutants into the acceptance ledger") { options[:accept_survivors] = true }
o.on("--exclude PAT", "Skip files matching glob, relative to root (repeatable)") { |v| options[:exclude] << v }
o.on("--max-mutants N", Integer, "Deterministically sample the first N mutants") { |v| options[:max_mutants] = v }
o.on("--debug-plan", "Print the planned mutant list as JSON and exit") { options[:debug_plan] = true }
o.on("--fail-at SCORE", Float, "Exit 0 if mutation score >= SCORE even with survivors (default: any survivor fails)") do |v|
raise OptionParser::InvalidArgument, "--fail-at must be within 0..100" unless (0..100).cover?(v)
options[:fail_at] = v
end
end.parse(argv)
options.delete(:serial_patterns_replaced)
Config.new(paths: paths, root: Dir.pwd, **options)
end
|