hegeltest 0.4.0-x86_64-linux → 0.4.1-x86_64-linux

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 341eaae31f3446fb2282f5c0c1beca4d4589694894e3a28f69b4c76c0262f1dc
4
- data.tar.gz: c384924a4c727e53820ba86169130758c06ab53ee6d3efd04b5b882294f13a53
3
+ metadata.gz: 5d19d55e56d1e7b4712b080b854b953b483c953f345c83261a79985d6b19266b
4
+ data.tar.gz: b4f55d6d50b7c99b4183efe89540b679c171f451e60d08a59e9b96c66b5dd22c
5
5
  SHA512:
6
- metadata.gz: b55a89f13cb10ae1804daca37bf9e18e95a887acc609f11882859bd44a72c687d948994c7e79b05601b0594f688a62f41dc6e3e23f3ea6c8a4b9a6662c7eac57
7
- data.tar.gz: d42ef433b5675fc0d21f9ae17f336445e65421352042b9162df886d95aded68aae70304f405e246c4560d8675c60554af5eaa929fabc8aec1c9614123a16137b
6
+ metadata.gz: a4a57120472ea15aacfe7c9bfb91efdb637ac031d17a9e5ce5f08b4d5c3373a31111602ebf99e933769a525e569420535478016b0c75bf89c261555f54ec68a4
7
+ data.tar.gz: 38d6ed2f6f7713918d935bd9fb181d174cf5c827ef0a424a95fe0a332ec2c67ed7bda0ddad92613375f9294c454e809a0d25cd640e6e7574b46a4067a5ff0685
data/CHANGELOG.md CHANGED
@@ -2,6 +2,44 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.4.1] - 2026-10-29
6
+
7
+ ### Engine: libhegel 0.49.0 (was 0.48.0)
8
+
9
+ The [libhegel 0.49.0 release notes](https://github.com/hegeldev/hegel/releases/tag/libhegel/0.49.0)
10
+ describe 0.49.0.
11
+
12
+ Each platform gem carries `libhegel` 0.49.0. The engine adds functions and
13
+ constants to its C interface, and changes no function that these bindings
14
+ call. A caller has nothing to change in code. The engine move changes these
15
+ results for a caller:
16
+
17
+ - The engine reads new environment variables over the settings profile:
18
+ `HEGEL_VERBOSITY`, `HEGEL_BACKEND`, `HEGEL_PHASES`, and
19
+ `HEGEL_SUPPRESS_HEALTH_CHECK`. Each one applies to a run that does not set
20
+ the matching `Hegel.test` keyword. `HEGEL_UNBOUNDED_CHOICES` sets a setting
21
+ that has no `Hegel.test` keyword. A malformed value makes `Hegel.test`
22
+ raise `Hegel::Error` with `HEGEL_E_INVALID_ARG` and a message that names
23
+ the variable. In 0.48.0, the engine ignored these variables. `HEGEL_REPORT_MULTIPLE_FAILURES` has no
24
+ effect, because `Hegel.test` always sets `report_multiple_failures`.
25
+ - The engine reads a `max_size` of `2**64 - 2` as "no maximum given". The gem
26
+ now sends a `max_size` at or above `2**64 - 2` as `2**64 - 1`, no upper
27
+ bound, so `text`, `binary`, `arrays`, `sets`, `hashes`, and the other sized
28
+ generators keep the meaning the caller wrote. A `max_size` above
29
+ `2**64 - 1` also means no upper bound now. Before, it raised `RangeError`.
30
+ See [ADR 0037](docs/adr/0037-send-a-maximum-at-the-engine-default-sentinel-as-no-bound.md).
31
+
32
+ ### Changed
33
+
34
+ - The string generators follow the Unicode version Ruby was built with,
35
+ `RbConfig::CONFIG["UNICODE_VERSION"]`, through libhegel's
36
+ `hegel_set_unicode_version`. A drawn character falls in the General
37
+ Category Ruby gives it, and `from_regex` reads `\w`, `\d`, and `\s` as
38
+ Ruby does. Before, they followed the engine's newest tables, Unicode 17.0.0.
39
+ On Ruby 3.4, which follows Unicode 15.0.0, a character assigned after 15.0.0
40
+ is now drawn as unassigned. See
41
+ [ADR 0036](docs/adr/0036-follow-rubys-unicode-version.md).
42
+
5
43
  ## [0.4.0] - 2026-10-21
6
44
 
7
45
  ### Engine: libhegel 0.48.0 (was 0.47.2)
@@ -454,6 +492,7 @@ The engine is called through the `ffi` gem. Each platform gem carries the
454
492
  matching `libhegel` 0.32.5 build. The platform-independent gem carries none,
455
493
  so a run on it needs `HEGEL_LIBHEGEL_PATH` pointing at a local build.
456
494
 
495
+ [0.4.1]: https://github.com/meganemura/hegel-ruby/releases/tag/v0.4.1
457
496
  [0.4.0]: https://github.com/meganemura/hegel-ruby/releases/tag/v0.4.0
458
497
  [0.3.4]: https://github.com/meganemura/hegel-ruby/releases/tag/v0.3.4
459
498
  [0.3.3]: https://github.com/meganemura/hegel-ruby/releases/tag/v0.3.3
data/docs/README.md CHANGED
@@ -42,6 +42,8 @@ An index of the design records for `hegel-ruby`.
42
42
  - [0033: Cache compiled grammars for the length of a run](adr/0033-cache-compiled-grammars-for-the-length-of-a-run.md)
43
43
  - [0034: Repeat a test body with a Ruby-side indent](adr/0034-repeat-a-test-body-with-a-ruby-side-indent.md)
44
44
  - [0035: Defer concurrent state machines and the engine printer](adr/0035-defer-concurrent-state-machines-and-the-engine-printer.md)
45
+ - [0036: Follow Ruby's Unicode version](adr/0036-follow-rubys-unicode-version.md)
46
+ - [0037: Send a maximum at the engine's default sentinel as no bound](adr/0037-send-a-maximum-at-the-engine-default-sentinel-as-no-bound.md)
45
47
 
46
48
  A new decision gets a new record. A changed decision supersedes the old
47
49
  record instead of editing it, so the history stays readable.
@@ -0,0 +1,50 @@
1
+ # 0036: Follow Ruby's Unicode version
2
+
3
+ ## Status
4
+
5
+ Accepted.
6
+
7
+ ## Context
8
+
9
+ libhegel 0.48.0 adds `hegel_set_unicode_version`. It sets, for the whole
10
+ process, the Unicode version that the text, regex, and grammar generators
11
+ follow: the General Categories an alphabet is built from, and the `\w`,
12
+ `\d`, `\s`, word boundaries, and case-insensitive matching of patterns. The
13
+ engine has tables for Unicode 14.0.0 to 17.0.0. A binding that never calls it
14
+ gets the newest.
15
+
16
+ Ruby classifies characters by the Unicode version it was built with, which
17
+ `RbConfig::CONFIG["UNICODE_VERSION"]` reports: 15.0.0 on Ruby 3.4. With the
18
+ engine's 17.0.0 tables, `text(categories: ["Lu"])` can draw a character Ruby
19
+ calls unassigned, and `from_regex` can draw a string Ruby's own `Regexp` reads
20
+ differently. U+20C1 is one such character: a currency sign in 17.0.0, and
21
+ unassigned in every earlier version.
22
+
23
+ The Python frontend in hegeldev/hegel calls the function at import with
24
+ `unicodedata.unidata_version`, for the same reason. The Rust frontend declares
25
+ it in `frontends/rust/src/ffi/sys/fns.rs`; a search of `frontends/rust/src` at
26
+ the `libhegel/0.48.0` tag finds that declaration as its one occurrence, so this
27
+ gem follows the Python frontend here.
28
+
29
+ ## Decision
30
+
31
+ `LibHegel::Real#initialize` passes `RbConfig::CONFIG["UNICODE_VERSION"]` to
32
+ `hegel_set_unicode_version`, next to the engine version check, so that every
33
+ generator built afterwards follows Ruby's tables. No keyword exposes it. ADR
34
+ 0015 leaves mechanism to each binding, and which host's tables the engine
35
+ follows is mechanism: the interface stays the same, and a drawn value now
36
+ means what Ruby takes it to mean.
37
+
38
+ A version the engine has no tables for raises `Hegel::Error` when the engine
39
+ is loaded. Ruby 3.4, the oldest Ruby this gem supports, reports 15.0.0, and
40
+ the engine answers a version newer than its own with its newest tables, so
41
+ no supported Ruby reaches that error.
42
+
43
+ ## Consequences
44
+
45
+ On Ruby 3.4, characters assigned in Unicode 15.1.0 to 17.0.0 are drawn as
46
+ unassigned (`Cn`) rather than in their new categories, as Ruby classifies
47
+ them.
48
+
49
+ The setting is process-wide, so a test that changes it puts back Ruby's
50
+ version afterwards.
@@ -0,0 +1,48 @@
1
+ # 0037: Send a maximum at the engine's default sentinel as no bound
2
+
3
+ ## Status
4
+
5
+ Accepted.
6
+
7
+ ## Context
8
+
9
+ libhegel 0.49.0 adds `HEGEL_MAX_SIZE_DEFAULT`, which is `UINT64_MAX - 1`.
10
+ `hegel_new_collection`, `hegel_string_generator_text`, and
11
+ `hegel_generate_bytes` read that `max_size` as "the caller gave no maximum",
12
+ and apply the default of text and bytes: 100, or `min_size + 100` when
13
+ `min_size` is above 100. `UINT64_MAX` continues to mean no upper bound. The
14
+ engine changelog tells a frontend to pass `UINT64_MAX` for a user's explicit
15
+ maximum at or above `HEGEL_MAX_SIZE_DEFAULT`.
16
+
17
+ Before this change, the gem sent a caller's `max_size` to the engine as
18
+ it is. Measured on linux-amd64 with Ruby 3.4.10 against 0.49.0, without this
19
+ change, 300 cases of `binary(min_size: 200, max_size: 2**64 - 2)` had 200 to
20
+ 300 bytes: the engine applied `min_size + 100`. With this change, the same
21
+ draw had 200 to 1465 bytes. A Ruby `Integer` has no upper limit, so a caller
22
+ can also write a maximum above `UINT64_MAX`. Without this change,
23
+ `binary(max_size: 2**70)` raised `RangeError` from the `ffi` gem (measured on
24
+ the same setup).
25
+
26
+ The Rust frontend declares the constant in
27
+ `frontends/rust/src/ffi/sys/types.rs` at the `libhegel/0.49.0` tag. Its text
28
+ generator, in `frontends/rust/src/generators/strings.rs`, continues to pass
29
+ its own default of 100 (inferred from reading that file).
30
+
31
+ ## Decision
32
+
33
+ `Hegel::LibHegel.max_size_arg` changes a `max_size` at or above
34
+ `HEGEL_MAX_SIZE_DEFAULT` to `UINT64_MAX`. `Hegel::TestCase#new_collection`,
35
+ `#with_text_generator`, and `#generate_bytes` apply it, so every generator
36
+ that sizes a draw through the engine sends the caller's maximum this way.
37
+
38
+ The gem continues to compute its own default maximum of text and bytes
39
+ (`Hegel::Generators.default_max_size`) and to send it as a number. It does not
40
+ send the sentinel. The default is the same number, and a reader of the
41
+ generator then sees the default in Ruby.
42
+
43
+ ## Consequences
44
+
45
+ A maximum at or above `2**64 - 2` means no upper bound. No allocation can
46
+ reach a size of that order, so a caller who writes one gets the meaning
47
+ they wrote. A maximum above `2**64 - 1` also means no upper bound now, where it
48
+ raised `RangeError` before.
@@ -89,6 +89,7 @@ module Hegel
89
89
  "hegel_settings_register_profile", [:pointer, StringStruct.by_value, :pointer], :int32
90
90
  )
91
91
  @hegel_set_default_profile_fn = bind("hegel_set_default_profile", [:pointer, StringStruct.by_value], :int32)
92
+ @hegel_set_unicode_version_fn = bind("hegel_set_unicode_version", [:pointer, StringStruct.by_value], :int32)
92
93
  @hegel_settings_set_test_cases_fn = bind(
93
94
  "hegel_settings_set_test_cases", [:pointer, :pointer, :uint64], :int32
94
95
  )
@@ -307,7 +308,7 @@ module Hegel
307
308
  "hegel_generate_grammar", [:pointer, :pointer, :pointer, :uint64, :pointer], :int32
308
309
  )
309
310
 
310
- LibHegel.with_context(self) { |ctx| LibHegel.warn_on_version_mismatch(self, ctx, io: io) }
311
+ LibHegel.with_context(self) { |ctx| LibHegel.prepare(self, ctx, io: io) }
311
312
  end
312
313
 
313
314
  # hegel_context_new never returns NULL (guaranteed by the header), so
@@ -382,6 +383,14 @@ module Hegel
382
383
  nil
383
384
  end
384
385
 
386
+ # Sets the Unicode version for the whole process. "" selects the
387
+ # engine's newest tables.
388
+ def set_unicode_version(ctx, version)
389
+ code = @hegel_set_unicode_version_fn.call(ctx, string_view(version))
390
+ LibHegel.check!(self, ctx, code)
391
+ nil
392
+ end
393
+
385
394
  # No-op when +s+ is nil, matching hegel_settings_free's documented
386
395
  # no-op-on-NULL contract; not translated, for the same reason as
387
396
  # #context_free: the header documents this call as always returning
@@ -773,7 +782,9 @@ module Hegel
773
782
  end
774
783
 
775
784
  # Closes the most recently opened span. +discard+ true marks it
776
- # rejected, so libhegel retries from before the span opened.
785
+ # rejected, so the shrinker can delete its draws. The engine does not
786
+ # draw again by itself: the caller does, as Hegel::Generator#filter
787
+ # does.
777
788
  def stop_span(ctx, tc, discard)
778
789
  code = @hegel_stop_span_fn.call(ctx, tc, discard)
779
790
  LibHegel.check!(self, ctx, code)
@@ -110,6 +110,14 @@ module Hegel
110
110
  # value a 64-bit unsigned integer maxes out at.
111
111
  HEGEL_COLLECTION_MAX_SIZE_UNBOUNDED = (2**64) - 1
112
112
 
113
+ # HEGEL_MAX_SIZE_DEFAULT, named from hegel.h's #define of the same name,
114
+ # UINT64_MAX - 1. Since libhegel 0.49.0, hegel_new_collection,
115
+ # hegel_string_generator_text, and hegel_generate_bytes read this max_size
116
+ # as "no maximum given" and apply the default of text and bytes. The header
117
+ # tells a binding to pass UINT64_MAX for a caller's own maximum at or above
118
+ # it. See #max_size_arg.
119
+ HEGEL_MAX_SIZE_DEFAULT = (2**64) - 2
120
+
113
121
  # hegel_generate_float's smallest_nonzero_magnitude must be positive
114
122
  # and finite. The header names 5e-324 as the width-64 value that
115
123
  # places no restriction on which nonzero magnitudes get drawn.
@@ -121,7 +129,7 @@ module Hegel
121
129
  # responds to each name here.
122
130
  METHODS = %i[
123
131
  context_new context_free context_last_error version
124
- settings_new settings_new_for_profile settings_register_profile set_default_profile
132
+ settings_new settings_new_for_profile settings_register_profile set_default_profile set_unicode_version
125
133
  settings_free settings_set_test_cases settings_set_verbosity
126
134
  settings_set_seed settings_set_derandomize settings_set_database
127
135
  run_start next_test_case run_free test_case_free mark_complete
@@ -212,6 +220,30 @@ module Hegel
212
220
  MESSAGE
213
221
  end
214
222
 
223
+ # What a freshly loaded engine needs before it builds a generator. Held
224
+ # here rather than in Real#initialize so the Fake can check it.
225
+ def prepare(impl, ctx, io: $stderr)
226
+ warn_on_version_mismatch(impl, ctx, io: io)
227
+ follow_host_unicode_version(impl, ctx)
228
+ end
229
+
230
+ # Sets the engine's Unicode version to the one Ruby's own Regexp and
231
+ # String methods follow, so a drawn character falls in the category Ruby
232
+ # puts it in (docs/adr/0036). The setting is process-wide and read when
233
+ # a generator is built, so it is made once, when the engine is loaded.
234
+ def follow_host_unicode_version(impl, ctx)
235
+ impl.set_unicode_version(ctx, RbConfig::CONFIG.fetch("UNICODE_VERSION"))
236
+ end
237
+
238
+ # The max_size to pass to the engine for a caller's own +max_size+. A
239
+ # maximum at or above HEGEL_MAX_SIZE_DEFAULT goes over as UINT64_MAX, no
240
+ # upper bound: otherwise the engine reads UINT64_MAX - 1 as its default of
241
+ # 100, and a larger value does not fit the u64 argument. No allocation can
242
+ # reach either size, so "no upper bound" keeps the caller's meaning.
243
+ def max_size_arg(max_size)
244
+ (max_size >= HEGEL_MAX_SIZE_DEFAULT) ? HEGEL_COLLECTION_MAX_SIZE_UNBOUNDED : max_size
245
+ end
246
+
215
247
  # A span label is an opaque u64 that names the generator which opened
216
248
  # the span. The engine treats two spans with one label as draws of one
217
249
  # generator, and swaps or reorders them when it shrinks. hegel.h defines
@@ -5,5 +5,5 @@ module Hegel
5
5
  # Hegel::VERSION (the gem's own release): hegel-go and hegel-typescript keep
6
6
  # the two separate too, so bumping the pinned engine can land as its own
7
7
  # commit without also releasing a new gem version.
8
- LIBHEGEL_VERSION = "0.48.0"
8
+ LIBHEGEL_VERSION = "0.49.0"
9
9
  end
data/lib/hegel/runner.rb CHANGED
@@ -202,9 +202,9 @@ module Hegel
202
202
  # Pulls test cases from +run+ until hegel_next_test_case reports none
203
203
  # left (a nil out-parameter, not an error). Never counts iterations
204
204
  # itself: test_cases bounds generation, not how many times shrinking
205
- # and rejected cases call the body. Measured against libhegel 0.48.0, a
205
+ # and rejected cases call the body. Measured against libhegel 0.49.0, a
206
206
  # run configured for 20 test cases whose body rejected nearly every case
207
- # took 782 to 2047 iterations over 30 runs. +stats+ does its own, different counting
207
+ # took 806 to 2032 iterations over 30 runs. +stats+ does its own, different counting
208
208
  # -- see GenerationStats above.
209
209
  def drive(impl, ctx, run, grammars, stats, captures, &block)
210
210
  loop do
@@ -310,7 +310,7 @@ module Hegel
310
310
  # hegel_new_collection: Hegel::Generators::ArrayGenerator's own sizing
311
311
  # primitive, paired with #collection_more and #collection_free.
312
312
  def new_collection(min_size, max_size)
313
- @impl.new_collection(@ctx, live_handle, min_size, max_size)
313
+ @impl.new_collection(@ctx, live_handle, min_size, LibHegel.max_size_arg(max_size))
314
314
  end
315
315
 
316
316
  # hegel_collection_more: whether to draw another element into
@@ -340,8 +340,9 @@ module Hegel
340
340
  # read the same way as #with_regex_generator/#with_email_generator/
341
341
  # #with_url_generator/#with_domain_generator below, each named for its
342
342
  # own hegel_string_generator_* call.
343
- def with_text_generator(**kwargs, &block)
344
- with_generator_handle(@impl.string_generator_text(@ctx, **kwargs), &block)
343
+ def with_text_generator(max_size:, **kwargs, &block)
344
+ handle = @impl.string_generator_text(@ctx, max_size: LibHegel.max_size_arg(max_size), **kwargs)
345
+ with_generator_handle(handle, &block)
345
346
  end
346
347
 
347
348
  # Runs the block with a regex-matching string generator handle built
@@ -381,7 +382,7 @@ module Hegel
381
382
  # hegel_generate_bytes. A future Hegel::Generators::BinaryGenerator's
382
383
  # own primitive, the bytes counterpart to #generate_string.
383
384
  def generate_bytes(min_size, max_size)
384
- @impl.generate_bytes(@ctx, live_handle, min_size, max_size)
385
+ @impl.generate_bytes(@ctx, live_handle, min_size, LibHegel.max_size_arg(max_size))
385
386
  end
386
387
 
387
388
  # hegel_generate_ipv4, returning the address's 4 raw bytes. Converting
data/lib/hegel/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Hegel
4
- VERSION = "0.4.0"
4
+ VERSION = "0.4.1"
5
5
  end
data/sig/hegel.rbs CHANGED
@@ -701,12 +701,16 @@ module Hegel
701
701
  HEGEL_HC_LARGE_INITIAL_TEST_CASE: Integer
702
702
  HEGEL_STATE_MACHINE_DONE: Integer
703
703
  HEGEL_COLLECTION_MAX_SIZE_UNBOUNDED: Integer
704
+ HEGEL_MAX_SIZE_DEFAULT: Integer
704
705
  HEGEL_FLOAT64_SMALLEST_NONZERO_MAGNITUDE_UNRESTRICTED: Float
705
706
  METHODS: Array[Symbol]
706
707
 
707
708
  def self.with_context: [R] (untyped impl) { (untyped) -> R } -> R
708
709
  def self.check!: (untyped impl, untyped ctx, Integer code) -> void
709
710
  def self.warn_on_version_mismatch: (untyped impl, untyped ctx, ?io: untyped) -> void
711
+ def self.prepare: (untyped impl, untyped ctx, ?io: untyped) -> nil
712
+ def self.follow_host_unicode_version: (untyped impl, untyped ctx) -> nil
713
+ def self.max_size_arg: (Integer max_size) -> Integer
710
714
  def self.label_from_name: (String name) -> Integer
711
715
  def self.label_combine: (Array[Integer] labels) -> Integer
712
716
  def self.encode_integer_le: (Integer n) -> String
@@ -722,6 +726,7 @@ module Hegel
722
726
  def settings_new_for_profile: (untyped ctx, String name) -> untyped
723
727
  def settings_register_profile: (untyped ctx, String name, untyped s) -> nil
724
728
  def set_default_profile: (untyped ctx, String name) -> nil
729
+ def set_unicode_version: (untyped ctx, String version) -> nil
725
730
  def settings_free: (untyped ctx, untyped s) -> nil
726
731
  def settings_set_test_cases: (untyped ctx, untyped s, Integer n) -> nil
727
732
  def settings_set_verbosity: (untyped ctx, untyped s, Integer v) -> nil
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hegeltest
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.4.1
5
5
  platform: x86_64-linux
6
6
  authors:
7
7
  - meganemura
@@ -82,6 +82,8 @@ files:
82
82
  - docs/adr/0033-cache-compiled-grammars-for-the-length-of-a-run.md
83
83
  - docs/adr/0034-repeat-a-test-body-with-a-ruby-side-indent.md
84
84
  - docs/adr/0035-defer-concurrent-state-machines-and-the-engine-printer.md
85
+ - docs/adr/0036-follow-rubys-unicode-version.md
86
+ - docs/adr/0037-send-a-maximum-at-the-engine-default-sentinel-as-no-bound.md
85
87
  - docs/architecture.md
86
88
  - lib/hegel.rb
87
89
  - lib/hegel/draw_name.rb