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 =
".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].freeze
Instance Attribute Summary collapse
-
#boot ⇒ Object
Returns the value of attribute boot.
-
#cache_dir ⇒ Object
Returns the value of attribute cache_dir.
-
#dry_run ⇒ Object
Returns the value of attribute dry_run.
-
#format ⇒ Object
Returns the value of attribute format.
-
#framework ⇒ Object
Returns the value of attribute framework.
-
#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.
-
#tests ⇒ Object
Returns the value of attribute tests.
-
#threshold ⇒ Object
Returns the value of attribute threshold.
Class Method Summary collapse
- .coerce(known_key, value, file_name) ⇒ Object
-
.detect_framework(tests) ⇒ Object
Pick rspec when a MAJORITY of the given test files end with _spec.rb; otherwise minitest.
- .field_for(known_key) ⇒ Object
-
.filter_operators(names, file_name) ⇒ Object
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.
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
# File 'lib/mutineer/config.rb', line 34 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? end |
Instance Attribute Details
#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 |
#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 |
#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 |
#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 |
#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 |
Class Method Details
.coerce(known_key, value, file_name) ⇒ Object
137 138 139 140 141 142 143 144 145 146 147 148 |
# File 'lib/mutineer/config.rb', line 137 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" else value end end |
.detect_framework(tests) ⇒ Object
Pick rspec when a MAJORITY of the given test files end with _spec.rb; otherwise minitest. Empty/ambiguous -> minitest (the safe default).
127 128 129 130 131 |
# File 'lib/mutineer/config.rb', line 127 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) ⇒ Object
133 134 135 |
# File 'lib/mutineer/config.rb', line 133 def self.field_for(known_key) known_key == "require" ? :require_paths : known_key.to_sym end |
.filter_operators(names, file_name) ⇒ Object
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.
153 154 155 156 157 158 159 160 161 162 |
# File 'lib/mutineer/config.rb', line 153 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.
54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
# File 'lib/mutineer/config.rb', line 54 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.
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
# File 'lib/mutineer/config.rb', line 73 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.
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
# File 'lib/mutineer/config.rb', line 100 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 |