Class: Hegel::LibHegel::Real

Inherits:
Object
  • Object
show all
Defined in:
lib/hegel/lib_hegel/real.rb,
sig/hegel.rbs

Overview

Drives libhegel's C ABI through the ffi gem. Every other file works against the plain Ruby values and method calls this class exposes, so a future change to how the native call happens has exactly one file to change.

Opens the library and binds every function once, in #initialize, each as its own FFI::Function held in an instance variable (see #initialize's own comment for why this binds one callable per function rather than attach_function's usual class-body DSL). Each call below reuses that already-bound function rather than re-resolving the symbol.

Instance Method Summary collapse

Constructor Details

#initialize(path = Hegel::Locate.resolve, io: $stderr) ⇒ Real

Opens path (default: Hegel::Locate.resolve) and binds the functions this boundary calls. Immediately after, opens a context of its own to compare the loaded engine's version against Hegel::LIBHEGEL_VERSION, warning on io (default $stderr) on a mismatch; see LibHegel.warn_on_version_mismatch. io exists so a test can capture the warning instead of writing to the real stderr.

attach_function's usual DSL binds a fixed library, named at class-definition time, to methods it defines on a class or module body. This class instead takes path as a constructor argument, resolved fresh per instance, so attach_function's own per-instance form is an anonymous module built fresh in #initialize. Measured on ffi 1.17.4, arm64-darwin, over a failing property that shrinks: 777 ms for that form against 750 ms for binding each function directly off a resolved symbol, which is what #bind below does: FFI::DynamicLibrary.open gives a handle to resolve symbols against, and each call to #bind wraps one resolved symbol as a callable FFI::Function, stored in its own instance variable and invoked with #call by the method below it.

Parameters:

  • path (String) (defaults to: Hegel::Locate.resolve)
  • io: (Object) (defaults to: $stderr)


73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
# File 'lib/hegel/lib_hegel/real.rb', line 73

def initialize(path = Hegel::Locate.resolve, io: $stderr)
  @handle = FFI::DynamicLibrary.open(path, FFI::DynamicLibrary::RTLD_LAZY | FFI::DynamicLibrary::RTLD_GLOBAL)

  @hegel_context_new_fn = bind("hegel_context_new", [], :pointer)
  @hegel_context_free_fn = bind("hegel_context_free", [:pointer], :int32)
  @hegel_context_last_error_fn = bind("hegel_context_last_error", [:pointer], :pointer)
  @hegel_version_fn = bind("hegel_version", [:pointer, :pointer], :int32)

  @hegel_settings_new_fn = bind("hegel_settings_new", [:pointer, :pointer], :int32)
  @hegel_settings_free_fn = bind("hegel_settings_free", [:pointer, :pointer], :int32)
  @hegel_settings_set_test_cases_fn = bind(
    "hegel_settings_set_test_cases", [:pointer, :pointer, :uint64], :int32
  )
  @hegel_settings_set_verbosity_fn = bind(
    "hegel_settings_set_verbosity", [:pointer, :pointer, :uint32], :int32
  )
  @hegel_settings_set_seed_fn = bind(
    "hegel_settings_set_seed", [:pointer, :pointer, :uint64, :bool], :int32
  )
  @hegel_settings_set_derandomize_fn = bind(
    "hegel_settings_set_derandomize", [:pointer, :pointer, :bool], :int32
  )
  @hegel_settings_set_database_fn = bind(
    "hegel_settings_set_database", [:pointer, :pointer, :string], :int32
  )
  @hegel_settings_set_stateful_step_count_fn = bind(
    "hegel_settings_set_stateful_step_count", [:pointer, :pointer, :int64], :int32
  )
  @hegel_settings_set_report_multiple_failures_fn = bind(
    "hegel_settings_set_report_multiple_failures", [:pointer, :pointer, :bool], :int32
  )
  @hegel_settings_set_database_key_fn = bind(
    "hegel_settings_set_database_key", [:pointer, :pointer, :string], :int32
  )
  @hegel_settings_set_phases_fn = bind(
    "hegel_settings_set_phases", [:pointer, :pointer, :uint32], :int32
  )
  @hegel_settings_set_suppress_health_check_fn = bind(
    "hegel_settings_set_suppress_health_check", [:pointer, :pointer, :uint32], :int32
  )

  @hegel_run_start_fn = bind(
    "hegel_run_start", [:pointer, :pointer, :pointer, :pointer, :pointer], :int32
  )
  @hegel_next_test_case_fn = bind("hegel_next_test_case", [:pointer, :pointer, :pointer], :int32)
  @hegel_run_free_fn = bind("hegel_run_free", [:pointer, :pointer], :int32)
  @hegel_test_case_free_fn = bind("hegel_test_case_free", [:pointer, :pointer], :int32)
  @hegel_mark_complete_fn = bind("hegel_mark_complete", [:pointer, :pointer, :uint32, :string], :int32)
  @hegel_target_fn = bind("hegel_target", [:pointer, :pointer, :double, :string], :int32)

  @hegel_run_result_fn = bind("hegel_run_result", [:pointer, :pointer, :pointer], :int32)
  @hegel_run_result_free_fn = bind("hegel_run_result_free", [:pointer, :pointer], :int32)
  @hegel_run_result_status_fn = bind("hegel_run_result_status", [:pointer, :pointer, :pointer], :int32)
  @hegel_run_result_error_fn = bind("hegel_run_result_error", [:pointer, :pointer, :pointer], :int32)
  @hegel_run_result_failure_count_fn = bind(
    "hegel_run_result_failure_count", [:pointer, :pointer, :pointer], :int32
  )
  @hegel_run_result_failure_fn = bind(
    "hegel_run_result_failure", [:pointer, :pointer, :size_t, :pointer], :int32
  )
  @hegel_failure_free_fn = bind("hegel_failure_free", [:pointer, :pointer], :int32)
  @hegel_failure_origin_fn = bind("hegel_failure_origin", [:pointer, :pointer, :pointer], :int32)
  @hegel_failure_reproduction_blob_fn = bind(
    "hegel_failure_reproduction_blob", [:pointer, :pointer, :pointer], :int32
  )
  @hegel_test_case_from_blob_fn = bind(
    "hegel_test_case_from_blob", [:pointer, :pointer, :string, :pointer, :pointer, :pointer], :int32
  )

  @hegel_generate_boolean_fn = bind(
    "hegel_generate_boolean", [:pointer, :pointer, :double, :bool, :bool, :pointer], :int32
  )
  @hegel_generate_integer_fn = bind(
    "hegel_generate_integer", [:pointer, :pointer, :int64, :int64, :pointer], :int32
  )
  @hegel_generate_integer_big_fn = bind(
    "hegel_generate_integer_big",
    [:pointer, :pointer, :pointer, :size_t, :pointer, :size_t, :pointer, :size_t, :pointer], :int32
  )

  @hegel_start_span_fn = bind("hegel_start_span", [:pointer, :pointer, :uint64], :int32)
  @hegel_stop_span_fn = bind("hegel_stop_span", [:pointer, :pointer, :bool], :int32)

  @hegel_new_collection_fn = bind(
    "hegel_new_collection", [:pointer, :pointer, :uint64, :uint64, :pointer], :int32
  )
  @hegel_collection_more_fn = bind("hegel_collection_more", [:pointer, :pointer, :pointer, :pointer], :int32)
  @hegel_collection_reject_fn = bind(
    "hegel_collection_reject", [:pointer, :pointer, :pointer, :string], :int32
  )
  @hegel_collection_free_fn = bind("hegel_collection_free", [:pointer, :pointer], :int32)

  @hegel_new_pool_fn = bind("hegel_new_pool", [:pointer, :pointer, :pointer], :int32)
  @hegel_pool_add_fn = bind("hegel_pool_add", [:pointer, :pointer, :pointer, :pointer], :int32)
  @hegel_pool_generate_fn = bind(
    "hegel_pool_generate", [:pointer, :pointer, :pointer, :bool, :pointer], :int32
  )
  @hegel_pool_free_fn = bind("hegel_pool_free", [:pointer, :pointer], :int32)

  @hegel_new_state_machine_fn = bind(
    "hegel_new_state_machine",
    [:pointer, :pointer, :pointer, :size_t, :pointer, :size_t, :pointer], :int32
  )
  @hegel_state_machine_next_rule_fn = bind(
    "hegel_state_machine_next_rule", [:pointer, :pointer, :pointer, :pointer], :int32
  )
  @hegel_state_machine_rule_rejected_fn = bind(
    "hegel_state_machine_rule_rejected", [:pointer, :pointer, :pointer], :int32
  )
  @hegel_state_machine_free_fn = bind("hegel_state_machine_free", [:pointer, :pointer], :int32)

  @hegel_generate_float_fn = bind(
    "hegel_generate_float",
    [:pointer, :pointer, :uint32, :double, :double, :bool, :bool, :bool, :bool, :double, :pointer], :int32
  )

  # hegel_string_generator_text takes 14 arguments past ctx; the
  # last 8 (categories_len through exclude_characters_len) are the
  # category and explicit-character filter parameters this task
  # does not wire up (see #string_generator_text).
  @hegel_string_generator_text_fn = bind(
    "hegel_string_generator_text",
    [:pointer, :uint64, :uint64, :string, :uint32, :uint32,
      :pointer, :size_t, :pointer, :size_t, :pointer, :size_t, :pointer, :size_t,
      :pointer],
    :int32
  )
  @hegel_string_generator_free_fn = bind("hegel_string_generator_free", [:pointer, :pointer], :int32)
  @hegel_generate_string_fn = bind(
    "hegel_generate_string", [:pointer, :pointer, :pointer, :pointer], :int32
  )
  @hegel_generate_string_result_free_fn = bind(
    "hegel_generate_string_result_free", [:pointer, :pointer], :int32
  )

  @hegel_generate_bytes_fn = bind(
    "hegel_generate_bytes", [:pointer, :pointer, :uint64, :uint64, :pointer], :int32
  )
  @hegel_generate_bytes_result_free_fn = bind(
    "hegel_generate_bytes_result_free", [:pointer, :pointer], :int32
  )

  @hegel_string_generator_regex_fn = bind(
    "hegel_string_generator_regex", [:pointer, :string, :bool, :pointer, :pointer], :int32
  )
  @hegel_string_generator_email_fn = bind("hegel_string_generator_email", [:pointer, :pointer], :int32)
  @hegel_string_generator_url_fn = bind("hegel_string_generator_url", [:pointer, :pointer], :int32)
  @hegel_string_generator_domain_fn = bind(
    "hegel_string_generator_domain", [:pointer, :uint64, :pointer], :int32
  )

  @hegel_generate_ipv4_fn = bind("hegel_generate_ipv4", [:pointer, :pointer, :pointer], :int32)
  @hegel_generate_ipv6_fn = bind("hegel_generate_ipv6", [:pointer, :pointer, :pointer], :int32)
  @hegel_generate_uuid_fn = bind(
    "hegel_generate_uuid", [:pointer, :pointer, :uint8, :bool, :pointer], :int32
  )

  @hegel_generate_date_fn = bind(
    "hegel_generate_date", [:pointer, :pointer, DateStruct.by_value, DateStruct.by_value, :pointer], :int32
  )
  @hegel_generate_time_fn = bind(
    "hegel_generate_time", [:pointer, :pointer, TimeStruct.by_value, TimeStruct.by_value, :pointer], :int32
  )
  @hegel_generate_datetime_fn = bind(
    "hegel_generate_datetime",
    [:pointer, :pointer, DatetimeStruct.by_value, DatetimeStruct.by_value, :pointer], :int32
  )

  LibHegel.with_context(self) { |ctx| LibHegel.warn_on_version_mismatch(self, ctx, io: io) }
