Class: Henitai::Configuration

Inherits:
Object
  • Object
show all
Defined in:
lib/henitai/configuration.rb,
sig/henitai.rbs

Overview

Loads and validates .henitai.yml configuration.

Configuration is resolved from built-in defaults and the project-root .henitai.yml file.

Constant Summary collapse

DEFAULT_TIMEOUT =

Returns:

  • (Float)
10.0
DEFAULT_TIMEOUT_MULTIPLIER =

Returns:

  • (Float)
3.0
DEFAULT_OPERATORS =

Returns:

  • (Symbol)
:light
DEFAULT_JOBS =

Returns:

  • (Object)
1
DEFAULT_MAX_FLAKY_RETRIES =

Returns:

  • (Integer)
3
DEFAULT_MAX_LOG_BYTES =

Cap on captured child stdout/stderr per stream (bytes). A runaway mutant (e.g. one that puts henitai's own suite into a spewing recursion) can otherwise write hundreds of MB per child; overflow is discarded.

Returns:

  • (Integer)
5_000_000
DEFAULT_MAX_TIMEOUT =

Ceiling for the per-mutant auto-calibrated timeout (seconds). Keeps a runaway from running for minutes when the calibrated value is large.

Returns:

  • (Float)
30.0
DEFAULT_CHECKPOINT_EVERY =

Incremental report checkpoint cadence for long full runs.

Returns:

  • (Integer)
200
DEFAULT_CHECKPOINT_INTERVAL =

Returns:

  • (Float)
30.0
DEFAULT_REPORTS_DIR =

Returns:

  • (String)
"reports"
DEFAULT_COVERAGE_CRITERIA =

All three default to true because Result's MS numerator has always counted killed + timeout + runtime_error unconditionally. Until 0.5.0 this block was validated but never read, so the shipped false defaults described behavior the scorer did not have. Wiring it up with those defaults intact would have silently dropped timeouts and aborts out of every user's score; flipping them keeps the numerator exactly as it was and makes the knob mean what it says. See Result::CRITERION_STATUSES for the mapping.

Returns:

  • (Hash[Symbol, bool])
{
  test_result: true,
  timeout: true,
  process_abort: true
}.freeze
DEFAULT_THRESHOLDS =

Returns:

  • (Hash[Symbol, Integer])
{ high: 80, low: 60 }.freeze
CONFIG_FILE =

Returns:

  • (String)
".henitai.yml"
DEFAULT_MAX_MUTANTS_PER_LINE =

Returns:

  • (Integer)

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(path: CONFIG_FILE, overrides: {}) ⇒ Configuration

Returns a new instance of Configuration.

Parameters:

  • path: (String) (defaults to: CONFIG_FILE)
  • overrides: (Hash[Symbol, untyped]) (defaults to: {})


63
64
65
66
67
68
69
70
71
72
73
# File 'lib/henitai/configuration.rb', line 63

def initialize(path: CONFIG_FILE, overrides: {})
  @config_dir = File.dirname(File.expand_path(path))
  raw = load_raw_configuration(path)
  unless raw.is_a?(Hash)
    raise Henitai::ConfigurationError,
          "Invalid configuration value for configuration: expected Hash, got #{raw.class}"
  end
  merged = merge_defaults(raw, symbolize_keys(overrides))
  ConfigurationValidator.validate!(merged)
  apply_defaults(merged)
end

Instance Attribute Details

#all_logsBoolean (readonly)

Returns the value of attribute all_logs.

Returns:

  • (Boolean)


44
45
46
# File 'lib/henitai/configuration.rb', line 44

def all_logs
  @all_logs
end

#checkpoint_enabledBoolean (readonly)

Returns the value of attribute checkpoint_enabled.

Returns:

  • (Boolean)


44
45
46
# File 'lib/henitai/configuration.rb', line 44

def checkpoint_enabled
  @checkpoint_enabled
end

#checkpoint_everyInteger (readonly)

Returns the value of attribute checkpoint_every.

Returns:

  • (Integer)


44
45
46
# File 'lib/henitai/configuration.rb', line 44

def checkpoint_every
  @checkpoint_every
end

#checkpoint_intervalFloat (readonly)

Returns the value of attribute checkpoint_interval.

Returns:

  • (Float)


44
45
46
# File 'lib/henitai/configuration.rb', line 44

def checkpoint_interval
  @checkpoint_interval
end

#coverage_criteriaHash[Symbol, untyped] (readonly)

Returns the value of attribute coverage_criteria.

Returns:

  • (Hash[Symbol, untyped])


44
45
46
# File 'lib/henitai/configuration.rb', line 44

def coverage_criteria
  @coverage_criteria
end

#dashboardHash[Symbol, untyped] (readonly)

Returns the value of attribute dashboard.

Returns:

  • (Hash[Symbol, untyped])


44
45
46
# File 'lib/henitai/configuration.rb', line 44

def dashboard
  @dashboard
end

#excludesArray[String] (readonly)

Returns the value of attribute excludes.

Returns:

  • (Array[String])


44
45
46
# File 'lib/henitai/configuration.rb', line 44

def excludes
  @excludes
end

#ignore_patternsArray[untyped] (readonly)

Returns the value of attribute ignore_patterns.

Returns:

  • (Array[untyped])


44
45
46
# File 'lib/henitai/configuration.rb', line 44

def ignore_patterns
  @ignore_patterns
end

#includesArray[String] (readonly)

Returns the value of attribute includes.

Returns:

  • (Array[String])


44
45
46
# File 'lib/henitai/configuration.rb', line 44

def includes
  @includes
end

#integrationObject (readonly)

Returns the value of attribute integration.

Returns:

  • (Object)


44
45
46
# File 'lib/henitai/configuration.rb', line 44

def integration
  @integration
end

#jobsObject (readonly)

Returns the value of attribute jobs.

Returns:

  • (Object)


44
45
46
# File 'lib/henitai/configuration.rb', line 44

def jobs
  @jobs
end

#max_flaky_retriesInteger (readonly)

Returns the value of attribute max_flaky_retries.

Returns:

  • (Integer)


44
45
46
# File 'lib/henitai/configuration.rb', line 44

def max_flaky_retries
  @max_flaky_retries
end

#max_log_bytesInteger (readonly)

Returns the value of attribute max_log_bytes.

Returns:

  • (Integer)


44
45
46
# File 'lib/henitai/configuration.rb', line 44

def max_log_bytes
  @max_log_bytes
end

#max_mutants_per_lineInteger (readonly)

Returns the value of attribute max_mutants_per_line.

Returns:

  • (Integer)


188
189
190
# File 'sig/henitai.rbs', line 188

def max_mutants_per_line
  @max_mutants_per_line
end

#max_timeoutFloat (readonly)

Returns the value of attribute max_timeout.

Returns:

  • (Float)


44
45
46
# File 'lib/henitai/configuration.rb', line 44

def max_timeout
  @max_timeout
end

#operatorsSymbol (readonly)

Returns the value of attribute operators.

Returns:

  • (Symbol)


44
45
46
# File 'lib/henitai/configuration.rb', line 44

def operators
  @operators
end

#reportersArray[String] (readonly)

Returns the value of attribute reporters.

Returns:

  • (Array[String])


44
45
46
# File 'lib/henitai/configuration.rb', line 44

def reporters
  @reporters
end

#reports_dirString (readonly)

Returns the value of attribute reports_dir.

Returns:

  • (String)


44
45
46
# File 'lib/henitai/configuration.rb', line 44

def reports_dir
  @reports_dir
end

#samplingObject (readonly)

Returns the value of attribute sampling.

Returns:

  • (Object)


44
45
46
# File 'lib/henitai/configuration.rb', line 44

def sampling
  @sampling
end

#test_excludesArray[String] (readonly)

Returns the value of attribute test_excludes.

Returns:

  • (Array[String])


44
45
46
# File 'lib/henitai/configuration.rb', line 44

def test_excludes
  @test_excludes
end

#thresholdsHash[Symbol, Integer] (readonly)

Returns the value of attribute thresholds.

Returns:

  • (Hash[Symbol, Integer])


44
45
46
# File 'lib/henitai/configuration.rb', line 44

def thresholds
  @thresholds
end

#timeoutFloat (readonly)

Returns the value of attribute timeout.

Returns:

  • (Float)


44
45
46
# File 'lib/henitai/configuration.rb', line 44

def timeout
  @timeout
end

#timeout_multiplierFloat (readonly)

Returns the value of attribute timeout_multiplier.

Returns:

  • (Float)


44
45
46
# File 'lib/henitai/configuration.rb', line 44

def timeout_multiplier
  @timeout_multiplier
end

Class Method Details

.load(path: CONFIG_FILE, overrides: {}) ⇒ Configuration

Parameters:

  • path (String) (defaults to: CONFIG_FILE)

    path to .henitai.yml (default: project root)

  • path: (String) (defaults to: CONFIG_FILE)
  • overrides: (Hash[Symbol, untyped]) (defaults to: {})

Returns:



59
60
61
# File 'lib/henitai/configuration.rb', line 59

def self.load(path: CONFIG_FILE, overrides: {})
  new(path:, overrides:)
end

Instance Method Details

#apply_analysis_defaults(raw) ⇒ void

This method returns an undefined value.

Parameters:

  • (Hash[Symbol, untyped])


136
137
138
139
140
# File 'lib/henitai/configuration.rb', line 136

def apply_analysis_defaults(raw)
  @coverage_criteria = merge_defaults(DEFAULT_COVERAGE_CRITERIA,
                                      raw[:coverage_criteria])
  @thresholds = merge_defaults(DEFAULT_THRESHOLDS, raw[:thresholds])
end

#apply_defaults(raw) ⇒ void

This method returns an undefined value.

Parameters:

  • (Hash[Symbol, untyped])


92
93
94
95
96
97
# File 'lib/henitai/configuration.rb', line 92

def apply_defaults(raw)
  apply_general_defaults(raw)
  apply_mutation_defaults(raw)
  apply_reports_defaults(raw)
  apply_analysis_defaults(raw)
end

#apply_general_defaults(raw) ⇒ void

This method returns an undefined value.

Parameters:

  • (Hash[Symbol, untyped])


99
100
101
102
103
104
105
106
107
108
109
# File 'lib/henitai/configuration.rb', line 99

def apply_general_defaults(raw)
  @integration = resolve_integration_default(raw[:integration])
  @includes = raw[:includes] || ["lib"]
  @excludes = raw[:excludes] || []
  @test_excludes = raw[:test_excludes] || []
  @jobs = raw.fetch(:jobs, DEFAULT_JOBS)
  @reporters = raw[:reporters] || ["terminal"]
  @reports_dir = raw[:reports_dir] || DEFAULT_REPORTS_DIR
  @all_logs = raw[:all_logs] == true
  @dashboard = default_dashboard(raw[:dashboard])
end

#apply_mutation_defaults(raw) ⇒ void

This method returns an undefined value.

Parameters:

  • (Hash[Symbol, untyped])


111
112
113
114
115
116
117
118
119
120
121
# File 'lib/henitai/configuration.rb', line 111

def apply_mutation_defaults(raw)
  mutation = raw[:mutation] || {}

  @operators = (mutation[:operators] || DEFAULT_OPERATORS).to_sym
  @timeout_configured = !mutation[:timeout].nil?
  @timeout = mutation[:timeout] || DEFAULT_TIMEOUT
  @timeout_multiplier = mutation[:timeout_multiplier] || DEFAULT_TIMEOUT_MULTIPLIER
  @ignore_patterns = mutation[:ignore_patterns] || []
  @sampling = mutation[:sampling]
  apply_mutation_limit_defaults(mutation)
end

#apply_mutation_limit_defaults(mutation) ⇒ void

This method returns an undefined value.

Parameters:

  • (Hash[Symbol, untyped])


123
124
125
126
127
# File 'lib/henitai/configuration.rb', line 123

def apply_mutation_limit_defaults(mutation)
  @max_flaky_retries = mutation.fetch(:max_flaky_retries, DEFAULT_MAX_FLAKY_RETRIES)
  @max_log_bytes = mutation[:max_log_bytes] || DEFAULT_MAX_LOG_BYTES
  @max_timeout = mutation[:max_timeout] || DEFAULT_MAX_TIMEOUT
end

#apply_reports_defaults(raw) ⇒ void

This method returns an undefined value.

Parameters:

  • (Hash[Symbol, untyped])


129
130
131
132
133
134
# File 'lib/henitai/configuration.rb', line 129

def apply_reports_defaults(raw)
  reports = raw[:reports] || {}
  @checkpoint_enabled = reports.fetch(:checkpoint, true)
  @checkpoint_every = reports[:checkpoint_every] || DEFAULT_CHECKPOINT_EVERY
  @checkpoint_interval = reports[:checkpoint_interval] || DEFAULT_CHECKPOINT_INTERVAL
end

#default_dashboard(overrides) ⇒ Hash[Symbol, untyped]

Parameters:

  • (Hash[Symbol, untyped], nil)

Returns:

  • (Hash[Symbol, untyped])


161
162
163
164
165
# File 'lib/henitai/configuration.rb', line 161

def default_dashboard(overrides)
  # @type var empty_dashboard: Hash[Symbol, untyped]
  empty_dashboard = {}
  merge_defaults(empty_dashboard, overrides)
end

#detect_integrationString

Returns:

  • (String)


84
85
86
87
88
89
90
# File 'lib/henitai/configuration.rb', line 84

def detect_integration
  return "rspec"    if File.exist?(File.join(@config_dir, ".rspec"))
  return "minitest" if File.directory?(File.join(@config_dir, "test"))
  return "rspec"    if File.directory?(File.join(@config_dir, "spec"))

  "rspec"
end

#load_raw_configuration(path) ⇒ Hash[Symbol, untyped]

Parameters:

  • (String)

Returns:

  • (Hash[Symbol, untyped])


77
78
79
80
81
82
# File 'lib/henitai/configuration.rb', line 77

def load_raw_configuration(path)
  return {} unless File.exist?(path)

  raw = Psych.safe_load(File.read(path), symbolize_names: true)
  raw || {}
end

#merge_defaults(defaults, overrides) ⇒ Hash[Symbol, untyped]

Parameters:

  • (Hash[Symbol, untyped])
  • (Hash[Symbol, untyped], nil)

Returns:

  • (Hash[Symbol, untyped])


142
143
144
145
146
147
148
149
150
151
152
# File 'lib/henitai/configuration.rb', line 142

def merge_defaults(defaults, overrides)
  return defaults.dup if overrides.nil?

  defaults.merge(overrides) do |_key, default_value, override_value|
    if default_value.is_a?(Hash) && override_value.is_a?(Hash)
      merge_defaults(default_value, override_value)
    else
      override_value
    end
  end
end

#resolve_integration_default(integration) ⇒ Object

Parameters:

  • (Object)

Returns:

  • (Object)


154
155
156
157
158
159
# File 'lib/henitai/configuration.rb', line 154

def resolve_integration_default(integration)
  return integration[:name] || detect_integration if integration.is_a?(Hash)
  return detect_integration if integration.nil?

  integration
end

#symbolize_keys(value) ⇒ Object

Parameters:

  • (Object)

Returns:

  • (Object)


167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/henitai/configuration.rb', line 167

def symbolize_keys(value)
  case value
  when Hash
    # @type var result: Hash[Symbol, untyped]
    result = {}
    value.each do |key, val|
      result[key.to_sym] = symbolize_keys(val)
    end
    result
  when Array
    value.map { |item| symbolize_keys(item) }
  else
    value
  end
end

#timeout_configured?Boolean

True when mutation.timeout was set explicitly (file or CLI override); a fixed timeout disables per-mutant auto-calibration.

Returns:

  • (Boolean)


54
55
56
# File 'lib/henitai/configuration.rb', line 54

def timeout_configured?
  @timeout_configured
end