Module: Hegel::Settings

Defined in:
lib/hegel/settings.rb,
sig/hegel.rbs

Overview

Copies Hegel.test's keyword arguments onto a libhegel settings handle. Most keywords follow one rule: nil means "leave libhegel's own default in place", so a caller of Hegel.test who passes none of them gets exactly the engine's untouched defaults (100 test cases, a random seed, no derandomize, every phase, every health check). database and database_key follow the table #apply_database documents instead, and report_multiple_failures has no nil case at all -- see #apply's own comment for why.

Constant Summary collapse

VERBOSITY_CODES =

Hegel.test's verbosity: values, mapped to hegel.h's hegel_verbosity_t.

Returns:

  • (Hash[Symbol, Integer])
{
  quiet: LibHegel::HEGEL_VERBOSITY_QUIET,
  normal: LibHegel::HEGEL_VERBOSITY_NORMAL,
  verbose: LibHegel::HEGEL_VERBOSITY_VERBOSE,
  debug: LibHegel::HEGEL_VERBOSITY_DEBUG
}.freeze
PHASE_CODES =

Hegel.test's phases: Symbols, mapped to hegel.h's hegel_phase_t. The engine ORs these together itself when every phase is passed; this table only needs one bit per Symbol.

Returns:

  • (Hash[Symbol, Integer])
{
  explicit: LibHegel::HEGEL_PHASE_EXPLICIT,
  reuse: LibHegel::HEGEL_PHASE_REUSE,
  generate: LibHegel::HEGEL_PHASE_GENERATE,
  target: LibHegel::HEGEL_PHASE_TARGET,
  shrink: LibHegel::HEGEL_PHASE_SHRINK
}.freeze
HEALTH_CHECK_CODES =

Hegel.test's suppress_health_check: Symbols, mapped to hegel.h's hegel_health_check_t.

Returns:

  • (Hash[Symbol, Integer])
{
  filter_too_much: LibHegel::HEGEL_HC_FILTER_TOO_MUCH,
  too_slow: LibHegel::HEGEL_HC_TOO_SLOW,
  test_cases_too_large: LibHegel::HEGEL_HC_TEST_CASES_TOO_LARGE,
  large_initial_test_case: LibHegel::HEGEL_HC_LARGE_INITIAL_TEST_CASE
}.freeze

Class Method Summary collapse

Class Method Details

.apply(impl, ctx, settings, test_cases:, seed:, derandomize:, verbosity:, database:, database_key:, phases:, suppress_health_check:, report_multiple_failures:, stateful_step_count:) ⇒ void

This method returns an undefined value.

Applies every one of Hegel.test's settings keywords to settings via impl. test_cases, seed, derandomize, verbosity, phases, and suppress_health_check all skip their setter when left nil. +database+/+database_key+ follow #apply_database's own table, called unconditionally since even the nil/nil case has a setter to call (see its comment). report_multiple_failures is called unconditionally too, with no nil case: Hegel.test defaults it to false rather than leaving it nil, so this method never sees nil for it -- see Hegel::Runner.run's own comment for why that default departs from every other keyword's nil-means-engine-default rule. stateful_step_count follows the same nil-means-engine-default rule as +test_cases+/+seed+/and so on: the header documents the engine's own default (50) and requires the value be at least 1, and, like +tc.target+'s own label, that requirement is left to the engine rather than re-checked here.

Parameters:

  • impl (Object)
  • ctx (Object)
  • settings (Object)
  • test_cases: (Integer, nil)
  • seed: (Integer, nil)
  • derandomize: (Boolean, nil)
  • verbosity: (Symbol, nil)
  • database: (String, nil)
  • database_key: (String, nil)
  • phases: (Array[Symbol], nil)
  • suppress_health_check: (Array[Symbol], nil)
  • report_multiple_failures: (Boolean)
  • stateful_step_count: (Integer, nil)


60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/hegel/settings.rb', line 60

def apply(impl, ctx, settings, test_cases:, seed:, derandomize:, verbosity:, database:, database_key:, phases:,
  suppress_health_check:, report_multiple_failures:, stateful_step_count:)
  impl.settings_set_test_cases(ctx, settings, test_cases) unless test_cases.nil?
  impl.settings_set_seed(ctx, settings, seed, true) unless seed.nil?
  impl.settings_set_derandomize(ctx, settings, derandomize) unless derandomize.nil?
  apply_verbosity(impl, ctx, settings, verbosity) unless verbosity.nil?
  apply_database(impl, ctx, settings, database: database, database_key: database_key)
  apply_phases(impl, ctx, settings, phases) unless phases.nil?
  apply_suppress_health_check(impl, ctx, settings, suppress_health_check) unless suppress_health_check.nil?
  impl.settings_set_report_multiple_failures(ctx, settings, report_multiple_failures)
  impl.settings_set_stateful_step_count(ctx, settings, stateful_step_count) unless stateful_step_count.nil?
end

.apply_database(impl, ctx, settings, database:, database_key:) ⇒ void

This method returns an undefined value.

docs/adr/0009-turn-the-example-database-on-with-a-key.md decides this table and the reasons behind it; read it before changing this method.