end

Instance Method Details

#collection_free(ctx, collection) ⇒ nil

No-op when collection is nil, matching hegel_collection_free's documented no-op-on-NULL contract; not translated, for the same reason as #context_free. Unlike every other *_free above, this call takes no test-case handle: the header documents a collection as independent of the test case and run it was created under.

Parameters:

  • ctx (Object)
  • collection (Object)

Returns:

  • (nil)


645
646
647
648
# File 'lib/hegel/lib_hegel/real.rb', line 645

def collection_free(ctx, collection)
  @hegel_collection_free_fn.call(ctx, collection)
  nil
end

#collection_more(ctx, tc, collection) ⇒ Boolean

Returns whether libhegel wants another element; call in a loop, drawing the next element each time this is true, until it is false.

Parameters:

  • ctx (Object)
  • tc (Object)
  • collection (Object)

Returns:

  • (Boolean)


622
623
624
625
626
627
# File 'lib/hegel/lib_hegel/real.rb', line 622

def collection_more(ctx, tc, collection)
  out = FFI::MemoryPointer.new(:bool)
  code = @hegel_collection_more_fn.call(ctx, tc, collection, out)
  LibHegel.check!(self, ctx, code)
  out.read_uint8 != 0
end

#collection_reject(ctx, tc, collection, why = nil) ⇒ nil

Tells libhegel the last element collection produced is invalid. why is an optional human-readable reason (nil marshals to NULL for this :string argument, which the header allows); the header documents it as validated but reserved for future rejection diagnostics, unused today.

Parameters:

  • ctx (Object)
  • tc (Object)
  • collection (Object)
  • why (String, nil) (defaults to: nil)

Returns:

  • (nil)


634
635
636
637
638
# File 'lib/hegel/lib_hegel/real.rb', line 634

def collection_reject(ctx, tc, collection, why = nil)
  code = @hegel_collection_reject_fn.call(ctx, tc, collection, why)
  LibHegel.check!(self, ctx, code)
  nil
end

#context_free(ctx) ⇒ nil

No-op when ctx is nil: libhegel documents hegel_context_free as a no-op on NULL, and a Ruby nil marshals to a NULL pointer for a :pointer argument here, so no separate nil check is needed on this side. The result code is not translated: the header documents this call as always returning HEGEL_OK, so there is nothing to raise.

Parameters:

  • ctx (Object)

Returns:

  • (nil)


255
256
257
258
# File 'lib/hegel/lib_hegel/real.rb', line 255

def context_free(ctx)
  @hegel_context_free_fn.call(ctx)
  nil
end

#context_last_error(ctx) ⇒ String

Copies the message out of libhegel's own buffer into a Ruby String before returning, since the header documents that buffer as borrowed and invalidated by the next call taking the same context.

Parameters:

  • ctx (Object)

Returns:

  • (String)


263
264
265
# File 'lib/hegel/lib_hegel/real.rb', line 263

def context_last_error(ctx)
  utf8(@hegel_context_last_error_fn.call(ctx))
end

#context_newObject

hegel_context_new never returns NULL (guaranteed by the header), so the handle returned here is always live.

Returns:

  • (Object)


246
247
248
# File 'lib/hegel/lib_hegel/real.rb', line 246

def context_new
  @hegel_context_new_fn.call
end

#failure_free(ctx, f) ⇒ nil

No-op when f is nil, matching hegel_failure_free's documented no-op-on-NULL contract; not translated, for the same reason as #context_free.

Parameters:

  • ctx (Object)
  • f (Object)

Returns:

  • (nil)


490
491
492
493
# File 'lib/hegel/lib_hegel/real.rb', line 490

