Class: Mutineer::Config
- Inherits:
-
Struct
- Object
- Struct
- Mutineer::Config
- Defined in:
- lib/mutineer/config.rb
Overview
Plain run configuration, populated by the CLI (or directly by the
integration test). operators nil means "all default operators";
threshold 0.0 means the CI gate is off (spec §10).
M5 adds: jobs (parallel workers), format (human|json), output (report file), strategy (reload|redefine), require_paths (extra files to load). Config loading and the CLI > file > default precedence merge live here (KTD3/KTD4).
Boot mode adds: boot (a file to require ONCE in the parent so the app env — e.g. Rails — is booted before forking; sources are then NOT manually required) and rails (sugar: defaults boot to config/environment and strategy to redefine, and reconnects ActiveRecord per fork).
Constant Summary collapse
- CONFIG_FILE =
Config file name.
".mutineer.yml"- KNOWN_KEYS =
Keys accepted in .mutineer.yml (R7).
requiremaps to the :require_paths field. %w[operators jobs threshold only require boot rails since framework verbose ignore baseline fail_fast test_command daemon].freeze
Instance Attribute Summary collapse
-
#baseline ⇒ Object
Returns the value of attribute baseline.
-
#baseline_epsilon ⇒ Object
Returns the value of attribute baseline_epsilon.
-
#boot ⇒ Object
Returns the value of attribute boot.
-
#cache_dir ⇒ Object
Returns the value of attribute cache_dir.
-
#daemon ⇒ Object
Returns the value of attribute daemon.
-
#daemon_timeout ⇒ Object
Returns the value of attribute daemon_timeout.
-
#dry_run ⇒ Object
Returns the value of attribute dry_run.
-
#fail_fast ⇒ Object
Returns the value of attribute fail_fast.
-
#format ⇒ Object
Returns the value of attribute format.
-
#framework ⇒ Object
Returns the value of attribute framework.
-
#ignore ⇒ Object
Returns the value of attribute ignore.
-
#jobs ⇒ Object
Returns the value of attribute jobs.
-
#load_paths ⇒ Object
Returns the value of attribute load_paths.
-
#only ⇒ Object
Returns the value of attribute only.
-
#operators ⇒ Object
Returns the value of attribute operators.
-
#output ⇒ Object
Returns the value of attribute output.
-
#project_root ⇒ Object
Returns the value of attribute project_root.
-
#rails ⇒ Object
Returns the value of attribute rails.
-
#require_paths ⇒ Object
Returns the value of attribute require_paths.
-
#since ⇒ Object
Returns the value of attribute since.
-
#sources ⇒ Object
Returns the value of attribute sources.
-
#strategy ⇒ Object
Returns the value of attribute strategy.
-
#test_command ⇒ Object
Returns the value of attribute test_command.
-
#tests ⇒ Object
Returns the value of attribute tests.
-
#threshold ⇒ Object
Returns the value of attribute threshold.
-
#verbose ⇒ Object
Returns the value of attribute verbose.
Class Method Summary collapse
-
.coerce(known_key, value, file_name) ⇒ Object
Coerces a config value to its target type.
-
.detect_framework(tests) ⇒ String
Pick rspec when a MAJORITY of the given test files end with _spec.rb; otherwise minitest.
-
.field_for(known_key) ⇒ Symbol
Maps a config key to its Struct field.
-
.filter_operators(names, file_name) ⇒ Array<String>
private
Drop (with a warning) operator names the registry doesn't know (R7).
-
.find_file(start = Dir.pwd, home = File.expand_path("~")) ⇒ Object
Walk from
starttowardhome, returning the first .mutineer.yml path found or nil. -
.from_file(path) ⇒ Object
Parse a .mutineer.yml into a symbol-keyed hash of recognized keys.
-
.resolve(cli_opts, file_hash, explicit) ⇒ Object
Apply precedence (KTD3): start from the CLI-provided values, then fill in a config-file value only for keys the user did NOT type on the command line.
Instance Method Summary collapse
-
#initialize(**kwargs) ⇒ Config
constructor
A new instance of Config.
Constructor Details
#initialize(**kwargs) ⇒ Config
Returns a new instance of Config.
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
# File 'lib/mutineer/config.rb', line 39 def initialize(**kwargs) super self.sources ||= [] self.tests ||= [] self.threshold ||= 0.0 self.dry_run ||= false self.cache_dir ||= ".mutineer" self.project_root ||= Dir.pwd self.load_paths ||= ["lib"] self.jobs ||= Etc.nprocessors self.format ||= "human" self.strategy ||= "reload" self.require_paths ||= [] self.rails = false if rails.nil? self.verbose = false if verbose.nil? self.ignore ||= [] self.baseline_epsilon ||= 0.0 self.fail_fast = false if fail_fast.nil? self.daemon = false if daemon.nil? end |
Instance Attribute Details
#baseline ⇒ Object
Returns the value of attribute baseline
23 24 25 |
# File 'lib/mutineer/config.rb', line 23 def baseline @baseline end |
#baseline_epsilon ⇒ Object
Returns the value of attribute baseline_epsilon
23 24 25 |
# File 'lib/mutineer/config.rb', line 23 def baseline_epsilon @baseline_epsilon end |
#boot ⇒ Object
Returns the value of attribute boot
23 24 25 |
# File 'lib/mutineer/config.rb', line 23 def boot @boot end |
#cache_dir ⇒ Object
Returns the value of attribute cache_dir
23 24 25 |
# File 'lib/mutineer/config.rb', line 23 def cache_dir @cache_dir end |
#daemon ⇒ Object
Returns the value of attribute daemon
23 24 25 |
# File 'lib/mutineer/config.rb', line 23 def daemon @daemon end |
#daemon_timeout ⇒ Object
Returns the value of attribute daemon_timeout
23 24 25 |
# File 'lib/mutineer/config.rb', line 23 def daemon_timeout @daemon_timeout end |
#dry_run ⇒ Object
Returns the value of attribute dry_run
23 24 25 |
# File 'lib/mutineer/config.rb', line 23 def dry_run @dry_run end |
#fail_fast ⇒ Object
Returns the value of attribute fail_fast
23 24 25 |
# File 'lib/mutineer/config.rb', line 23 def fail_fast @fail_fast end |
#format ⇒ Object
Returns the value of attribute format
23 24 25 |
# File 'lib/mutineer/config.rb', line 23 def format @format end |
#framework ⇒ Object
Returns the value of attribute framework
23 24 25 |
# File 'lib/mutineer/config.rb', line 23 def framework @framework end |
#ignore ⇒ Object
Returns the value of attribute ignore
23 24 25 |
# File 'lib/mutineer/config.rb', line 23 def ignore @ignore end |
#jobs ⇒ Object
Returns the value of attribute jobs
23 24 25 |
# File 'lib/mutineer/config.rb', line 23 def jobs @jobs end |
#load_paths ⇒ Object
Returns the value of attribute load_paths
23 24 25 |
# File 'lib/mutineer/config.rb', line 23 def load_paths @load_paths end |
#only ⇒ Object
Returns the value of attribute only
23 24 25 |
# File 'lib/mutineer/config.rb', line 23 def only @only end |
#operators ⇒ Object
Returns the value of attribute operators
23 24 25 |
# File 'lib/mutineer/config.rb', line 23 def operators @operators end |
#output ⇒ Object
Returns the value of attribute output
23 24 25 |
# File 'lib/mutineer/config.rb', line 23 def output @output end |
#project_root ⇒ Object
Returns the value of attribute project_root
23 24 25 |
# File 'lib/mutineer/config.rb', line 23 def project_root @project_root end |
#rails ⇒ Object
Returns the value of attribute rails
23 24 25 |
# File 'lib/mutineer/config.rb', line 23 def rails @rails end |
#require_paths ⇒ Object
Returns the value of attribute require_paths
23 24 25 |
# File 'lib/mutineer/config.rb', line 23 def require_paths @require_paths end |
#since ⇒ Object
Returns the value of attribute since
23 24 25 |
# File 'lib/mutineer/config.rb', line 23 def since @since end |
#sources ⇒ Object
Returns the value of attribute sources
23 24 25 |
# File 'lib/mutineer/config.rb', line 23 def sources @sources end |
#strategy ⇒ Object
Returns the value of attribute strategy
23 24 25 |
# File 'lib/mutineer/config.rb', line 23 def strategy @strategy end |
#test_command ⇒ Object
Returns the value of attribute test_command
23 24 25 |
# File 'lib/mutineer/config.rb', line 23 def test_command @test_command end |
#tests ⇒ Object
Returns the value of attribute tests
23 24 25 |
# File 'lib/mutineer/config.rb', line 23 def tests @tests end |
#threshold ⇒ Object
Returns the value of attribute threshold
23 24 25 |
# File 'lib/mutineer/config.rb', line 23 def threshold @threshold end |
#verbose ⇒ Object
Returns the value of attribute verbose
23 24 25 |
# File 'lib/mutineer/config.rb', line 23 def verbose @verbose end |
Class Method Details
.coerce(known_key, value, file_name) ⇒ Object
Coerces a config value to its target type.
161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 |
# File 'lib/mutineer/config.rb', line 161 def self.coerce(known_key, value, file_name) case known_key when "operators" then filter_operators(Array(value).map(&:to_s), file_name) when "jobs" then value.to_i when "threshold" then value.to_f when "require" then Array(value).map(&:to_s) when "boot" then value.to_s when "framework" then value.to_s when "rails" then value == true || value.to_s == "true" when "daemon" then value == true || value.to_s == "true" when "verbose" then value == true || value.to_s == "true" when "ignore" then Array(value).map(&:to_s) when "baseline" then value.to_s when "test_command" then value.to_s else value end end |
.detect_framework(tests) ⇒ String
Pick rspec when a MAJORITY of the given test files end with _spec.rb; otherwise minitest. Empty/ambiguous -> minitest (the safe default). Detects the test framework from the file list.
141 142 143 144 145 |
# File 'lib/mutineer/config.rb', line 141 def self.detect_framework(tests) tests = Array(tests) specs = tests.count { |t| t.to_s.end_with?("_spec.rb") } specs > tests.length / 2.0 ? "rspec" : "minitest" end |
.field_for(known_key) ⇒ Symbol
Maps a config key to its Struct field.
151 152 153 |
# File 'lib/mutineer/config.rb', line 151 def self.field_for(known_key) known_key == "require" ? :require_paths : known_key.to_sym end |
.filter_operators(names, file_name) ⇒ Array<String>
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Drop (with a warning) operator names the registry doesn't know (R7). Referenced lazily so config.rb carries no load-order dependency on the registry; by the time a config is parsed at runtime, it is loaded. Filters out unknown operator names.
188 189 190 191 192 193 194 195 196 197 |
# File 'lib/mutineer/config.rb', line 188 def self.filter_operators(names, file_name) known = MutatorRegistry::ALL.keys names.select do |n| next true if known.include?(n) warn "mutineer: unknown operator #{n.inspect} in #{file_name} " \ "(known: #{known.join(', ')}); ignored" false end end |
.find_file(start = Dir.pwd, home = File.expand_path("~")) ⇒ Object
Walk from start toward home, returning the first .mutineer.yml path found
or nil. Checks home itself, then stops; if start is above home
(e.g. /tmp), the walk continues to the filesystem root (KTD4). Pure
discovery — reads no file content.
64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
# File 'lib/mutineer/config.rb', line 64 def self.find_file(start = Dir.pwd, home = File.("~")) dir = File.(start) loop do candidate = File.join(dir, CONFIG_FILE) return candidate if File.file?(candidate) break if dir == home parent = File.dirname(dir) break if parent == dir # filesystem root dir = parent end nil end |
.from_file(path) ⇒ Object
Parse a .mutineer.yml into a symbol-keyed hash of recognized keys. Unknown keys / unknown operator names emit a one-line stderr warning and are ignored (R7). A YAML syntax error raises ConfigError (R7a/R8) — never a silent fallback to defaults, and never an exit from the lib layer.
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 |
# File 'lib/mutineer/config.rb', line 83 def self.from_file(path) raw = YAML.safe_load(File.read(path)) || {} name = File.basename(path) unless raw.is_a?(Hash) warn "mutineer: #{name} ignored: expected a YAML mapping of keys to values" return {} end out = {} raw.each do |key, value| ks = key.to_s unless KNOWN_KEYS.include?(ks) warn "mutineer: unknown config key #{ks.inspect} in #{name} " \ "(known: #{KNOWN_KEYS.join(', ')}); ignored" next end out[field_for(ks)] = coerce(ks, value, name) end out rescue Psych::SyntaxError => e raise ConfigError, "#{File.basename(path)} parse error: #{e.}" end |
.resolve(cli_opts, file_hash, explicit) ⇒ Object
Apply precedence (KTD3): start from the CLI-provided values, then fill in a
config-file value only for keys the user did NOT type on the command line.
explicit is a Set of field symbols the CLI saw with a value; a Set (not
nil-sentinels) is used because some valid values are zero/false.
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 |
# File 'lib/mutineer/config.rb', line 110 def self.resolve(cli_opts, file_hash, explicit) merged = cli_opts.dup file_hash.each { |k, v| merged[k] = v unless explicit.include?(k) } config = new(**merged) # --rails sugar: boot config/environment and prefer the surgical (redefine) # strategy, which avoids writing tempfiles into the app tree and Zeitwerk # reload hazards. An explicit --strategy always wins. if config.rails config.boot ||= "config/environment" config.strategy = "redefine" unless explicit.include?(:strategy) # #12: parallel mutant forks share one database; transactional-fixture # setup/teardown across processes contends and deadlocks. Default to # serial under --rails; an explicit --jobs N opts back into parallelism # (with the per-worker DB-isolation that implies). config.jobs = 1 unless explicit.include?(:jobs) end # Auto-detect the framework only when neither CLI nor config file set it # (explicit value, from either source, is already on config.framework and # always wins). Default minitest unless the test files clearly look RSpec. config.framework ||= detect_framework(config.tests) config end |