database_key: | database: | does
nil           | nil       | settings_set_database(ctx, s, "")
nil           | String    | raises Hegel::Error
String        | nil       | settings_set_database_key(ctx, s, key) only
String        | String    | settings_set_database(ctx, s, database), then settings_set_database_key

The nil/nil row calls settings_set_database("") explicitly rather than leaving it uncalled, unlike every other nil-means-default keyword here: the ADR measured that an unkeyed run writes nothing even with the engine's own default path left in place, but that is behaviour this project measured against one libhegel build, not a promise the header makes, and the cost of relying on it being wrong is a directory appearing in a caller's working copy that never asked for one.

Parameters:

  • impl (Object)
  • ctx (Object)
  • settings (Object)
  • database: (String, nil)
  • database_key: (String, nil)


99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/hegel/settings.rb', line 99

def apply_database(impl, ctx, settings, database:, database_key:)
  if database_key.nil?
    unless database.nil?
      raise Hegel::Error,
        "hegel: database: needs database_key: to scope what it stores and replays; " \
          "pass database_key: too, or drop database: and pass neither."
    end
    impl.settings_set_database(ctx, settings, "")
  else
    impl.settings_set_database(ctx, settings, database) unless database.nil?
    impl.settings_set_database_key(ctx, settings, database_key)
  end
end

.apply_phases(impl, ctx, settings, phases) ⇒ void

This method returns an undefined value.

Split from #apply so the PHASE_CODES lookup and the OR-together step are not buried inside the top-level sequence, the same reason #apply_verbosity is split out. Raises Hegel::Error for a Symbol not in PHASE_CODES, or for an empty Array: HEGEL_PHASE_* bits are additive (each one turns a phase on), and mask 0 -- what an empty Array would OR together to -- has not been measured against libhegel, unlike dropping a single named phase (see the class-level phases: keyword documentation this backs). Rejecting it here matches #apply_verbosity's own precedent: refuse at the boundary with a message naming the accepted values, rather than pass through a combination nobody has watched the engine handle.

Parameters:

  • impl (Object)
  • ctx (Object)
  • settings (Object)
  • phases (Array[Symbol])


124
125
126
127
# File 'lib/hegel/settings.rb', line 124

def apply_phases(impl, ctx, settings, phases)
  mask = mask_for(phases, PHASE_CODES, "phases")
  impl.settings_set_phases(ctx, settings, mask)
end

.apply_suppress_health_check(impl, ctx, settings, checks) ⇒ void

This method returns an undefined value.

Split from #apply for the same reason #apply_phases is. Raises Hegel::Error for a Symbol not in HEALTH_CHECK_CODES, or for an empty Array, aligned with #apply_phases's own empty-Array rule so the two keywords read the same way. The alignment is deliberate even though the two are not symmetric: 0 here is the well-documented default (no suppression), whereas nil already spells that meaning for this keyword -- "no suppression" is nil, and an empty Array is rejected the same way phases: [] is, rather than accepted as a second spelling of nil.

Parameters:

  • impl (Object)
  • ctx (Object)
  • settings (Object)
  • checks (Array[Symbol])


138
139
140
141
# File 'lib/hegel/settings.rb', line 138

def apply_suppress_health_check(impl, ctx, settings, checks)
  mask = mask_for(checks, HEALTH_CHECK_CODES, "suppress_health_check")
  impl.settings_set_suppress_health_check(ctx, settings, mask)
end

.apply_verbosity(impl, ctx, settings, verbosity) ⇒ void

This method returns an undefined value.

Split from #apply so the VERBOSITY_CODES lookup (one of several keywords that can fail) is not buried inside the top-level sequence.

Parameters:

  • impl (Object)
  • ctx (Object)
  • settings (Object)
  • verbosity (Symbol)


75
76
77
78
79
80
81
# File 'lib/hegel/settings.rb', line 75

def apply_verbosity(impl, ctx, settings, verbosity)
  code = VERBOSITY_CODES.fetch(verbosity) do
    raise Hegel::Error,
      "hegel: unknown verbosity #{verbosity.inspect}; expected one of #{VERBOSITY_CODES.keys.inspect}"
  end
  impl.settings_set_verbosity(ctx, settings, code)
end

.mask_for(values, codes, keyword) ⇒ Integer

Shared by #apply_phases and #apply_suppress_health_check: looks up every Symbol in values against codes and ORs the results together. keyword is the Hegel.test keyword being applied, named in both raised messages so a caller who passes a bad Symbol to either one is told which they got wrong -- the same reason #apply_verbosity's own message says "verbosity". codes.keys appears there the same way VERBOSITY_CODES.keys does in that method's.

Parameters:

  • values (Array[Symbol])
  • codes (Hash[Symbol, Integer])

Returns:

  • (Integer)


150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/hegel/settings.rb', line 150

def mask_for(values, codes, keyword)
  if values.empty?
    raise Hegel::Error,
      "hegel: #{keyword} expects one or more of #{codes.keys.inspect}, got an empty Array"
  end

  values.reduce(0) do |mask, value|
    code = codes.fetch(value) do
      raise Hegel::Error, "hegel: unknown #{keyword} #{value.inspect}; expected one of #{codes.keys.inspect}"
    end
    mask | code
  end
end