def failure_free(ctx, f)
  @hegel_failure_free_fn.call(ctx, f)
  nil
end

#failure_origin(ctx, f) ⇒ String

Copies the origin string out of libhegel's own buffer before returning, since it is owned by the failure and only valid until #failure_free.

Parameters:

  • ctx (Object)
  • f (Object)

Returns:

  • (String)


498
499
500
501
502
503
# File 'lib/hegel/lib_hegel/real.rb', line 498

def failure_origin(ctx, f)
  out = FFI::MemoryPointer.new(:pointer)
  code = @hegel_failure_origin_fn.call(ctx, f, out)
  LibHegel.check!(self, ctx, code)
  utf8(out.read_pointer)
end

#failure_reproduction_blob(ctx, f) ⇒ String?

Returns nil when libhegel produced no reproduction blob for this failure, matching the header's documented NULL-on-that-case contract. See #nullable_out_string for the shared ownership note.

Parameters:

  • ctx (Object)
  • f (Object)

Returns:

  • (String, nil)


508
509
510
511
512
513
# File 'lib/hegel/lib_hegel/real.rb', line 508

def failure_reproduction_blob(ctx, f)
  out = FFI::MemoryPointer.new(:pointer)
  code = @hegel_failure_reproduction_blob_fn.call(ctx, f, out)
  LibHegel.check!(self, ctx, code)
  nullable_out_string(out)
end

#generate_boolean(ctx, tc, p, forced, has_forced) ⇒ Boolean

Forcing has to agree with p. Measured against libhegel 0.32.5: forcing true at p = 0.0 and forcing false at p = 1.0 both come back HEGEL_E_INVALID_ARG ("generate_boolean: cannot force ..."), while forcing either way succeeds at any p between them. The header describes the two ends as yielding false and true without consuming entropy, and says nothing about what forcing does against them, so this is written down where a caller constructing a forced draw will look for it.

Parameters:

  • ctx (Object)
  • tc (Object)
  • p (Float)
  • forced (Boolean)
  • has_forced (Boolean)

Returns:

  • (Boolean)


544
545
546
547
548
549
# File 'lib/hegel/lib_hegel/real.rb', line 544

def generate_boolean(ctx, tc, p, forced, has_forced)
  out = FFI::MemoryPointer.new(:bool)
  code = @hegel_generate_boolean_fn.call(ctx, tc, p, forced, has_forced, out)
  LibHegel.check!(self, ctx, code)
  out.read_uint8 != 0
end

#generate_bytes(ctx, tc, min_size, max_size) ⇒ String

Returns drawn bytes as a String, read via len the same way #generate_string reads its own out-parameter: the header gives no NUL-termination guarantee for this buffer either, so the length is what makes the copy exact.

Unlike #generate_string, the result is never force-encoded. hegel_generate_bytes_result_t is documented as a byte buffer, not text, and FFI::Pointer#read_bytes already returns ASCII-8BIT (Encoding::BINARY), which is the encoding a byte string belongs in.

The native buffer is released in ensure via #generate_bytes_result_free, for the same reason #generate_string frees its own result from inside this file: ffi is confined to this file, so the freeable handle never leaves this method. Freeing is safe even when the draw above raised, for the same zero-filled-allocation reason documented on #generate_string: the header documents hegel_generate_bytes_result_free as safe on an already-freed (zeroed) struct too.

Parameters:

  • ctx (Object)
  • tc (Object)
  • min_size (Integer)
  • max_size (Integer)

Returns:

  • (String)


853
854
855
856
857
858
859
860
# File 'lib/hegel/lib_hegel/real.rb', line 853

def generate_bytes(ctx, tc, min_size, max_size)
  out = RawResultStruct.new
  code = @hegel_generate_bytes_fn.call(ctx, tc, min_size, max_size, out)
  LibHegel.check!(self, ctx, code)
  out[:data].read_bytes(out[:len])
ensure
  generate_bytes_result_free(ctx, out)
end

#generate_bytes_result_free(ctx, result) ⇒ nil

No-op when result is nil, matching hegel_generate_bytes_result_free's documented no-op-on-NULL contract (also safe on an already-freed, zeroed struct); not translated, for the same reason as #context_free.

Parameters:

  • ctx (Object)
  • result (Object)

Returns:

  • (nil)


866
867
868
869
# File 'lib/hegel/lib_hegel/real.rb', line 866

def generate_bytes_result_free(ctx, result)
  @hegel_generate_bytes_result_free_fn.call(ctx, result)
  nil
end

#generate_date(ctx, tc, min_value, max_value) ⇒ Array[Integer]

hegel_generate_date. +min_value+/+max_value+ are each a [year, month, day] Array, written by #date_struct into a DateStruct passed .by_value (see the comment on that class, above #initialize). Returns a [year, month, day] Array, the field-level shape Hegel::Generators::DatesGenerator builds its own Date from -- the same division of labor #generate_ipv4/#generate_uuid already follow, returning raw values for a generator one layer up to turn into the caller-facing type. This layer does not validate year/month/day itself: measured against libhegel 0.32.5, an invalid date (month 13, say) already comes back HEGEL_E_INVALID_ARG, translated by LibHegel.check! below, even though the header's Returns line for this call names only HEGEL_OK/HEGEL_E_STOP_TEST. #run_result_failure's own comment records the same measured behaviour for a different call.

Parameters:

  • ctx (Object)
  • tc (Object)
  • min_value (Array[Integer])
  • max_value (Array[Integer])

Returns:

  • (Array[Integer])


974
975
976
977
978
979
# File 'lib/hegel/lib_hegel/real.rb', line 974

def generate_date(ctx, tc, min_value, max_value)
  out = DateStruct.new
  code = @hegel_generate_date_fn.call(ctx, tc, date_struct(min_value), date_struct(max_value), out)
  LibHegel.check!(self, ctx, code)
  read_date(out)
end

#generate_datetime(ctx, tc, min_date, min_time, max_date, max_time) ⇒ [Array[Integer], Array[Integer]]

