Module: Hegel

Defined in:
lib/hegel.rb,
lib/hegel/errors.rb,
lib/hegel/locate.rb,
lib/hegel/report.rb,
lib/hegel/runner.rb,
lib/hegel/version.rb,
lib/hegel/settings.rb,
lib/hegel/stateful.rb,
lib/hegel/draw_name.rb,
lib/hegel/generator.rb,
lib/hegel/lib_hegel.rb,
lib/hegel/test_case.rb,
lib/hegel/generators.rb,
lib/hegel/state_machine.rb,
lib/hegel/stateful/pool.rb,
lib/hegel/lib_hegel/real.rb,
lib/hegel/syntax/methods.rb,
lib/hegel/libhegel_version.rb,
sig/hegel.rbs

Overview

Hegel is a property-based testing library for Ruby. It drives libhegel, the native engine that also backs the Rust, Go, TypeScript, Java, OCaml, and C++ implementations of the Hegel protocol.

The gem is named hegeltest while the namespace and require path are hegel, mirroring the Rust implementation: the published crate is hegeltest and callers write use hegel::....

Defined Under Namespace

Modules: DrawName, Generators, LibHegel, Locate, Report, Runner, Settings, Stateful, Syntax Classes: AssumeFailed, Error, Generator, StateMachine, StopTest, TestCase

Constant Summary collapse

FATAL_EXCEPTIONS =

Say the process is ending, not that a property failed. Every rescue Exception in this library re-raises one of these before doing anything else: catching one as a counterexample would have the engine spend its shrink budget minimising an interrupt instead of letting the process exit, and NoMemoryError in particular must not be answered with another native call.

Here rather than beside the run loop because two files now rescue Exception -- Hegel::Runner and Hegel::Stateful -- and this is the exception vocabulary both of them need, which is what this file is for.

Returns:

  • (Array[singleton(Exception)])
[Interrupt, SignalException, SystemExit, NoMemoryError].freeze
VERSION =

Returns:

  • (String)
"0.1.0"
LIBHEGEL_VERSION =

The libhegel engine release these bindings target. This is independent of Hegel::VERSION (the gem's own release): hegel-go and hegel-typescript keep the two separate too, so bumping the pinned engine can land as its own commit without also releasing a new gem version.

Returns:

  • (String)
"0.32.5"

Class Method Summary collapse

Class Method Details

.default_implObject

The Hegel::LibHegel::Real instance #test uses by default, built once and reused: Hegel::LibHegel::Real#initialize opens the native library, and that should not happen again on every #test call in a process that calls it more than once. Building it here rather than at load time also means a caller who always passes their own impl: never opens the library.

Returns:

  • (Object)


89
90
91
# File 'lib/hegel.rb', line 89

def default_impl
  @default_impl ||= LibHegel::Real.new
end

.test(test_cases: nil, seed: nil, derandomize: nil, verbosity: nil, database: nil, database_key: nil, phases: nil, suppress_health_check: nil, report_multiple_failures: false, stateful_step_count: nil, output: $stderr, reproduce_failure: nil, impl: default_impl) {|arg0| ... } ⇒ nil

Runs block as a Hegel property, drawing test cases from the tc it yields (see Hegel::TestCase). Returns nil on a passing run. On a failing run, writes a failure report to output (see Hegel::Report) and then re-raises the exception the smallest failing case's body raised, class and backtrace intact, so a host test framework reports it as its own assertion failure rather than as a Hegel-specific one. Raises Hegel::Error for a run-level failure instead of a property failure.

test_cases, seed, derandomize, verbosity, phases, and suppress_health_check all default to nil, which means the same thing for each of them: do not call the matching libhegel setter, and let the engine's own default apply instead. See Hegel::Settings for the keyword-to-setter mapping, the verbosity Symbols it accepts, and the +phases+/+suppress_health_check+ Symbols each of those two accepts (as an Array; an empty Array raises Hegel::Error rather than silently meaning "none"). verbosity: :quiet also silences the failure report itself, not just libhegel's own progress output.

database and database_key opt a run into libhegel's example database: database_key is the switch, database only means something alongside it. Left at their shared default (nil, nil), a run stores nothing, matching every Hegel.test call before these two keywords existed. Given a String, database_key scopes what a run stores and replays -- make it unique to the property, since two properties sharing a key share one replay scope. database then chooses the directory (libhegel's own default, ./.hegel/examples/ outside CI, if left nil alongside a key). Passing database without database_key raises Hegel::Error. See docs/adr/0009 for the decision and the measurements behind it.

report_multiple_failures defaults to false, not nil, unlike every keyword above: see Hegel::Runner.run's own comment for why departing from libhegel's own default (true) is itself the decision here.

stateful_step_count bounds how many rules a Hegel::Stateful.run call applies per test case; left nil (the default) leaves libhegel's own default of 50 in place. hegel.h documents it as needing to be at least 1; like +tc.target+'s label, that requirement is left to the engine rather than re-checked here.

output (default $stderr) is where a failure report is written; a caller passes its own IO to capture that report instead (tests do).

reproduce_failure, when given, replays the single case that blob (printed at the end of an earlier failure report) encodes, instead of starting a run: test_cases and the other run-shaping keywords above have nothing to bound in that case. See Hegel::Runner.reproduce.

impl exists for tests: it lets Hegel::LibHegel::Fake stand in for the real engine. Ordinary callers never pass it. Its default expression (#default_impl) only runs when impl is not given, so a test that does pass one never opens the native library at all.

Parameters:

  • test_cases: (Integer, nil) (defaults to: nil)
  • seed: (Integer, nil) (defaults to: nil)
  • derandomize: (Boolean, nil) (defaults to: nil)
  • verbosity: (Symbol, nil) (defaults to: nil)
  • database: (String, nil) (defaults to: nil)
  • database_key: (String, nil) (defaults to: nil)
  • phases: (Array[Symbol], nil) (defaults to: nil)
  • suppress_health_check: (Array[Symbol], nil) (defaults to: nil)
  • report_multiple_failures: (Boolean) (defaults to: false)
  • stateful_step_count: (Integer, nil) (defaults to: nil)
  • output: (Object) (defaults to: $stderr)
  • reproduce_failure: (String, nil) (defaults to: nil)
  • impl: (Object) (defaults to: default_impl)

Yields:

Yield Parameters:

Yield Returns:

  • (void)

Returns:

  • (nil)


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

def test(test_cases: nil, seed: nil, derandomize: nil, verbosity: nil, database: nil, database_key: nil,
  phases: nil, suppress_health_check: nil, report_multiple_failures: false, stateful_step_count: nil,
  output: $stderr, reproduce_failure: nil, impl: default_impl, &block)
  Runner.run(impl: impl, test_cases: test_cases, seed: seed, derandomize: derandomize, verbosity: verbosity,
    database: database, database_key: database_key, phases: phases, suppress_health_check: suppress_health_check,
    report_multiple_failures: report_multiple_failures, stateful_step_count: stateful_step_count, output: output,
    reproduce_failure: reproduce_failure, &block)
end