hegel_generate_datetime. +min_date+/+max_date+ are each a [year, month, day] Array; +min_time+/+max_time+ are each an [hour, minute, second, microsecond] Array -- hegel_datetime_t is a hegel_date_t followed by a hegel_time_t (see DatetimeStruct's own layout, above #initialize). Returns a [[year, month, day], [hour, minute, second, microsecond]] pair, for Hegel::Generators::DatetimesGenerator to build its own Time from.

Parameters:

  • ctx (Object)
  • tc (Object)
  • min_date (Array[Integer])
  • min_time (Array[Integer])
  • max_date (Array[Integer])
  • max_time (Array[Integer])

Returns:

  • ([Array[Integer], Array[Integer]])


999
1000
1001
1002
1003
1004
1005
1006
# File 'lib/hegel/lib_hegel/real.rb', line 999

def generate_datetime(ctx, tc, min_date, min_time, max_date, max_time)
  out = DatetimeStruct.new
  code = @hegel_generate_datetime_fn.call(
    ctx, tc, datetime_struct(min_date, min_time), datetime_struct(max_date, max_time), out
  )
  LibHegel.check!(self, ctx, code)
  [read_date(out[:date]), read_time(out[:time])]
end

#generate_float(ctx, tc, width, min_value, max_value, allow_nan, allow_infinity, exclude_min, exclude_max, smallest_nonzero_magnitude) ⇒ Float

Returns a drawn double. smallest_nonzero_magnitude must be positive and finite; pass HEGEL_FLOAT64_SMALLEST_NONZERO_MAGNITUDE_UNRESTRICTED for width 64 with no restriction, per the header.

Parameters:

  • ctx (Object)
  • tc (Object)
  • width (Integer)
  • min_value (Float)
  • max_value (Float)
  • allow_nan (Boolean)
  • allow_infinity (Boolean)
  • exclude_min (Boolean)
  • exclude_max (Boolean)
  • smallest_nonzero_magnitude (Float)

Returns:

  • (Float)


764
765
766
767
768
769
770
771
# File 'lib/hegel/lib_hegel/real.rb', line 764

def generate_float(ctx, tc, width, min_value, max_value, allow_nan, allow_infinity, exclude_min, exclude_max,
  smallest_nonzero_magnitude)
  out = FFI::MemoryPointer.new(:double)
  code = @hegel_generate_float_fn.call(ctx, tc, width, min_value, max_value, allow_nan, allow_infinity,
    exclude_min, exclude_max, smallest_nonzero_magnitude, out)
  LibHegel.check!(self, ctx, code)
  out.read_double
end

#generate_integer(ctx, tc, min_value, max_value) ⇒ Integer

Parameters:

  • ctx (Object)
  • tc (Object)
  • min_value (Integer)
  • max_value (Integer)

Returns:

  • (Integer)


551
552
553
554
555
556
# File 'lib/hegel/lib_hegel/real.rb', line 551

def generate_integer(ctx, tc, min_value, max_value)
  out = FFI::MemoryPointer.new(:int64)
  code = @hegel_generate_integer_fn.call(ctx, tc, min_value, max_value, out)
  LibHegel.check!(self, ctx, code)
  out.read_int64
end

#generate_integer_big(ctx, tc, min_value, max_value) ⇒ Integer

hegel_generate_integer_big, for bounds that do not fit int64_t (see Hegel::Generators::IntegerGenerator#do_draw, which dispatches here instead of #generate_integer only when a bound is outside that range). +min_value+/+max_value+ are encoded and the result decoded via LibHegel.encode_integer_le/.decode_integer_le, which own the two's-complement little-endian convention itself; this method only owns the buffer marshalling around it. min_value_ptr/max_value_ptr are declared :pointer, not :string: the encoded bytes routinely contain interior zero bytes (256 encodes as "\x00\x01"), which a NUL-terminated const char* argument would truncate. out_value's capacity is the larger of the two encoded bounds, per the header's "out_value_cap >= max(min_value_len, max_value_len) always succeeds"; the result is read back at its own reported out_value_len, not the buffer's full capacity, since decode_integer_le needs only that many bytes.

Parameters:

  • ctx (Object)
  • tc (Object)
  • min_value (Integer)
  • max_value (Integer)

Returns:

  • (Integer)


573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
# File 'lib/hegel/lib_hegel/real.rb', line 573

def generate_integer_big(ctx, tc, min_value, max_value)
  min_bytes = LibHegel.encode_integer_le(min_value)
  max_bytes = LibHegel.encode_integer_le(max_value)
  min_value_ptr = bytes_to_pointer(min_bytes)
  max_value_ptr = bytes_to_pointer(max_bytes)

  cap = [min_bytes.bytesize, max_bytes.bytesize].max
  out_value = FFI::MemoryPointer.new(cap)
  out_value_len = FFI::MemoryPointer.new(:size_t)

  code = @hegel_generate_integer_big_fn.call(
    ctx, tc, min_value_ptr, min_bytes.bytesize, max_value_ptr, max_bytes.bytesize, out_value, cap,
    out_value_len
  )
  LibHegel.check!(self, ctx, code)
  len = out_value_len.read_uint64
  LibHegel.decode_integer_le(out_value.read_bytes(len))
end

#generate_ipv4(ctx, tc) ⇒ String

hegel_generate_ipv4 writes into a caller-supplied fixed-length buffer instead of handing back a pointer through an out-parameter, unlike every generate_* call above: the header documents out_bytes as the address's 4 network-order bytes with no separate length to read, so the buffer's own size is the contract instead of a trailing len field. IPAddr conversion is left to the generator built on top of this call; this layer returns the raw bytes.

Parameters:

  • ctx (Object)
  • tc (Object)

Returns:

  • (String)


926
927
928
929
930
931
# File 'lib/hegel/lib_hegel/real.rb', line 926

def generate_ipv4(ctx, tc)
  out = FFI::MemoryPointer.new(4)
  code = @hegel_generate_ipv4_fn.call(ctx, tc, out)
  LibHegel.check!(self, ctx, code)
  out.read_bytes(4)
end

#generate_ipv6(ctx, tc) ⇒ String

Same fixed-buffer shape as #generate_ipv4, sized for the header's documented 16 network-order bytes.

Parameters:

  • ctx (Object)
  • tc (Object)

Returns:

  • (String)


935
936
937
938
939
940
# File 'lib/hegel/lib_hegel/real.rb', line 935

def generate_ipv6(ctx, tc)
  out = FFI::MemoryPointer.new(16)
  code = @hegel_generate_ipv6_fn.call(ctx, tc, out)
  LibHegel.check!(self, ctx, code)
  out.read_bytes(16)
end

#generate_string(ctx, tc, generator) ⇒ String

Returns a drawn String, force-encoded as UTF-8 (this codec's alphabet), copying exactly the returned len bytes rather than reading a NUL-terminated buffer: the header documents it as not NUL-terminated and possibly containing interior NUL bytes, since the drawn alphabet can include U+0000.

The native buffer is released in ensure via #generate_string_result_free, called directly from here rather than left to the caller: nothing outside this file may hold or read the raw struct (ffi is confined to this file), so unlike #new_collection or #string_generator_text, the freeable handle here never leaves this method. Freeing is safe even when the draw above raised: out starts zero-filled (FFI::Struct.new allocates cleared memory) and libhegel only writes into it on success, so the struct #generate_string_result_free sees here is always either a completed draw or the all-zero state the header documents as already safe to free.

Parameters:

  • ctx (Object)
  • tc (Object)
  • generator (Object)

Returns:

  • (String)


817
818
819
820
821
822
823
824
# File 'lib/hegel/lib_hegel/real.rb', line 817

def generate_string(ctx, tc, generator)
  out = RawResultStruct.new
  code = @hegel_generate_string_fn.call(ctx, tc, generator, out)
  LibHegel.check!(self, ctx, code)
  out[:data].read_bytes(out[:len]).force_encoding(Encoding::UTF_8)
ensure
  generate_string_result_free(ctx, out)
end

#generate_string_result_free(ctx, result) ⇒ nil

No-op when result is nil, matching hegel_generate_string_result_free's documented no-op-on-NULL contract (also safe on an already-freed, zeroed struct); not translated, for the same reason as #context_free.

Parameters:

  • ctx (Object)
  • result (Object)

Returns:

  • (nil)


830
831
832
833
# File 'lib/hegel/lib_hegel/real.rb', line 830

def generate_string_result_free(ctx, result)
  @hegel_generate_string_result_free_fn.call(ctx, result)
  nil
end

#generate_time(ctx, tc, min_value, max_value) ⇒ Array[Integer]

hegel_generate_time. +min_value+/+max_value+ are each an [hour, minute, second, microsecond] Array; same struct-passing, return shape, and validation division of labor as #generate_date above, for Hegel::Generators::TimesGenerator.

Parameters:

  • ctx (Object)
  • tc (Object)
  • min_value (Array[Integer])
  • max_value (Array[Integer])

Returns:

  • (Array[Integer])


985
986
987
988
989
990
# File 'lib/hegel/lib_hegel/real.rb', line 985

def generate_time(ctx, tc, min_value, max_value)
  out = TimeStruct.new
  code = @hegel_generate_time_fn.call(ctx, tc, time_struct(min_value), time_struct(max_value), out)
  LibHegel.check!(self, ctx, code)
  read_time(out)
end

#generate_uuid(ctx, tc, version, has_version) ⇒ String

hegel_generate_uuid, returning the drawn UUID's 16 raw bytes. has_version true forces the RFC 4122 version nibble to version (0..15) and the variant nibble to the RFC 4122 variant, per the header; version is ignored (but still marshalled; pass 0) when has_version is false. Converting the raw bytes to the standard 8-4-4-4-12 hex String is left to Hegel::Generators::UuidsGenerator, the same division of labor #generate_ipv4/#generate_ipv6 already follow for their own byte-to-address conversion. An out-of-range version is not checked here: measured against libhegel 0.32.5, the engine itself returns HEGEL_E_INVALID_ARG for one, which LibHegel.check! already translates.

Parameters:

  • ctx (Object)
  • tc (Object)
  • version (Integer)
  • has_version (Boolean)

Returns:

  • (String)


953
954
955
956
957
958
# File 'lib/hegel/lib_hegel/real.rb', line 953

def generate_uuid(ctx, tc, version, has_version)
  out = FFI::MemoryPointer.new(16)
  code = @hegel_generate_uuid_fn.call(ctx, tc, version, has_version, out)
  LibHegel.check!(self, ctx, code)
  out.read_bytes(16)
end

#mark_complete(ctx, tc, status, origin) ⇒ nil

origin must be non-nil only when status is HEGEL_STATUS_INTERESTING, per the header; this layer neither builds nor validates that string, only passes through what the caller supplies. nil marshals to NULL for this :string argument.

Parameters:

  • ctx (Object)
  • tc (Object)
  • status (Integer)
  • origin (String, nil)

Returns:

  • (nil)


408
409
410
411
412
# File 'lib/hegel/lib_hegel/real.rb', line 408

def mark_complete(ctx, tc, status, origin)
  code = @hegel_mark_complete_fn.call(ctx, tc, status, origin)
  LibHegel.check!(self, ctx, code)
  nil
end

#new_collection(ctx, tc, min_size, max_size) ⇒ Object

Returns a caller-owned collection handle, released separately with #collection_free. Pass HEGEL_COLLECTION_MAX_SIZE_UNBOUNDED as max_size for no upper bound, per the header.

Parameters:

  • ctx (Object)
  • tc (Object)
  • min_size (Integer)
  • max_size (Integer)

Returns:

  • (Object)


612
613
614
615
616
617
# File 'lib/hegel/lib_hegel/real.rb', line 612

def new_collection(ctx, tc, min_size, max_size)
  out = FFI::MemoryPointer.new(:pointer)
  code = @hegel_new_collection_fn.call(ctx, tc, min_size, max_size, out)
  LibHegel.check!(self, ctx, code)
  out.read_pointer
end

#new_pool(ctx, tc) ⇒ Object

Returns a caller-owned pool handle, released separately with #pool_free. A pool tracks a set of variable ids libhegel can draw from and shrink over -- mostly used for stateful testing, where a rule acts on a value a previous rule generated; the caller keeps its own mapping from variable id to that value, per the header.

Parameters:

  • ctx (Object)
  • tc (Object)

Returns:

  • (Object)


655
656
657
658
659
660
# File 'lib/hegel/lib_hegel/real.rb', line 655

def new_pool(ctx, tc)
  out = FFI::MemoryPointer.new(:pointer)
  code = @hegel_new_pool_fn.call(ctx, tc, out)
  LibHegel.check!(self, ctx, code)
  out.read_pointer
end

#new_state_machine(ctx, tc, rule_names, invariant_names) ⇒ Object

Returns a caller-owned state-machine handle, released separately with #state_machine_free. rule_names and invariant_names are each an Array of Ruby Strings, packed into the const char *const * arguments hegel_new_state_machine expects by #pack_name_array; see that method's own comment for how and why. Validating rule_names as non-empty (the header's own requirement) is left to the caller, the same division of labor #new_collection leaves to the caller for its own min_size/max_size ordering.

Parameters:

  • ctx (Object)
  • tc (Object)
  • rule_names (Array[String])
  • invariant_names (Array[String])

Returns:

  • (Object)


706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
# File 'lib/hegel/lib_hegel/real.rb', line 706

def new_state_machine(ctx, tc, rule_names, invariant_names)
  out = FFI::MemoryPointer.new(:pointer)
  # _rule_pointers / _invariant_pointers are unread past the call
  # below, the same shape #generate_integer_big's own
  # min_value_ptr/max_value_ptr already have; keeping them as local
  # variables here, not discarded inside #pack_name_array, is what
  # keeps the native buffers each one owns live through the call.
  # The leading underscore tells the linter that on purpose, the
  # same way it would for a block argument the block never reads.
  rule_names_ptr, _rule_pointers = pack_name_array(rule_names)
  invariant_names_ptr, _invariant_pointers = pack_name_array(invariant_names)

  code = @hegel_new_state_machine_fn.call(
    ctx, tc, rule_names_ptr, rule_names.size, invariant_names_ptr, invariant_names.size, out
  )
  LibHegel.check!(self, ctx, code)
  out.read_pointer
end

#next_test_case(ctx, run) ⇒ Object?

Returns the next test case, or nil once the run has finished (the header documents *out_test_case as NULL at that point, with a HEGEL_OK result rather than an error).

Parameters:

  • ctx (Object)
  • run (Object)

Returns:

  • (Object, nil)


380
381
382
383
384
385
386
# File 'lib/hegel/lib_hegel/real.rb', line 380

def next_test_case(ctx, run)
  out = FFI::MemoryPointer.new(:pointer)
  code = @hegel_next_test_case_fn.call(ctx, run, out)
  LibHegel.check!(self, ctx, code)
  ptr = out.read_pointer
  ptr.null? ? nil : ptr
end

#pool_add(ctx, tc, pool) ⇒ Integer

Returns a fresh variable id for the caller to associate with the value it just generated. The header documents the id as drawn from +tc+'s stream and recorded by value, not by pool position, so it stays stable across shrinking: deleting an earlier addition never renumbers the survivors.

Parameters:

  • ctx (Object)
  • tc (Object)
  • pool (Object)

Returns:

  • (Integer)


667
668
669
670
671
672
# File 'lib/hegel/lib_hegel/real.rb', line 667

def pool_add(ctx, tc, pool)
  out = FFI::MemoryPointer.new(:int64)
  code = @hegel_pool_add_fn.call(ctx, tc, pool, out)
  LibHegel.check!(self, ctx, code)
  out.read_int64
end

#pool_free(ctx, pool) ⇒ nil

No-op when pool is nil, matching hegel_pool_free's documented no-op-on-NULL contract; not translated, for the same reason as #context_free.

Parameters:

  • ctx (Object)
  • pool (Object)

Returns:

  • (nil)


693
694
695
696
# File 'lib/hegel/lib_hegel/real.rb', line 693

def pool_free(ctx, pool)
  @hegel_pool_free_fn.call(ctx, pool)
  nil
end

#pool_generate(ctx, tc, pool, consume) ⇒ Integer

Returns a variable id libhegel chose from pool (and can shrink which one it chose). consume true removes the drawn variable from the pool; false leaves it. LibHegel.check! already translates HEGEL_E_ASSUME -- what the header documents this call returning when pool holds no variables -- to Hegel::AssumeFailed, the same translation every other assumption failure gets, so no extra code is needed here for that case; pinned by test_real_pool_generate_on_an_empty_pool_raises_assume_failed in test/hegel/test_lib_hegel.rb.

Parameters:

  • ctx (Object)
  • tc (Object)
  • pool (Object)
  • consume (Boolean)

Returns:

  • (Integer)


683
684
685
686
687
688
# File 'lib/hegel/lib_hegel/real.rb', line 683

def pool_generate(ctx, tc, pool, consume)
  out = FFI::MemoryPointer.new(:int64)
  code = @hegel_pool_generate_fn.call(ctx, tc, pool, consume, out)
  LibHegel.check!(self, ctx, code)
  out.read_int64
end

#run_free(ctx, run) ⇒ nil

No-op when run is nil, matching hegel_run_free's documented no-op-on-NULL contract; not translated, for the same reason as #context_free.

Parameters:

  • ctx (Object)
  • run (Object)

Returns:

  • (nil)


391
392
393
394
# File 'lib/hegel/lib_hegel/real.rb', line 391

def run_free(ctx, run)
  @hegel_run_free_fn.call(ctx, run)
  nil
end

#run_result(ctx, run) ⇒ Object

Returns a caller-owned copy of the finished run's result, or raises HEGEL_E_NOT_COMPLETE (via LibHegel.check!) if the run has not finished. The header documents this copy as staying valid after #run_free, so run can be freed as soon as this call returns; it must be released separately, exactly once, with #run_result_free.

Parameters:

  • ctx (Object)
  • run (Object)

Returns:

  • (Object)


431
432
433
434
435
436
# File 'lib/hegel/lib_hegel/real.rb', line 431

def run_result(ctx, run)
  out = FFI::MemoryPointer.new(:pointer)
  code = @hegel_run_result_fn.call(ctx, run, out)
  LibHegel.check!(self, ctx, code)
  out.read_pointer
end

#run_result_error(ctx, r) ⇒ String?

Returns nil when the run completed normally (PASSED or FAILED), matching the header's documented NULL-on-success contract for this out-parameter, distinct from an empty-string message. See #nullable_out_string for the ownership note shared with #failure_reproduction_blob.

Parameters:

  • ctx (Object)
  • r (Object)

Returns:

  • (String, nil)


461
462
463
464
465
466
# File 'lib/hegel/lib_hegel/real.rb', line 461

def run_result_error(ctx, r)
  out = FFI::MemoryPointer.new(:pointer)
  code = @hegel_run_result_error_fn.call(ctx, r, out)
  LibHegel.check!(self, ctx, code)
  nullable_out_string(out)
end

#run_result_failure(ctx, r, index) ⇒ Object

index must be less than #run_result_failure_count's value, per the header. Returns a caller-owned failure handle, released separately with #failure_free. Measured against libhegel 0.32.5: an out-of-range index comes back HEGEL_E_INVALID_ARG, even though the header's Returns line for this call names only HEGEL_OK.

Parameters:

  • ctx (Object)
  • r (Object)
  • index (Integer)

Returns:

  • (Object)


480
481
482
483
484
485
# File 'lib/hegel/lib_hegel/real.rb', line 480

def run_result_failure(ctx, r, index)
  out = FFI::MemoryPointer.new(:pointer)
  code = @hegel_run_result_failure_fn.call(ctx, r, index, out)
  LibHegel.check!(self, ctx, code)
  out.read_pointer
end

#run_result_failure_count(ctx, r) ⇒ Integer

Parameters:

  • ctx (Object)
  • r (Object)

Returns:

  • (Integer)


468
469
470
471
472
473
# File 'lib/hegel/lib_hegel/real.rb', line 468

def run_result_failure_count(ctx, r)
  out = FFI::MemoryPointer.new(:size_t)
  code = @hegel_run_result_failure_count_fn.call(ctx, r, out)
  LibHegel.check!(self, ctx, code)
  out.read_uint64
end

#run_result_free(ctx, r) ⇒ nil

No-op when r is nil, matching hegel_run_result_free's documented no-op-on-NULL contract; not translated, for the same reason as #context_free.

Parameters:

  • ctx (Object)
  • r (Object)

Returns:

  • (nil)


441
442
443
444
# File 'lib/hegel/lib_hegel/real.rb', line 441

def run_result_free(ctx, r)
  @hegel_run_result_free_fn.call(ctx, r)
  nil
end

#run_result_status(ctx, r) ⇒ Integer

Returns the raw hegel_run_status_t value (HEGEL_RUN_STATUS_PASSED / _FAILED / _ERROR); this layer does not interpret it, matching how #mark_complete passes hegel_status_t values through unexamined.

Parameters:

  • ctx (Object)
  • r (Object)

Returns:

  • (Integer)


449
450
451
452
453
454
# File 'lib/hegel/lib_hegel/real.rb', line 449

def run_result_status(ctx, r)
  out = FFI::MemoryPointer.new(:int32)
  code = @hegel_run_result_status_fn.call(ctx, r, out)
  LibHegel.check!(self, ctx, code)
  out.read_int32
end

#run_start(ctx, settings) ⇒ Object

settings can be freed by the caller as soon as this call returns: the header documents that hegel_run_start copies the settings it is given rather than borrowing them. callback and user_data are always NULL here, which the header documents as leaving libhegel's output on stderr; wiring a Ruby-backed callback is left to a later task.

Parameters:

  • ctx (Object)
  • settings (Object)

Returns:

  • (Object)


370
371
372
373
374
375
# File 'lib/hegel/lib_hegel/real.rb', line 370

def run_start(ctx, settings)
  out = FFI::MemoryPointer.new(:pointer)
  code = @hegel_run_start_fn.call(ctx, settings, nil, nil, out)
  LibHegel.check!(self, ctx, code)
  out.read_pointer
end

#settings_free(ctx, s) ⇒ nil

No-op when s is nil, matching hegel_settings_free's documented no-op-on-NULL contract; not translated, for the same reason as #context_free: the header documents this call as always returning HEGEL_OK.

Parameters:

  • ctx (Object)
  • s (Object)

Returns:

  • (nil)


290
291
292
293
# File 'lib/hegel/lib_hegel/real.rb', line 290

def settings_free(ctx, s)
  @hegel_settings_free_fn.call(ctx, s)
  nil
end

#settings_new(ctx) ⇒ Object

Returns a settings handle initialized with libhegel's defaults, or raises the exception LibHegel.check! translates this call's result code to.

Parameters:

  • ctx (Object)

Returns:

  • (Object)


279
280
281
282
283
284
# File 'lib/hegel/lib_hegel/real.rb', line 279

def settings_new(ctx)
  out = FFI::MemoryPointer.new(:pointer)
  code = @hegel_settings_new_fn.call(ctx, out)
  LibHegel.check!(self, ctx, code)
  out.read_pointer
end

#settings_set_database(ctx, s, database) ⇒ nil

database may be nil (libhegel's own default path) or a String, including "" to disable the database. Declared :string below, so a Ruby String marshals as a const char* to its bytes and nil marshals to NULL, both directly -- no separate pointer to build here.

Parameters:

  • ctx (Object)
  • s (Object)
  • database (String, nil)

Returns:

  • (nil)


323
324
325
326
327
# File 'lib/hegel/lib_hegel/real.rb', line 323

def settings_set_database(ctx, s, database)
  code = @hegel_settings_set_database_fn.call(ctx, s, database)
  LibHegel.check!(self, ctx, code)
  nil
end

#settings_set_database_key(ctx, s, key) ⇒ nil

key may be nil, which the header documents as clearing the key (the default); nil marshals to NULL for this :string argument, the same as #settings_set_database's own nilable database argument.

Parameters:

  • ctx (Object)
  • s (Object)
  • key (String, nil)

Returns:

  • (nil)


344
345
346
347
348
# File 'lib/hegel/lib_hegel/real.rb', line 344

def settings_set_database_key(ctx, s, key)
  code = @hegel_settings_set_database_key_fn.call(ctx, s, key)
  LibHegel.check!(self, ctx, code)
  nil
end

#settings_set_derandomize(ctx, s, derandomize) ⇒ nil

Parameters:

  • ctx (Object)
  • s (Object)
  • derandomize (Boolean)

Returns:

  • (nil)


313
314
315
316
317
# File 'lib/hegel/lib_hegel/real.rb', line 313

def settings_set_derandomize(ctx, s, derandomize)
  code = @hegel_settings_set_derandomize_fn.call(ctx, s, derandomize)
  LibHegel.check!(self, ctx, code)
  nil
end

#settings_set_phases(ctx, s, phases) ⇒ nil

phases is a bitwise OR of the HEGEL_PHASE_* constants.

Parameters:

  • ctx (Object)
  • s (Object)
  • phases (Integer)

Returns:

  • (nil)


351
352
353
354
355
# File 'lib/hegel/lib_hegel/real.rb', line 351

def settings_set_phases(ctx, s, phases)
  code = @hegel_settings_set_phases_fn.call(ctx, s, phases)
  LibHegel.check!(self, ctx, code)
  nil
end

#settings_set_report_multiple_failures(ctx, s, yes) ⇒ nil

Parameters:

  • ctx (Object)
  • s (Object)
  • yes (Boolean)

Returns:

  • (nil)


335
336
337
338
339
# File 'lib/hegel/lib_hegel/real.rb', line 335

def settings_set_report_multiple_failures(ctx, s, yes)
  code = @hegel_settings_set_report_multiple_failures_fn.call(ctx, s, yes)
  LibHegel.check!(self, ctx, code)
  nil
end

#settings_set_seed(ctx, s, seed, has_seed) ⇒ nil

Parameters:

  • ctx (Object)
  • s (Object)
  • seed (Integer)
  • has_seed (Boolean)

Returns:

  • (nil)


307
308
309
310
311
# File 'lib/hegel/lib_hegel/real.rb', line 307

def settings_set_seed(ctx, s, seed, has_seed)
  code = @hegel_settings_set_seed_fn.call(ctx, s, seed, has_seed)
  LibHegel.check!(self, ctx, code)
  nil
end

#settings_set_stateful_step_count(ctx, s, n) ⇒ nil

Parameters:

  • ctx (Object)
  • s (Object)
  • n (Integer)

Returns:

  • (nil)


329
330
331
332
333
# File 'lib/hegel/lib_hegel/real.rb', line 329

def settings_set_stateful_step_count(ctx, s, n)
  code = @hegel_settings_set_stateful_step_count_fn.call(ctx, s, n)
  LibHegel.check!(self, ctx, code)
  nil
end

#settings_set_suppress_health_check(ctx, s, checks) ⇒ nil

checks is a bitwise OR of the HEGEL_HC_* constants. Each call overwrites the previous suppressions, per the header.

Parameters:

  • ctx (Object)
  • s (Object)
  • checks (Integer)

Returns:

  • (nil)


359
360
361
362
363
# File 'lib/hegel/lib_hegel/real.rb', line 359

def settings_set_suppress_health_check(ctx, s, checks)
  code = @hegel_settings_set_suppress_health_check_fn.call(ctx, s, checks)
  LibHegel.check!(self, ctx, code)
  nil
end

#settings_set_test_cases(ctx, s, n) ⇒ nil

Parameters:

  • ctx (Object)
  • s (Object)
  • n (Integer)

Returns:

  • (nil)


295
296
297
298
299
# File 'lib/hegel/lib_hegel/real.rb', line 295

def settings_set_test_cases(ctx, s, n)
  code = @hegel_settings_set_test_cases_fn.call(ctx, s, n)
  LibHegel.check!(self, ctx, code)
  nil
end

#settings_set_verbosity(ctx, s, v) ⇒ nil

Parameters:

  • ctx (Object)
  • s (Object)
  • v (Integer)

Returns:

  • (nil)


301
302
303
304
305
# File 'lib/hegel/lib_hegel/real.rb', line 301

def settings_set_verbosity(ctx, s, v)
  code = @hegel_settings_set_verbosity_fn.call(ctx, s, v)
  LibHegel.check!(self, ctx, code)
  nil
end

#start_span(ctx, tc, label) ⇒ nil

Opens a span labelled label (one of the HEGEL_LABEL_* constants, or a caller-defined value that avoids them). Must be paired with exactly one #stop_span call, per the header.

Parameters:

  • ctx (Object)
  • tc (Object)
  • label (Integer)

Returns:

  • (nil)


595
596
597
598
599
# File 'lib/hegel/lib_hegel/real.rb', line 595

def start_span(ctx, tc, label)
  code = @hegel_start_span_fn.call(ctx, tc, label)
  LibHegel.check!(self, ctx, code)
  nil
end

#state_machine_free(ctx, state_machine) ⇒ nil

No-op when state_machine is nil, matching hegel_state_machine_free's documented no-op-on-NULL contract; not translated, for the same reason as #context_free.

Parameters:

  • ctx (Object)
  • state_machine (Object)

Returns:

  • (nil)


755
756
757
758
# File 'lib/hegel/lib_hegel/real.rb', line 755

def state_machine_free(ctx, state_machine)
  @hegel_state_machine_free_fn.call(ctx, state_machine)
  nil
end

#state_machine_next_rule(ctx, tc, state_machine) ⇒ Integer

Returns the index (in 0...num_rules) of the next stateful-testing rule to run, or HEGEL_STATE_MACHINE_DONE (-1) once +state_machine+'s step budget is exhausted -- returned as the raw sentinel value, not translated to nil. Unlike #next_test_case's out-parameter, which is NULL (no value) at the equivalent boundary, the header documents this out-parameter as holding a real value, -1, at that point; a caller comparing against HEGEL_STATE_MACHINE_DONE is the layer that should decide what that value means, the same way #run_result_status hands back its raw HEGEL_RUN_STATUS_* value unexamined.

Parameters:

  • ctx (Object)
  • tc (Object)
  • state_machine (Object)

Returns:

  • (Integer)


734
735
736
737
738
739
# File 'lib/hegel/lib_hegel/real.rb', line 734

def state_machine_next_rule(ctx, tc, state_machine)
  out = FFI::MemoryPointer.new(:int64)
  code = @hegel_state_machine_next_rule_fn.call(ctx, tc, state_machine, out)
  LibHegel.check!(self, ctx, code)
  out.read_int64
end

#state_machine_rule_rejected(ctx, tc, state_machine) ⇒ nil

Reports the rule most recently returned by #state_machine_next_rule as rejected (an assumption failed before it completed), so it does not count toward the step budget. Raises HEGEL_E_INVALID_ARG (via LibHegel.check!, translated to Hegel::Error) when no rule is outstanding, per the header.

Parameters:

  • ctx (Object)
  • tc (Object)
  • state_machine (Object)

Returns:

  • (nil)


746
747
748
749
750
# File 'lib/hegel/lib_hegel/real.rb', line 746

def state_machine_rule_rejected(ctx, tc, state_machine)
  code = @hegel_state_machine_rule_rejected_fn.call(ctx, tc, state_machine)
  LibHegel.check!(self, ctx, code)
  nil
end

#stop_span(ctx, tc, discard) ⇒ nil

Closes the most recently opened span. discard true marks it rejected, so libhegel retries from before the span opened.

Parameters:

  • ctx (Object)
  • tc (Object)
  • discard (Boolean)

Returns:

  • (nil)


603
604
605
606
607
# File 'lib/hegel/lib_hegel/real.rb', line 603

def stop_span(ctx, tc, discard)
  code = @hegel_stop_span_fn.call(ctx, tc, discard)
  LibHegel.check!(self, ctx, code)
  nil
end

#string_generator_domain(ctx, max_length) ⇒ Object

Returns a caller-owned string generator handle producing fully-qualified domain names, released the same way as #string_generator_text. max_length is the total FQDN length; the header documents it as valid in 4..=255. This layer does not validate that range itself, only translates the HEGEL_E_INVALID_ARG the engine returns outside it, the same division of labor #settings_set_database and every other setter above already follows.

Parameters:

  • ctx (Object)
  • max_length (Integer)

Returns:

  • (Object)


912
913
914
915
916
917
# File 'lib/hegel/lib_hegel/real.rb', line 912

def string_generator_domain(ctx, max_length)
  out = FFI::MemoryPointer.new(:pointer)
  code = @hegel_string_generator_domain_fn.call(ctx, max_length, out)
  LibHegel.check!(self, ctx, code)
  out.read_pointer
end

#string_generator_email(ctx) ⇒ Object

Returns a caller-owned string generator handle producing RFC 5321/5322 email addresses, released the same way as #string_generator_text.

Parameters:

  • ctx (Object)

Returns:

  • (Object)


888
889
890
891
892
893
# File 'lib/hegel/lib_hegel/real.rb', line 888

def string_generator_email(ctx)
  out = FFI::MemoryPointer.new(:pointer)
  code = @hegel_string_generator_email_fn.call(ctx, out)
  LibHegel.check!(self, ctx, code)
  out.read_pointer
end

#string_generator_free(ctx, generator) ⇒ nil

No-op when generator is nil, matching hegel_string_generator_free's documented no-op-on-NULL contract; not translated, for the same reason as #context_free.

Parameters:

  • ctx (Object)
  • generator (Object)

Returns:

  • (nil)


795
796
797
798
# File 'lib/hegel/lib_hegel/real.rb', line 795

def string_generator_free(ctx, generator)
  @hegel_string_generator_free_fn.call(ctx, generator)
  nil
end

#string_generator_regex(ctx, pattern, fullmatch, alphabet = nil) ⇒ Object

Returns a caller-owned string generator handle matching pattern (Python re syntax), released the same way as #string_generator_text: with #string_generator_free. alphabet is an optional string generator handle (built via #string_generator_text, scoped with Hegel::TestCase#with_text_generator) whose character set constrains the padding and wildcard characters; nil (the default) marshals to NULL, the header's documented "no particular alphabet" case.

Parameters:

  • ctx (Object)
  • pattern (String)
  • fullmatch (Boolean)
  • alphabet (Object) (defaults to: nil)

Returns:

  • (Object)


878
879
880
881
882
883
# File 'lib/hegel/lib_hegel/real.rb', line 878

def string_generator_regex(ctx, pattern, fullmatch, alphabet = nil)
  out = FFI::MemoryPointer.new(:pointer)
  code = @hegel_string_generator_regex_fn.call(ctx, pattern, fullmatch, alphabet, out)
  LibHegel.check!(self, ctx, code)
  out.read_pointer
end

#string_generator_text(ctx, min_size:, max_size:, codec: nil, min_codepoint: 0, max_codepoint: 0xFFFFFFFF) ⇒ Object

Returns a caller-owned string generator handle, released separately with #string_generator_free (or scoped with Hegel::TestCase#with_text_generator). categories, exclude_categories, include_characters, and exclude_characters are always passed as NULL/0 below: this call only wires up the codepoint-range constraints of the 14-argument bind; the category and explicit-character filter arguments are a later generator's scope, layered on top of this same bind.

Parameters:

  • ctx (Object)
  • min_size: (Integer)
  • max_size: (Integer)
  • codec: (String, nil) (defaults to: nil)
  • min_codepoint: (Integer) (defaults to: 0)
  • max_codepoint: (Integer) (defaults to: 0xFFFFFFFF)

Returns:

  • (Object)


781
782
783
784
785
786
787
788
789
790
# File 'lib/hegel/lib_hegel/real.rb', line 781

def string_generator_text(ctx, min_size:, max_size:, codec: nil, min_codepoint: 0, max_codepoint: 0xFFFFFFFF)
  out = FFI::MemoryPointer.new(:pointer)
  code = @hegel_string_generator_text_fn.call(
    ctx, min_size, max_size, codec, min_codepoint, max_codepoint,
    nil, 0, nil, 0, nil, 0, nil, 0,
    out
  )
  LibHegel.check!(self, ctx, code)
  out.read_pointer
end

#string_generator_url(ctx) ⇒ Object

Returns a caller-owned string generator handle producing RFC 3986 http/https URLs, released the same way as #string_generator_text.

Parameters:

  • ctx (Object)

Returns:

  • (Object)


897
898
899
900
901
902
# File 'lib/hegel/lib_hegel/real.rb', line 897

def string_generator_url(ctx)
  out = FFI::MemoryPointer.new(:pointer)
  code = @hegel_string_generator_url_fn.call(ctx, out)
  LibHegel.check!(self, ctx, code)
  out.read_pointer
end

#target(ctx, tc, value, label) ⇒ nil

Records a numeric observation under label for libhegel's own hill-climbing between generation rounds. The header documents this as a no-op unless HEGEL_PHASE_TARGET is enabled (the default), and a label as recordable at most once per test case; neither is checked here, matching how this layer leaves every other argument-shape rule to the engine's own HEGEL_E_INVALID_ARG.

Parameters:

  • ctx (Object)
  • tc (Object)
  • value (Float)
  • label (String)

Returns:

  • (nil)


420
421
422
423
424
# File 'lib/hegel/lib_hegel/real.rb', line 420

def target(ctx, tc, value, label)
  code = @hegel_target_fn.call(ctx, tc, value, label)
  LibHegel.check!(self, ctx, code)
  nil
end

#test_case_free(ctx, tc) ⇒ nil

No-op when tc is nil, matching hegel_test_case_free's documented no-op-on-NULL contract; not translated, for the same reason as #context_free.

Parameters:

  • ctx (Object)
  • tc (Object)

Returns:

  • (nil)


399
400
401
402
# File 'lib/hegel/lib_hegel/real.rb', line 399

def test_case_free(ctx, tc)
  @hegel_test_case_free_fn.call(ctx, tc)
  nil
end

#test_case_from_blob(ctx, settings, blob) ⇒ Object

Replays blob (from #failure_reproduction_blob) against settings with no run handle and no run loop involved, per the header. callback and user_data are always NULL here, for the same reason as #run_start. blob is declared :string, the same as #settings_set_database's own const char* argument. Raises HEGEL_E_INVALID_ARG (via LibHegel.check!) for a blob that is corrupt, non-UTF-8, or from an incompatible Hegel version.

A blob whose choices no longer match the caller's generators is a different case, and the header places it elsewhere: it "returns HEGEL_E_STOP_TEST from the draw that overruns", so the replay is built here and fails later, inside the body. Measured against 0.32.5, replaying a two-draw blob against a five-draw body builds fine and overruns at a draw.

Parameters:

  • ctx (Object)
  • settings (Object)
  • blob (String)

Returns:

  • (Object)


529
530
531
532
533
534
# File 'lib/hegel/lib_hegel/real.rb', line 529

def test_case_from_blob(ctx, settings, blob)
  out = FFI::MemoryPointer.new(:pointer)
  code = @hegel_test_case_from_blob_fn.call(ctx, settings, blob, nil, nil, out)
  LibHegel.check!(self, ctx, code)
  out.read_pointer
end

#version(ctx) ⇒ String

Returns the loaded engine's version string, or raises the exception LibHegel.check! translates this call's result code to.

Parameters:

  • ctx (Object)

Returns:

  • (String)


269
270
271
272
273
274
# File 'lib/hegel/lib_hegel/real.rb', line 269

def version(ctx)
  out = FFI::MemoryPointer.new(:pointer)
  code = @hegel_version_fn.call(ctx, out)
  LibHegel.check!(self, ctx, code)
  utf8(out.read_pointer)
end