hegeltest 0.3.0-aarch64-linux → 0.3.4-aarch64-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 +4 -4
- data/CHANGELOG.md +103 -0
- data/README.md +1 -1
- data/docs/README.md +2 -0
- data/docs/adr/0025-copy-each-string-input-into-a-hegel-string-t.md +61 -0
- data/docs/adr/0026-select-the-engine-release-repository-by-version.md +47 -0
- data/docs/architecture.md +17 -10
- data/lib/hegel/lib_hegel/real.rb +146 -114
- data/lib/hegel/lib_hegel.rb +2 -2
- data/lib/hegel/libhegel/libhegel-linux-arm64.so +0 -0
- data/lib/hegel/libhegel_version.rb +1 -1
- data/lib/hegel/locate.rb +10 -10
- data/lib/hegel/runner.rb +2 -2
- data/lib/hegel/version.rb +1 -1
- data/lib/tasks/libhegel.rake +17 -5
- data/lib/tasks/platform_gems.rake +3 -3
- data/sig/hegel.rbs +1 -1
- data/skills/hegel-ruby/references/ruby/reference.md +15 -9
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 544ae6338014de3a6d333bdb22908f05407e134f410f56c951bb514ca17a10b1
|
|
4
|
+
data.tar.gz: a0d77af10c8df7d6179f45983c179d07698dee54e88f1445a66d39ab7679a926
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fa798e612d182cc9f43c4c1ce0fd52dc17312b5a4b02b9340fd29ccbe0d60c3ab5543b4cd1bdadc6f94b05f70bf0b620940ef6f7ed47e52fe1604750dcb579c5
|
|
7
|
+
data.tar.gz: 901a2da33ccf6ba6579f4cbeb2852ef25c04da21ca9b9542b2e3661a00a7c7f1544208d9729e7da059331a6046ee7139136ff30890f5a47266ecfdaa1980175c
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,105 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.3.4] - 2026-10-20
|
|
6
|
+
|
|
7
|
+
### Engine: libhegel 0.47.2 (was 0.47.1)
|
|
8
|
+
|
|
9
|
+
The [libhegel 0.47.2 release notes](https://github.com/hegeldev/hegel/releases/tag/libhegel/0.47.2)
|
|
10
|
+
describe 0.47.2. It is the first engine release from the hegeldev/hegel
|
|
11
|
+
repository.
|
|
12
|
+
|
|
13
|
+
Each platform gem carries `libhegel` 0.47.2. The engine does not change its
|
|
14
|
+
C interface. A caller has nothing to change in code, and gets the same
|
|
15
|
+
results. The engine changes one text: the message of an internal engine
|
|
16
|
+
error now gives <https://github.com/hegeldev/hegel/issues> as the place to
|
|
17
|
+
report the problem, not the hegel-rust issue tracker.
|
|
18
|
+
|
|
19
|
+
### Development
|
|
20
|
+
|
|
21
|
+
- `rake libhegel:fetch` and `rake libhegel:fetch_all` download libhegel
|
|
22
|
+
0.47.2 and later from the hegeldev/hegel releases, and earlier versions
|
|
23
|
+
from the hegeldev/hegel-rust releases.
|
|
24
|
+
|
|
25
|
+
## [0.3.3] - 2026-10-17
|
|
26
|
+
|
|
27
|
+
### Engine: libhegel 0.47.1 (was 0.45.13)
|
|
28
|
+
|
|
29
|
+
The [libhegel 0.47.1 release notes](https://github.com/hegeldev/hegel-rust/releases/tag/libhegel-v0.47.1)
|
|
30
|
+
describe 0.47.1 alone. The
|
|
31
|
+
[engine changelog](https://github.com/hegeldev/hegel-rust/blob/libhegel-v0.47.1/hegel-c/CHANGELOG.md)
|
|
32
|
+
describes each engine release after 0.45.13, up to 0.47.1.
|
|
33
|
+
|
|
34
|
+
Each platform gem carries `libhegel` 0.47.1. The engine changes its C
|
|
35
|
+
interface: every string is now a `hegel_string_t`, a pointer and a length.
|
|
36
|
+
These bindings follow that change. A caller has nothing to change in code.
|
|
37
|
+
The engine move changes these results for a caller:
|
|
38
|
+
|
|
39
|
+
- A run with the default `report_multiple_failures: false` that finds more
|
|
40
|
+
than one distinct failure shrinks only the smallest of them and reports
|
|
41
|
+
it. Before, the engine shrank each failure and then reported one. The
|
|
42
|
+
example database keeps every distinct failure, as found. Such a run calls
|
|
43
|
+
the test body fewer times: for a body with two distinct failures and
|
|
44
|
+
`test_cases: 100`, 73 to 131 calls over 20 seeded runs, where 0.45.13
|
|
45
|
+
made 157 to 202. Both versions reported the same failure in each run.
|
|
46
|
+
- A string that holds a NUL character, such as a `target` label or a
|
|
47
|
+
`database:` path, reaches the engine whole. Before, it raised
|
|
48
|
+
`ArgumentError` ("string contains null byte") from the `ffi` gem.
|
|
49
|
+
|
|
50
|
+
## [0.3.2] - 2026-10-16
|
|
51
|
+
|
|
52
|
+
### Engine: libhegel 0.45.13 (was 0.45.7)
|
|
53
|
+
|
|
54
|
+
The [libhegel 0.45.13 release notes](https://github.com/hegeldev/hegel-rust/releases/tag/libhegel-v0.45.13)
|
|
55
|
+
describe 0.45.13 alone. The
|
|
56
|
+
[engine changelog](https://github.com/hegeldev/hegel-rust/blob/libhegel-v0.45.13/hegel-c/CHANGELOG.md)
|
|
57
|
+
describes each engine release after 0.45.7, up to 0.45.13.
|
|
58
|
+
|
|
59
|
+
Each platform gem carries `libhegel` 0.45.13. The engine only adds to its C
|
|
60
|
+
interface, and these bindings do not use the addition. A caller has nothing
|
|
61
|
+
to change in code. The engine changes these results for a caller:
|
|
62
|
+
|
|
63
|
+
- A run with a `seed:` can draw different values than it did before. The
|
|
64
|
+
engine changed how it copies values between spans that have the same
|
|
65
|
+
label, and how it draws an integer from a range wider than 128 bits.
|
|
66
|
+
- A failure that depends on an integer that is a multiple of a number, such
|
|
67
|
+
as 6, shrinks to the smallest such value more often. Before, the shrinker
|
|
68
|
+
could stop at a larger multiple, such as 78.
|
|
69
|
+
- A run of 1,000 test cases or more starts to use `target` scores soon
|
|
70
|
+
after it starts. Before, it started after half of the test cases.
|
|
71
|
+
- The `note:` line with the engine's nondeterminism caveat shows only for a
|
|
72
|
+
failure that the engine saw pass at least one time. Before, every failure
|
|
73
|
+
of a run that the engine treated as nondeterministic showed it.
|
|
74
|
+
- `reproduce_failure:` stops a replay when the test body draws differently
|
|
75
|
+
from the recorded choices. If no replay reproduces the failure, the run
|
|
76
|
+
raises `Hegel::Error` with the message that says that the blob did not
|
|
77
|
+
reproduce a failure, and the engine writes one line to standard error
|
|
78
|
+
that names the position where the draws left the recorded choices.
|
|
79
|
+
Before, the engine continued the replay with new draws and could report a
|
|
80
|
+
different failure as the failure of the blob.
|
|
81
|
+
|
|
82
|
+
## [0.3.1] - 2026-10-15
|
|
83
|
+
|
|
84
|
+
### Engine: libhegel 0.45.7 (was 0.45.4)
|
|
85
|
+
|
|
86
|
+
The [libhegel 0.45.7 release notes](https://github.com/hegeldev/hegel-rust/releases/tag/libhegel-v0.45.7)
|
|
87
|
+
describe 0.45.7 alone. The
|
|
88
|
+
[engine changelog](https://github.com/hegeldev/hegel-rust/blob/libhegel-v0.45.7/hegel-c/CHANGELOG.md)
|
|
89
|
+
describes each engine release after 0.45.4, up to 0.45.7.
|
|
90
|
+
|
|
91
|
+
Each platform gem carries `libhegel` 0.45.7. The engine only adds to its C
|
|
92
|
+
interface, and these bindings do not use the addition. A caller has nothing
|
|
93
|
+
to change in code. The engine changes these messages for a caller:
|
|
94
|
+
|
|
95
|
+
- `from_regex` with a pattern that no string matches, such as `"(?!)"`,
|
|
96
|
+
raises `Hegel::Error` with a message that names the pattern and says
|
|
97
|
+
that it `matches no string`. Before, the run raised `Hegel::Error` with
|
|
98
|
+
a message that starts with `Unsatisfiable`.
|
|
99
|
+
- The FilterTooMuch health check message says how to suppress the check
|
|
100
|
+
with the `suppress_health_check` setting, and it says "rejected by
|
|
101
|
+
assume() or a filter". Before, it showed Rust syntax. The
|
|
102
|
+
`FailedHealthCheck:` and `Unsatisfiable` prefixes do not change.
|
|
103
|
+
|
|
5
104
|
## [0.3.0] - 2026-10-14
|
|
6
105
|
|
|
7
106
|
### Engine: libhegel 0.45.4 (was 0.45.1)
|
|
@@ -174,6 +273,10 @@ The engine is called through the `ffi` gem. Each platform gem carries the
|
|
|
174
273
|
matching `libhegel` 0.32.5 build. The platform-independent gem carries none,
|
|
175
274
|
so a run on it needs `HEGEL_LIBHEGEL_PATH` pointing at a local build.
|
|
176
275
|
|
|
276
|
+
[0.3.4]: https://github.com/meganemura/hegel-ruby/releases/tag/v0.3.4
|
|
277
|
+
[0.3.3]: https://github.com/meganemura/hegel-ruby/releases/tag/v0.3.3
|
|
278
|
+
[0.3.2]: https://github.com/meganemura/hegel-ruby/releases/tag/v0.3.2
|
|
279
|
+
[0.3.1]: https://github.com/meganemura/hegel-ruby/releases/tag/v0.3.1
|
|
177
280
|
[0.3.0]: https://github.com/meganemura/hegel-ruby/releases/tag/v0.3.0
|
|
178
281
|
[0.2.1]: https://github.com/meganemura/hegel-ruby/releases/tag/v0.2.1
|
|
179
282
|
[0.2.0]: https://github.com/meganemura/hegel-ruby/releases/tag/v0.2.0
|
data/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
> there reviews or endorses it.
|
|
8
8
|
>
|
|
9
9
|
> The official implementations are
|
|
10
|
-
> [Rust](https://github.com/hegeldev/hegel
|
|
10
|
+
> [Rust](https://github.com/hegeldev/hegel/tree/main/frontends/rust),
|
|
11
11
|
> [Go](https://github.com/hegeldev/hegel-go),
|
|
12
12
|
> [C++](https://github.com/hegeldev/hegel-cpp),
|
|
13
13
|
> [TypeScript](https://github.com/hegeldev/hegel-typescript),
|
data/docs/README.md
CHANGED
|
@@ -31,6 +31,8 @@ An index of the design records for `hegel-ruby`.
|
|
|
31
31
|
- [0022: Keep microsecond times over a nanosecond engine](adr/0022-keep-microsecond-times-over-a-nanosecond-engine.md)
|
|
32
32
|
- [0023: Leave unset settings to the engine's profile](adr/0023-leave-unset-settings-to-the-engines-profile.md)
|
|
33
33
|
- [0024: Release when a merge changes the gem version](adr/0024-release-when-a-merge-changes-the-gem-version.md)
|
|
34
|
+
- [0025: Copy each string input into a hegel_string_t](adr/0025-copy-each-string-input-into-a-hegel-string-t.md)
|
|
35
|
+
- [0026: Select the engine release repository by version](adr/0026-select-the-engine-release-repository-by-version.md)
|
|
34
36
|
|
|
35
37
|
A new decision gets a new record. A changed decision supersedes the old
|
|
36
38
|
record instead of editing it, so the history stays readable.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# 0025: Copy each string input into a hegel_string_t
|
|
2
|
+
|
|
3
|
+
## Status
|
|
4
|
+
|
|
5
|
+
Accepted.
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
libhegel 0.46.0 changed every string in its C interface to a
|
|
10
|
+
`hegel_string_t`, a struct of a data pointer and a byte length. An ordinary
|
|
11
|
+
input goes by value. An optional input goes through a pointer to the
|
|
12
|
+
struct, and a NULL pointer means that the value is absent. An output is a
|
|
13
|
+
struct that the engine writes. Before 0.46, a string input was a
|
|
14
|
+
NUL-terminated `const char *`, which the `ffi` gem marshals from a Ruby
|
|
15
|
+
String with the `:string` type. That type stops at the first NUL, and
|
|
16
|
+
`ffi` 1.17.4 raises `ArgumentError` for a String with an interior NUL.
|
|
17
|
+
|
|
18
|
+
The struct is 16 bytes on a 64-bit platform. The calling conventions do not
|
|
19
|
+
pass a struct of that size in the same way: x86-64 System V and AArch64
|
|
20
|
+
pass it in two registers, and Windows x64 passes it through a hidden
|
|
21
|
+
pointer. This is inferred from the ABI documents of each platform, not
|
|
22
|
+
measured here; the CI runs the suite on each of them. A binding that
|
|
23
|
+
declared two scalar arguments in place of the struct would match some of
|
|
24
|
+
these conventions and not others.
|
|
25
|
+
|
|
26
|
+
hegel-rust's own frontend (`src/ffi.rs` at `libhegel-v0.47.1`) borrows the
|
|
27
|
+
bytes of a Rust string for the length of the call.
|
|
28
|
+
|
|
29
|
+
## Decision
|
|
30
|
+
|
|
31
|
+
`LibHegel::Real` declares each `hegel_string_t` argument as
|
|
32
|
+
`StringStruct.by_value`, and lets libffi apply the calling convention of the
|
|
33
|
+
platform. It declares each optional string argument as `:pointer`, and
|
|
34
|
+
passes `nil` for an absent value and a `StringStruct` for a present one, so
|
|
35
|
+
that `""` stays different from `nil`. `settings_set_database` needs that
|
|
36
|
+
difference: `""` turns the database off, and `nil` keeps the engine's
|
|
37
|
+
default directory.
|
|
38
|
+
|
|
39
|
+
Each input string is copied into a native buffer that the struct holds. The
|
|
40
|
+
binding does not point the struct at the memory of the Ruby String: a copy
|
|
41
|
+
does not depend on how long the garbage collector keeps that memory in
|
|
42
|
+
place (inferred: compaction can move the contents of a short String).
|
|
43
|
+
Measured on `ffi` 1.17.4, a struct field keeps the buffer it holds alive
|
|
44
|
+
through a full garbage collection. The struct is an argument of the native
|
|
45
|
+
call, so the buffer lives until the call returns.
|
|
46
|
+
|
|
47
|
+
An output is read by its length, not up to its trailing NUL, because an
|
|
48
|
+
engine string can hold an interior NUL.
|
|
49
|
+
|
|
50
|
+
## Consequences
|
|
51
|
+
|
|
52
|
+
A string that holds a NUL character, for example a `target` label, reaches
|
|
53
|
+
the engine whole. Before, it raised `ArgumentError`.
|
|
54
|
+
|
|
55
|
+
Each string input costs one native allocation and one copy. The strings that
|
|
56
|
+
cross on each test case are short: an origin for an interesting case, and
|
|
57
|
+
a label for each `target` call.
|
|
58
|
+
|
|
59
|
+
The rename of `hegel_generate_string_result_free` to
|
|
60
|
+
`hegel_owned_string_free` renames the binding method to `owned_string_free`.
|
|
61
|
+
`LibHegel::METHODS`, the Fake, and `sig/hegel.rbs` follow it.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# 0026: Select the engine release repository by version
|
|
2
|
+
|
|
3
|
+
## Status
|
|
4
|
+
|
|
5
|
+
Accepted.
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
Up to libhegel 0.47.1, hegeldev/hegel-rust published each engine release
|
|
10
|
+
with the tag `libhegel-v<version>`. From 0.47.2, the engine and the Rust
|
|
11
|
+
frontend live in the monorepo hegeldev/hegel, which publishes each engine
|
|
12
|
+
release with the tag `libhegel/<version>`. hegeldev/hegel-rust is now an
|
|
13
|
+
archive and gets no new release. The asset names did not change: `hegel.h`,
|
|
14
|
+
`libhegel-<os>-<arch>.<ext>`, and `<asset>.sha256`.
|
|
15
|
+
|
|
16
|
+
Measured on 2026-10-19 with HTTP requests to the release download URLs:
|
|
17
|
+
hegeldev/hegel does not carry the assets of the old releases
|
|
18
|
+
(`libhegel/0.47.1/hegel.h` gives HTTP 404). Measured on 2026-10-20: each of
|
|
19
|
+
the five platform assets of 0.47.2, and its `.sha256` file, gives HTTP 200
|
|
20
|
+
from hegeldev/hegel.
|
|
21
|
+
|
|
22
|
+
## Decision
|
|
23
|
+
|
|
24
|
+
`Hegel::LibhegelFetch.release_base` selects the repository from the version.
|
|
25
|
+
A version of 0.47.2 or later downloads from
|
|
26
|
+
`https://github.com/hegeldev/hegel/releases/download/libhegel/<version>`. An
|
|
27
|
+
earlier version downloads from
|
|
28
|
+
`https://github.com/hegeldev/hegel-rust/releases/download/libhegel-v<version>`.
|
|
29
|
+
|
|
30
|
+
The rule keeps the old releases reachable. A contributor who checks out an
|
|
31
|
+
old commit of this gem can still run `rake libhegel:fetch` for the version
|
|
32
|
+
that commit pins.
|
|
33
|
+
|
|
34
|
+
One alternative was refused: a single base URL for the new repository. It
|
|
35
|
+
is shorter, but it breaks `rake libhegel:fetch` on every old commit, because
|
|
36
|
+
the new repository does not carry the old assets.
|
|
37
|
+
|
|
38
|
+
## Consequences
|
|
39
|
+
|
|
40
|
+
The CHANGELOG rule in CLAUDE.md links the engine release notes and
|
|
41
|
+
`libhegel/CHANGELOG.md` in hegeldev/hegel for an engine move to 0.47.2 or
|
|
42
|
+
later. The reference paths in CLAUDE.md and `docs/architecture.md` point into
|
|
43
|
+
the monorepo tree: `libhegel/` for the engine, and `frontends/rust/` for the
|
|
44
|
+
Rust frontend.
|
|
45
|
+
|
|
46
|
+
If the old releases move to hegeldev/hegel later, the legacy branch can go,
|
|
47
|
+
and this record gets a successor.
|
data/docs/architecture.md
CHANGED
|
@@ -43,10 +43,13 @@ schedules it.
|
|
|
43
43
|
|
|
44
44
|
## Where meaning comes from
|
|
45
45
|
|
|
46
|
-
libhegel is
|
|
47
|
-
it from the `
|
|
48
|
-
`
|
|
49
|
-
|
|
46
|
+
libhegel is the native engine of every Hegel frontend. Since 0.47.2, the
|
|
47
|
+
hegeldev/hegel Cargo workspace builds it from the `libhegel` directory, as
|
|
48
|
+
the crate `hegeltest-c`, whose header is `libhegel/include/hegel.h`. The
|
|
49
|
+
same workspace holds the Rust frontend, hegel-rust, at `frontends/rust`.
|
|
50
|
+
Releases up to 0.47.1 came from hegeldev/hegel-rust, where the engine was at
|
|
51
|
+
`hegel-c`. The header defines the ABI this gem calls: what each function
|
|
52
|
+
does, what its error codes mean, and who owns each pointer.
|
|
50
53
|
|
|
51
54
|
The Java, Go, TypeScript, OCaml, and C++ implementations bind the same
|
|
52
55
|
engine. Each works under a constraint hegel-rust does not have: a dynamic
|
|
@@ -171,12 +174,16 @@ fields.
|
|
|
171
174
|
|
|
172
175
|
## Answered
|
|
173
176
|
|
|
174
|
-
A drawn string arrives as a pointer and a length,
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
177
|
+
A drawn string arrives as a pointer and a length, and can hold interior
|
|
178
|
+
NUL bytes, because the drawn alphabet can include U+0000. Read by length.
|
|
179
|
+
Before libhegel 0.46 the string had no NUL terminator. Since 0.46 every
|
|
180
|
+
string the engine hands back is a `hegel_string_t` with a trailing NUL that
|
|
181
|
+
its length excludes, but an interior NUL still ends the string early for a
|
|
182
|
+
reader that stops at the first NUL. Measured against 0.45.0: an alphabet
|
|
183
|
+
pinned to U+0000 produces a three-byte draw that reads as `""` when taken
|
|
184
|
+
up to the first NUL and as three NUL bytes when taken by its reported
|
|
185
|
+
length. Every string observed reports `valid_encoding?` after
|
|
186
|
+
`force_encoding(UTF-8)`.
|
|
180
187
|
|
|
181
188
|
Prism recovers a draw's assignment target through a method chain, an
|
|
182
189
|
instance-variable assignment, and an assignment spanning several lines.
|
data/lib/hegel/lib_hegel/real.rb
CHANGED
|
@@ -18,7 +18,7 @@ module Hegel
|
|
|
18
18
|
# symbol.
|
|
19
19
|
class Real
|
|
20
20
|
# hegel_date_t / hegel_time_t / hegel_datetime_t, transcribed field
|
|
21
|
-
# for field from
|
|
21
|
+
# for field from libhegel/include/hegel.h. FFI::Struct computes each
|
|
22
22
|
# field's offset and the struct's own alignment padding from this
|
|
23
23
|
# layout -- the same C-ABI rules the compiled library was built
|
|
24
24
|
# against -- so there is no hand-computed byte offset here to drift
|
|
@@ -40,17 +40,19 @@ module Hegel
|
|
|
40
40
|
layout :date, DateStruct, :time, TimeStruct
|
|
41
41
|
end
|
|
42
42
|
|
|
43
|
-
#
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
#
|
|
47
|
-
#
|
|
48
|
-
# and
|
|
49
|
-
|
|
43
|
+
# hegel_string_t (`{ const char *data; size_t len; }`), and
|
|
44
|
+
# hegel_generate_bytes_result_t (`{ uint8_t *data; size_t len; }`),
|
|
45
|
+
# from libhegel/include/hegel.h: the same layout under two names. Since
|
|
46
|
+
# libhegel 0.46 every string crosses the ABI as a hegel_string_t: by
|
|
47
|
+
# value for an ordinary input, through a nullable pointer for an
|
|
48
|
+
# optional input, and as a struct the engine writes for an output.
|
|
49
|
+
# hegel_owned_string_t, which #generate_string reads, is a typedef of
|
|
50
|
+
# hegel_string_t. One Ruby struct mirrors all of them.
|
|
51
|
+
class StringStruct < FFI::Struct
|
|
50
52
|
layout :data, :pointer, :len, :size_t
|
|
51
53
|
end
|
|
52
54
|
|
|
53
|
-
private_constant :DateStruct, :TimeStruct, :DatetimeStruct, :
|
|
55
|
+
private_constant :DateStruct, :TimeStruct, :DatetimeStruct, :StringStruct
|
|
54
56
|
|
|
55
57
|
# Opens +path+ (default: Hegel::Locate.resolve) and binds the
|
|
56
58
|
# functions this boundary calls. Immediately after, opens a context
|
|
@@ -75,7 +77,7 @@ module Hegel
|
|
|
75
77
|
|
|
76
78
|
@hegel_context_new_fn = bind("hegel_context_new", [], :pointer)
|
|
77
79
|
@hegel_context_free_fn = bind("hegel_context_free", [:pointer], :int32)
|
|
78
|
-
@hegel_context_last_error_fn = bind("hegel_context_last_error", [:pointer],
|
|
80
|
+
@hegel_context_last_error_fn = bind("hegel_context_last_error", [:pointer], StringStruct.by_value)
|
|
79
81
|
@hegel_version_fn = bind("hegel_version", [:pointer, :pointer], :int32)
|
|
80
82
|
|
|
81
83
|
@hegel_settings_new_fn = bind("hegel_settings_new", [:pointer, :pointer], :int32)
|
|
@@ -93,13 +95,13 @@ module Hegel
|
|
|
93
95
|
"hegel_settings_set_derandomize", [:pointer, :pointer, :bool], :int32
|
|
94
96
|
)
|
|
95
97
|
@hegel_settings_set_database_fn = bind(
|
|
96
|
-
"hegel_settings_set_database", [:pointer, :pointer, :
|
|
98
|
+
"hegel_settings_set_database", [:pointer, :pointer, :pointer], :int32
|
|
97
99
|
)
|
|
98
100
|
@hegel_settings_set_report_multiple_failures_fn = bind(
|
|
99
101
|
"hegel_settings_set_report_multiple_failures", [:pointer, :pointer, :bool], :int32
|
|
100
102
|
)
|
|
101
103
|
@hegel_settings_set_database_key_fn = bind(
|
|
102
|
-
"hegel_settings_set_database_key", [:pointer, :pointer, :
|
|
104
|
+
"hegel_settings_set_database_key", [:pointer, :pointer, :pointer], :int32
|
|
103
105
|
)
|
|
104
106
|
@hegel_settings_set_phases_fn = bind(
|
|
105
107
|
"hegel_settings_set_phases", [:pointer, :pointer, :uint32], :int32
|
|
@@ -112,7 +114,7 @@ module Hegel
|
|
|
112
114
|
"hegel_run_start", [:pointer, :pointer, :pointer, :pointer, :pointer], :int32
|
|
113
115
|
)
|
|
114
116
|
@hegel_run_start_blob_fn = bind(
|
|
115
|
-
"hegel_run_start_blob", [:pointer, :pointer,
|
|
117
|
+
"hegel_run_start_blob", [:pointer, :pointer, StringStruct.by_value, :pointer, :pointer, :pointer], :int32
|
|
116
118
|
)
|
|
117
119
|
@hegel_next_test_case_fn = bind("hegel_next_test_case", [:pointer, :pointer, :pointer], :int32)
|
|
118
120
|
@hegel_test_case_should_capture_fn = bind(
|
|
@@ -120,8 +122,8 @@ module Hegel
|
|
|
120
122
|
)
|
|
121
123
|
@hegel_run_free_fn = bind("hegel_run_free", [:pointer, :pointer], :int32)
|
|
122
124
|
@hegel_test_case_free_fn = bind("hegel_test_case_free", [:pointer, :pointer], :int32)
|
|
123
|
-
@hegel_mark_complete_fn = bind("hegel_mark_complete", [:pointer, :pointer, :uint32, :
|
|
124
|
-
@hegel_target_fn = bind("hegel_target", [:pointer, :pointer, :double,
|
|
125
|
+
@hegel_mark_complete_fn = bind("hegel_mark_complete", [:pointer, :pointer, :uint32, :pointer], :int32)
|
|
126
|
+
@hegel_target_fn = bind("hegel_target", [:pointer, :pointer, :double, StringStruct.by_value], :int32)
|
|
125
127
|
|
|
126
128
|
@hegel_run_result_fn = bind("hegel_run_result", [:pointer, :pointer, :pointer], :int32)
|
|
127
129
|
@hegel_run_result_free_fn = bind("hegel_run_result_free", [:pointer, :pointer], :int32)
|
|
@@ -159,7 +161,7 @@ module Hegel
|
|
|
159
161
|
)
|
|
160
162
|
@hegel_collection_more_fn = bind("hegel_collection_more", [:pointer, :pointer, :pointer, :pointer], :int32)
|
|
161
163
|
@hegel_collection_reject_fn = bind(
|
|
162
|
-
"hegel_collection_reject", [:pointer, :pointer, :pointer, :
|
|
164
|
+
"hegel_collection_reject", [:pointer, :pointer, :pointer, :pointer], :int32
|
|
163
165
|
)
|
|
164
166
|
@hegel_collection_free_fn = bind("hegel_collection_free", [:pointer, :pointer], :int32)
|
|
165
167
|
|
|
@@ -196,14 +198,14 @@ module Hegel
|
|
|
196
198
|
[:pointer, :pointer, :uint32, :double, :double, :bool, :bool, :bool, :bool, :double, :pointer], :int32
|
|
197
199
|
)
|
|
198
200
|
|
|
199
|
-
# hegel_string_generator_text takes
|
|
200
|
-
# last
|
|
201
|
-
#
|
|
202
|
-
#
|
|
201
|
+
# hegel_string_generator_text takes 12 arguments past ctx; the
|
|
202
|
+
# last 6 (categories through exclude_characters) are the category
|
|
203
|
+
# and explicit-character filter parameters this binding does not
|
|
204
|
+
# wire up (see #string_generator_text).
|
|
203
205
|
@hegel_string_generator_text_fn = bind(
|
|
204
206
|
"hegel_string_generator_text",
|
|
205
|
-
[:pointer, :uint64, :uint64, :
|
|
206
|
-
:pointer, :size_t, :pointer, :size_t,
|
|
207
|
+
[:pointer, :uint64, :uint64, :pointer, :uint32, :uint32,
|
|
208
|
+
:pointer, :size_t, :pointer, :size_t, StringStruct.by_value, StringStruct.by_value,
|
|
207
209
|
:pointer],
|
|
208
210
|
:int32
|
|
209
211
|
)
|
|
@@ -211,9 +213,7 @@ module Hegel
|
|
|
211
213
|
@hegel_generate_string_fn = bind(
|
|
212
214
|
"hegel_generate_string", [:pointer, :pointer, :pointer, :pointer], :int32
|
|
213
215
|
)
|
|
214
|
-
@
|
|
215
|
-
"hegel_generate_string_result_free", [:pointer, :pointer], :int32
|
|
216
|
-
)
|
|
216
|
+
@hegel_owned_string_free_fn = bind("hegel_owned_string_free", [:pointer, :pointer], :int32)
|
|
217
217
|
|
|
218
218
|
@hegel_generate_bytes_fn = bind(
|
|
219
219
|
"hegel_generate_bytes", [:pointer, :pointer, :uint64, :uint64, :pointer], :int32
|
|
@@ -223,7 +223,7 @@ module Hegel
|
|
|
223
223
|
)
|
|
224
224
|
|
|
225
225
|
@hegel_string_generator_regex_fn = bind(
|
|
226
|
-
"hegel_string_generator_regex", [:pointer,
|
|
226
|
+
"hegel_string_generator_regex", [:pointer, StringStruct.by_value, :bool, :pointer, :pointer], :int32
|
|
227
227
|
)
|
|
228
228
|
@hegel_string_generator_email_fn = bind("hegel_string_generator_email", [:pointer, :pointer], :int32)
|
|
229
229
|
@hegel_string_generator_url_fn = bind("hegel_string_generator_url", [:pointer, :pointer], :int32)
|
|
@@ -273,18 +273,18 @@ module Hegel
|
|
|
273
273
|
# before returning, since the header documents that buffer as
|
|
274
274
|
# borrowed and invalidated by the next call taking the same context.
|
|
275
275
|
def context_last_error(ctx)
|
|
276
|
-
|
|
276
|
+
read_string(@hegel_context_last_error_fn.call(ctx))
|
|
277
277
|
end
|
|
278
278
|
|
|
279
279
|
# Returns the loaded engine's version string, or raises the
|
|
280
280
|
# exception LibHegel.check! translates this call's result code to.
|
|
281
281
|
def version(ctx)
|
|
282
|
-
out =
|
|
282
|
+
out = StringStruct.new
|
|
283
283
|
code = @hegel_version_fn.call(ctx, out)
|
|
284
284
|
# This call takes only ctx and this method's own out argument; neither
|
|
285
285
|
# makes the real engine return anything but HEGEL_OK.
|
|
286
286
|
LibHegel.check!(self, ctx, code) # mutineer:disable-line statement_removal
|
|
287
|
-
|
|
287
|
+
read_string(out)
|
|
288
288
|
end
|
|
289
289
|
|
|
290
290
|
# Returns a settings handle initialized from the engine's resolved
|
|
@@ -334,11 +334,11 @@ module Hegel
|
|
|
334
334
|
end
|
|
335
335
|
|
|
336
336
|
# +database+ may be nil (libhegel's own default path) or a String,
|
|
337
|
-
# including "" to disable the database.
|
|
338
|
-
#
|
|
339
|
-
# to
|
|
337
|
+
# including "" to disable the database. The header takes an optional
|
|
338
|
+
# string here, so nil goes over as a NULL pointer and "" as a pointer
|
|
339
|
+
# to an empty string; see #optional_string.
|
|
340
340
|
def settings_set_database(ctx, s, database)
|
|
341
|
-
code = @hegel_settings_set_database_fn.call(ctx, s, database)
|
|
341
|
+
code = @hegel_settings_set_database_fn.call(ctx, s, optional_string(database))
|
|
342
342
|
LibHegel.check!(self, ctx, code)
|
|
343
343
|
nil
|
|
344
344
|
end
|
|
@@ -350,10 +350,10 @@ module Hegel
|
|
|
350
350
|
end
|
|
351
351
|
|
|
352
352
|
# +key+ may be nil, which the header documents as clearing the key
|
|
353
|
-
# (the default); nil
|
|
353
|
+
# (the default); nil goes over as NULL through #optional_string, the
|
|
354
354
|
# same as #settings_set_database's own nilable database argument.
|
|
355
355
|
def settings_set_database_key(ctx, s, key)
|
|
356
|
-
code = @hegel_settings_set_database_key_fn.call(ctx, s, key)
|
|
356
|
+
code = @hegel_settings_set_database_key_fn.call(ctx, s, optional_string(key))
|
|
357
357
|
LibHegel.check!(self, ctx, code)
|
|
358
358
|
nil
|
|
359
359
|
end
|
|
@@ -393,7 +393,7 @@ module Hegel
|
|
|
393
393
|
# version.
|
|
394
394
|
def run_start_blob(ctx, settings, blob)
|
|
395
395
|
out = FFI::MemoryPointer.new(:pointer)
|
|
396
|
-
code = @hegel_run_start_blob_fn.call(ctx, settings, blob, nil, nil, out)
|
|
396
|
+
code = @hegel_run_start_blob_fn.call(ctx, settings, string_view(blob), nil, nil, out)
|
|
397
397
|
LibHegel.check!(self, ctx, code)
|
|
398
398
|
out.read_pointer
|
|
399
399
|
end
|
|
@@ -442,9 +442,9 @@ module Hegel
|
|
|
442
442
|
# +origin+ must be non-nil only when +status+ is
|
|
443
443
|
# HEGEL_STATUS_INTERESTING, per the header; this layer neither builds
|
|
444
444
|
# nor validates that string, only passes through what the caller
|
|
445
|
-
# supplies. nil
|
|
445
|
+
# supplies. nil goes over as NULL through #optional_string.
|
|
446
446
|
def mark_complete(ctx, tc, status, origin)
|
|
447
|
-
code = @hegel_mark_complete_fn.call(ctx, tc, status, origin)
|
|
447
|
+
code = @hegel_mark_complete_fn.call(ctx, tc, status, optional_string(origin))
|
|
448
448
|
LibHegel.check!(self, ctx, code)
|
|
449
449
|
nil
|
|
450
450
|
end
|
|
@@ -456,7 +456,7 @@ module Hegel
|
|
|
456
456
|
# checked here, matching how this layer leaves every other
|
|
457
457
|
# argument-shape rule to the engine's own HEGEL_E_INVALID_ARG.
|
|
458
458
|
def target(ctx, tc, value, label)
|
|
459
|
-
code = @hegel_target_fn.call(ctx, tc, value, label)
|
|
459
|
+
code = @hegel_target_fn.call(ctx, tc, value, string_view(label))
|
|
460
460
|
LibHegel.check!(self, ctx, code)
|
|
461
461
|
nil
|
|
462
462
|
end
|
|
@@ -538,10 +538,10 @@ module Hegel
|
|
|
538
538
|
# returning, since it is owned by the failure and only valid until
|
|
539
539
|
# #failure_free.
|
|
540
540
|
def failure_origin(ctx, f)
|
|
541
|
-
out =
|
|
541
|
+
out = StringStruct.new
|
|
542
542
|
code = @hegel_failure_origin_fn.call(ctx, f, out)
|
|
543
543
|
LibHegel.check!(self, ctx, code)
|
|
544
|
-
|
|
544
|
+
read_string(out)
|
|
545
545
|
end
|
|
546
546
|
|
|
547
547
|
# Returns nil when libhegel produced no reproduction blob for this
|
|
@@ -659,12 +659,12 @@ module Hegel
|
|
|
659
659
|
end
|
|
660
660
|
|
|
661
661
|
# Tells libhegel the last element +collection+ produced is invalid.
|
|
662
|
-
# +why+ is an optional human-readable reason (nil
|
|
663
|
-
#
|
|
662
|
+
# +why+ is an optional human-readable reason (nil goes over as NULL
|
|
663
|
+
# through #optional_string, which the header allows); the header
|
|
664
664
|
# documents it as validated but reserved for future rejection
|
|
665
665
|
# diagnostics, unused today.
|
|
666
666
|
def collection_reject(ctx, tc, collection, why = nil)
|
|
667
|
-
code = @hegel_collection_reject_fn.call(ctx, tc, collection, why)
|
|
667
|
+
code = @hegel_collection_reject_fn.call(ctx, tc, collection, optional_string(why))
|
|
668
668
|
LibHegel.check!(self, ctx, code)
|
|
669
669
|
nil
|
|
670
670
|
end
|
|
@@ -733,9 +733,9 @@ module Hegel
|
|
|
733
733
|
|
|
734
734
|
# Returns a caller-owned state-machine handle, released separately
|
|
735
735
|
# with #state_machine_free. +rule_names+ and +invariant_names+ are
|
|
736
|
-
# each an Array of Ruby Strings, packed into the
|
|
737
|
-
#
|
|
738
|
-
#
|
|
736
|
+
# each an Array of Ruby Strings, packed into the hegel_string_t arrays
|
|
737
|
+
# hegel_new_state_machine expects by #pack_name_array; see that
|
|
738
|
+
# method's own comment for how and why. +always_check+ holds one
|
|
739
739
|
# flag per invariant name. Validating +rule_names+ as non-empty (the
|
|
740
740
|
# header's own requirement) is left to the caller, the same division
|
|
741
741
|
# of labor #new_collection leaves to the caller for its own
|
|
@@ -840,17 +840,18 @@ module Hegel
|
|
|
840
840
|
|
|
841
841
|
# Returns a caller-owned string generator handle, released
|
|
842
842
|
# separately with #string_generator_free (or scoped with
|
|
843
|
-
# Hegel::TestCase#with_text_generator). +categories
|
|
844
|
-
# +exclude_categories
|
|
845
|
-
# +
|
|
846
|
-
# only wires up the codepoint-range constraints of the
|
|
847
|
-
# bind; the category and explicit-character filter
|
|
848
|
-
# later generator's scope, layered on top of this
|
|
843
|
+
# Hegel::TestCase#with_text_generator). +categories+ and
|
|
844
|
+
# +exclude_categories+ are always passed as NULL/0 below, and
|
|
845
|
+
# +include_characters+ and +exclude_characters+ as empty strings:
|
|
846
|
+
# this call only wires up the codepoint-range constraints of the
|
|
847
|
+
# 12-argument bind; the category and explicit-character filter
|
|
848
|
+
# arguments are a later generator's scope, layered on top of this
|
|
849
|
+
# same bind.
|
|
849
850
|
def string_generator_text(ctx, min_size:, max_size:, codec: nil, min_codepoint: 0, max_codepoint: 0xFFFFFFFF)
|
|
850
851
|
out = FFI::MemoryPointer.new(:pointer)
|
|
851
852
|
code = @hegel_string_generator_text_fn.call(
|
|
852
|
-
ctx, min_size, max_size, codec, min_codepoint, max_codepoint,
|
|
853
|
-
nil, 0, nil, 0,
|
|
853
|
+
ctx, min_size, max_size, optional_string(codec), min_codepoint, max_codepoint,
|
|
854
|
+
nil, 0, nil, 0, StringStruct.new, StringStruct.new,
|
|
854
855
|
out
|
|
855
856
|
)
|
|
856
857
|
LibHegel.check!(self, ctx, code)
|
|
@@ -867,47 +868,46 @@ module Hegel
|
|
|
867
868
|
nil
|
|
868
869
|
end
|
|
869
870
|
|
|
870
|
-
# Returns a drawn String,
|
|
871
|
-
#
|
|
872
|
-
#
|
|
873
|
-
# NUL-terminated and possibly containing interior NUL bytes, since
|
|
874
|
-
# the drawn alphabet can include U+0000.
|
|
871
|
+
# Returns a drawn String, labelled UTF-8 by #read_string. The draw can
|
|
872
|
+
# hold interior NUL bytes, since the drawn alphabet can include
|
|
873
|
+
# U+0000, so #read_string copies exactly the returned +len+ bytes.
|
|
875
874
|
#
|
|
876
875
|
# The native buffer is released in +ensure+ via
|
|
877
|
-
# #
|
|
876
|
+
# #owned_string_free, called directly from here rather
|
|
878
877
|
# than left to the caller: nothing outside this file may hold or
|
|
879
878
|
# read the raw struct (ffi is confined to this file), so unlike
|
|
880
879
|
# #new_collection or #string_generator_text, the freeable handle
|
|
881
880
|
# here never leaves this method. Freeing is safe even when the draw
|
|
882
881
|
# above raised: +out+ starts zero-filled (FFI::Struct.new allocates
|
|
883
882
|
# cleared memory) and libhegel only writes into it on success, so
|
|
884
|
-
# the struct #
|
|
883
|
+
# the struct #owned_string_free sees here is always
|
|
885
884
|
# either a completed draw or the all-zero state the header
|
|
886
885
|
# documents as already safe to free.
|
|
887
886
|
def generate_string(ctx, tc, generator)
|
|
888
|
-
out =
|
|
887
|
+
out = StringStruct.new
|
|
889
888
|
code = @hegel_generate_string_fn.call(ctx, tc, generator, out)
|
|
890
889
|
LibHegel.check!(self, ctx, code)
|
|
891
|
-
|
|
890
|
+
read_string(out)
|
|
892
891
|
ensure
|
|
893
|
-
|
|
892
|
+
owned_string_free(ctx, out)
|
|
894
893
|
end
|
|
895
894
|
|
|
896
|
-
# No-op when +result+ is nil, matching
|
|
897
|
-
#
|
|
898
|
-
#
|
|
899
|
-
#
|
|
900
|
-
|
|
895
|
+
# No-op when +result+ is nil, matching hegel_owned_string_free's
|
|
896
|
+
# documented no-op-on-NULL contract (also safe on an already-freed,
|
|
897
|
+
# zeroed struct); not translated, for the same reason as
|
|
898
|
+
# #context_free. Before libhegel 0.46 this call was
|
|
899
|
+
# hegel_generate_string_result_free.
|
|
900
|
+
def owned_string_free(ctx, result)
|
|
901
901
|
# Removing this call only leaks native memory; Ruby has no way to
|
|
902
902
|
# observe the leak.
|
|
903
|
-
@
|
|
903
|
+
@hegel_owned_string_free_fn.call(ctx, result) # mutineer:disable-line statement_removal
|
|
904
904
|
nil
|
|
905
905
|
end
|
|
906
906
|
|
|
907
907
|
# Returns drawn bytes as a String, read via +len+ the same way
|
|
908
908
|
# #generate_string reads its own out-parameter: the header gives no
|
|
909
|
-
# NUL-termination guarantee for this buffer
|
|
910
|
-
#
|
|
909
|
+
# NUL-termination guarantee for this buffer, so the length is what
|
|
910
|
+
# makes the copy exact.
|
|
911
911
|
#
|
|
912
912
|
# Unlike #generate_string, the result is never force-encoded.
|
|
913
913
|
# hegel_generate_bytes_result_t is documented as a byte buffer, not
|
|
@@ -923,7 +923,7 @@ module Hegel
|
|
|
923
923
|
# header documents hegel_generate_bytes_result_free as safe on an
|
|
924
924
|
# already-freed (zeroed) struct too.
|
|
925
925
|
def generate_bytes(ctx, tc, min_size, max_size)
|
|
926
|
-
out =
|
|
926
|
+
out = StringStruct.new
|
|
927
927
|
code = @hegel_generate_bytes_fn.call(ctx, tc, min_size, max_size, out)
|
|
928
928
|
LibHegel.check!(self, ctx, code)
|
|
929
929
|
out[:data].read_bytes(out[:len])
|
|
@@ -953,9 +953,8 @@ module Hegel
|
|
|
953
953
|
# a NUL character in it reaches the engine instead of ending it.
|
|
954
954
|
def string_generator_regex(ctx, pattern, fullmatch, alphabet = nil)
|
|
955
955
|
out = FFI::MemoryPointer.new(:pointer)
|
|
956
|
-
bytes = pattern.encode(Encoding::UTF_8)
|
|
957
956
|
code = @hegel_string_generator_regex_fn.call(
|
|
958
|
-
ctx,
|
|
957
|
+
ctx, string_view(pattern.encode(Encoding::UTF_8)), fullmatch, alphabet, out
|
|
959
958
|
)
|
|
960
959
|
LibHegel.check!(self, ctx, code)
|
|
961
960
|
out.read_pointer
|
|
@@ -1103,8 +1102,8 @@ module Hegel
|
|
|
1103
1102
|
end
|
|
1104
1103
|
|
|
1105
1104
|
# Copies +bytes+ into a freshly allocated buffer, for
|
|
1106
|
-
# #generate_integer_big's min_value/max_value
|
|
1107
|
-
#
|
|
1105
|
+
# #generate_integer_big's min_value/max_value and for #write_string:
|
|
1106
|
+
# input bytes libhegel reads from, the opposite direction of an
|
|
1108
1107
|
# out-parameter buffer (which libhegel writes into, and the caller
|
|
1109
1108
|
# then reads).
|
|
1110
1109
|
def bytes_to_pointer(bytes)
|
|
@@ -1113,21 +1112,50 @@ module Hegel
|
|
|
1113
1112
|
ptr
|
|
1114
1113
|
end
|
|
1115
1114
|
|
|
1116
|
-
#
|
|
1117
|
-
#
|
|
1118
|
-
#
|
|
1119
|
-
#
|
|
1115
|
+
# Writes +string+'s bytes into +struct+, a StringStruct, as a borrowed
|
|
1116
|
+
# hegel_string_t input. The bytes go over with their length and no
|
|
1117
|
+
# terminator, so an interior NUL reaches the engine. An empty string
|
|
1118
|
+
# gets a zero-size buffer: the header accepts any pointer for a
|
|
1119
|
+
# length of zero.
|
|
1120
|
+
#
|
|
1121
|
+
# FFI::Struct keeps a reference to a pointer it stores in a field, so
|
|
1122
|
+
# the copied buffer lives as long as +struct+ does. Measured on ffi
|
|
1123
|
+
# 1.17.4: after a full GC, a WeakRef to a buffer held only by a struct
|
|
1124
|
+
# field is still alive, and one to a buffer with no holder is not. A
|
|
1125
|
+
# caller keeps the struct live for the native call by passing it as an
|
|
1126
|
+
# argument.
|
|
1127
|
+
def write_string(struct, string)
|
|
1128
|
+
struct[:data] = bytes_to_pointer(string)
|
|
1129
|
+
struct[:len] = string.bytesize
|
|
1130
|
+
struct
|
|
1131
|
+
end
|
|
1132
|
+
|
|
1133
|
+
# A hegel_string_t for an ordinary input argument, passed by value.
|
|
1134
|
+
def string_view(string)
|
|
1135
|
+
write_string(StringStruct.new, string)
|
|
1136
|
+
end
|
|
1137
|
+
|
|
1138
|
+
# A pointer to a hegel_string_t for an optional input argument: nil
|
|
1139
|
+
# for an absent value, which marshals to NULL, and a struct
|
|
1140
|
+
# otherwise. The header separates the two cases: NULL means absent,
|
|
1141
|
+
# and a pointer to an empty string means present and empty. "" for
|
|
1142
|
+
# #settings_set_database depends on that difference.
|
|
1143
|
+
def optional_string(string)
|
|
1144
|
+
string && string_view(string)
|
|
1145
|
+
end
|
|
1146
|
+
|
|
1147
|
+
# Packs +names+ (an Array of Ruby Strings) into a contiguous array of
|
|
1148
|
+
# hegel_string_t for #new_state_machine's rule_names/invariant_names
|
|
1149
|
+
# arguments: one StringStruct per name, each written in place over
|
|
1150
|
+
# its own slot of a single buffer.
|
|
1120
1151
|
#
|
|
1121
|
-
# Returns [pointer, kept_alive].
|
|
1122
|
-
#
|
|
1123
|
-
#
|
|
1124
|
-
#
|
|
1125
|
-
#
|
|
1126
|
-
#
|
|
1127
|
-
#
|
|
1128
|
-
# the objects, so the caller keeping +kept_alive+ as a live local for
|
|
1129
|
-
# the length of the call is what keeps the buffers each one owns
|
|
1130
|
-
# from being garbage-collected first.
|
|
1152
|
+
# Returns [pointer, kept_alive]. #write_string copies each name into
|
|
1153
|
+
# a fresh native buffer -- nothing here depends on the original
|
|
1154
|
+
# +names+ Strings staying live -- and each slot's struct holds a
|
|
1155
|
+
# reference to that buffer. The array buffer holds only addresses,
|
|
1156
|
+
# not references, so the caller keeping +kept_alive+ (the slot
|
|
1157
|
+
# structs) as a live local for the length of the call is what keeps
|
|
1158
|
+
# the buffers each one owns from being garbage-collected first.
|
|
1131
1159
|
#
|
|
1132
1160
|
# Empty +names+ maps to [nil, []] -- NULL and (by the caller passing
|
|
1133
1161
|
# names.size, 0) the header's own contract for invariant_names' "no
|
|
@@ -1142,10 +1170,11 @@ module Hegel
|
|
|
1142
1170
|
# hegel_new_state_machine reads zero names either way.
|
|
1143
1171
|
return [nil, []] if names.empty? # mutineer:disable-line statement_removal
|
|
1144
1172
|
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1173
|
+
array = FFI::MemoryPointer.new(StringStruct, names.size)
|
|
1174
|
+
slots = names.each_with_index.map do |name, i|
|
|
1175
|
+
write_string(StringStruct.new(array + (i * StringStruct.size)), name)
|
|
1176
|
+
end
|
|
1177
|
+
[array, slots]
|
|
1149
1178
|
end
|
|
1150
1179
|
|
|
1151
1180
|
# Packs +flags+ (an Array of true/false) into a const bool * buffer,
|
|
@@ -1222,26 +1251,29 @@ module Hegel
|
|
|
1222
1251
|
[struct[:hour], struct[:minute], struct[:second], struct[:nanosecond]]
|
|
1223
1252
|
end
|
|
1224
1253
|
|
|
1225
|
-
# Reads +out+'s const
|
|
1226
|
-
# returns nil if libhegel left it NULL. Shared by
|
|
1227
|
-
# #failure_reproduction_blob, and #failure_caveat,
|
|
1228
|
-
# the header documents as nullable, so both
|
|
1229
|
-
# exercised once between the call sites
|
|
1254
|
+
# Reads +out+'s const hegel_string_t* out-parameter into a Ruby
|
|
1255
|
+
# String, or returns nil if libhegel left it NULL. Shared by
|
|
1256
|
+
# #run_result_error, #failure_reproduction_blob, and #failure_caveat,
|
|
1257
|
+
# the out-parameters the header documents as nullable, so both
|
|
1258
|
+
# branches only need to be exercised once between the call sites
|
|
1259
|
+
# rather than at each one.
|
|
1230
1260
|
def nullable_out_string(out)
|
|
1231
1261
|
ptr = out.read_pointer
|
|
1232
|
-
ptr.null? ? nil :
|
|
1233
|
-
end
|
|
1234
|
-
|
|
1235
|
-
#
|
|
1236
|
-
# The header calls every string libhegel hands back valid
|
|
1237
|
-
# FFI::Pointer#
|
|
1238
|
-
# who matches an error message against a pattern of their
|
|
1239
|
-
# gets Encoding::CompatibilityError rather than an answer,
|
|
1240
|
-
# engine's own messages do carry characters outside ASCII:
|
|
1241
|
-
# FilterTooMuch health check writes an em dash into the sentence
|
|
1242
|
-
# caller is most likely to read.
|
|
1243
|
-
|
|
1244
|
-
|
|
1262
|
+
ptr.null? ? nil : read_string(StringStruct.new(ptr))
|
|
1263
|
+
end
|
|
1264
|
+
|
|
1265
|
+
# Copies the +len+ bytes at +struct+'s data pointer and labels them
|
|
1266
|
+
# UTF-8. The header calls every string libhegel hands back valid
|
|
1267
|
+
# UTF-8, but FFI::Pointer#read_bytes labels what it reads ASCII-8BIT.
|
|
1268
|
+
# A caller who matches an error message against a pattern of their
|
|
1269
|
+
# own then gets Encoding::CompatibilityError rather than an answer,
|
|
1270
|
+
# and the engine's own messages do carry characters outside ASCII:
|
|
1271
|
+
# the FilterTooMuch health check writes an em dash into the sentence
|
|
1272
|
+
# a caller is most likely to read. The length decides the copy, not
|
|
1273
|
+
# the trailing NUL the header also promises, since a string can hold
|
|
1274
|
+
# an interior NUL.
|
|
1275
|
+
def read_string(struct)
|
|
1276
|
+
struct[:data].read_bytes(struct[:len]).force_encoding(Encoding::UTF_8)
|
|
1245
1277
|
end
|
|
1246
1278
|
end
|
|
1247
1279
|
end
|
data/lib/hegel/lib_hegel.rb
CHANGED
|
@@ -18,7 +18,7 @@ module Hegel
|
|
|
18
18
|
# is testable without the native engine. hegel-java makes the same split,
|
|
19
19
|
# between its Libhegel interface, RealLibhegel, and FakeLibhegel.
|
|
20
20
|
module LibHegel
|
|
21
|
-
# hegel_result_t, named from
|
|
21
|
+
# hegel_result_t, named from libhegel/include/hegel.h's enum of the same
|
|
22
22
|
# name. HEGEL_OK is success; every other value is a negative error code.
|
|
23
23
|
HEGEL_OK = 0
|
|
24
24
|
HEGEL_E_STOP_TEST = -1
|
|
@@ -118,7 +118,7 @@ module Hegel
|
|
|
118
118
|
start_span stop_span
|
|
119
119
|
new_collection collection_more collection_reject collection_free
|
|
120
120
|
generate_float
|
|
121
|
-
string_generator_text string_generator_free generate_string
|
|
121
|
+
string_generator_text string_generator_free generate_string owned_string_free
|
|
122
122
|
generate_bytes generate_bytes_result_free
|
|
123
123
|
string_generator_regex string_generator_email string_generator_url string_generator_domain
|
|
124
124
|
generate_ipv4 generate_ipv6 generate_uuid
|
|
Binary file
|
|
@@ -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.
|
|
8
|
+
LIBHEGEL_VERSION = "0.47.2"
|
|
9
9
|
end
|
data/lib/hegel/locate.rb
CHANGED
|
@@ -16,7 +16,7 @@ module Hegel
|
|
|
16
16
|
# fallback.
|
|
17
17
|
# 2. The copy bundled into the gem under +lib/hegel/libhegel/+.
|
|
18
18
|
#
|
|
19
|
-
# A sibling `../hegel
|
|
19
|
+
# A sibling engine checkout (`../hegel`) is deliberately not searched. The env
|
|
20
20
|
# override already covers local-engine-build workflows without depending
|
|
21
21
|
# on any particular checkout layout, and `rake libhegel:fetch` covers the
|
|
22
22
|
# rest; a rule that depends on a layout this repo's own checkout does not
|
|
@@ -32,10 +32,10 @@ module Hegel
|
|
|
32
32
|
LIBRARY_PATH_ENV = "HEGEL_LIBHEGEL_PATH"
|
|
33
33
|
|
|
34
34
|
# Published release asset name for each supported "<host_cpu>-<host_os>"
|
|
35
|
-
# pair, mirroring the artifacts
|
|
36
|
-
# LIBHEGEL_VERSION. Hosts absent from this table (e.g.
|
|
37
|
-
# Intel Mac) are unsupported because
|
|
38
|
-
# for them.
|
|
35
|
+
# pair, mirroring the artifacts the libhegel release of the pinned
|
|
36
|
+
# LIBHEGEL_VERSION publishes. Hosts absent from this table (e.g.
|
|
37
|
+
# x86_64-darwin, Intel Mac) are unsupported because that release does not
|
|
38
|
+
# publish a build for them.
|
|
39
39
|
ASSET_NAMES = {
|
|
40
40
|
"arm64-darwin" => "libhegel-darwin-arm64.dylib",
|
|
41
41
|
"x86_64-linux" => "libhegel-linux-amd64.so",
|
|
@@ -76,7 +76,7 @@ module Hegel
|
|
|
76
76
|
end
|
|
77
77
|
|
|
78
78
|
# The release asset name for the given host, or raises Hegel::Error if
|
|
79
|
-
#
|
|
79
|
+
# the libhegel release does not publish a build for it.
|
|
80
80
|
def asset_name(host_cpu:, host_os:)
|
|
81
81
|
release_asset_name(host_cpu, host_os) || raise(Hegel::Error, unsupported_host_message(host_cpu, host_os))
|
|
82
82
|
end
|
|
@@ -101,7 +101,7 @@ module Hegel
|
|
|
101
101
|
# directory override is how an unsupported host (e.g. x86_64-darwin)
|
|
102
102
|
# points at a local build in the first place, so treating it as an
|
|
103
103
|
# error here would close off that path.
|
|
104
|
-
# 2. libhegel_c.<ext>, the name cargo gives the library when
|
|
104
|
+
# 2. libhegel_c.<ext>, the name cargo gives the library when libhegel's
|
|
105
105
|
# crate (`[lib] name = "hegel_c"`) is built without renaming it.
|
|
106
106
|
# 3. libhegel.<ext>, a renamed copy, per the hegel-go and hegel-ocaml
|
|
107
107
|
# READMEs.
|
|
@@ -122,8 +122,8 @@ module Hegel
|
|
|
122
122
|
[release_asset_name(host_cpu, host_os), "libhegel_c.#{ext}", "libhegel.#{ext}"].compact
|
|
123
123
|
end
|
|
124
124
|
|
|
125
|
-
# The release asset name for the given host, or nil if
|
|
126
|
-
# not publish a build for it. Split from asset_name (which raises) so a
|
|
125
|
+
# The release asset name for the given host, or nil if the libhegel
|
|
126
|
+
# release does not publish a build for it. Split from asset_name (which raises) so a
|
|
127
127
|
# directory override can treat an unsupported host as "skip this
|
|
128
128
|
# candidate" instead of an error.
|
|
129
129
|
def release_asset_name(host_cpu, host_os)
|
|
@@ -159,7 +159,7 @@ module Hegel
|
|
|
159
159
|
end
|
|
160
160
|
|
|
161
161
|
# Shared-library extension for host_os alone, independent of whether the
|
|
162
|
-
# specific host_cpu is one
|
|
162
|
+
# specific host_cpu is one libhegel publishes a build for. This lets
|
|
163
163
|
# an explicit directory override (step 1) resolve on an otherwise
|
|
164
164
|
# unsupported host (e.g. x86_64-darwin), matching a local libhegel build
|
|
165
165
|
# that always produces the OS-native extension.
|
data/lib/hegel/runner.rb
CHANGED
|
@@ -178,9 +178,9 @@ module Hegel
|
|
|
178
178
|
# Pulls test cases from +run+ until hegel_next_test_case reports none
|
|
179
179
|
# left (a nil out-parameter, not an error). Never counts iterations
|
|
180
180
|
# itself: test_cases bounds generation, not how many times shrinking
|
|
181
|
-
# and rejected cases call the body. Measured against libhegel 0.
|
|
181
|
+
# and rejected cases call the body. Measured against libhegel 0.47.1, a
|
|
182
182
|
# run configured for 20 test cases whose body rejected nearly every case
|
|
183
|
-
# took
|
|
183
|
+
# took 811 to 1847 iterations. +stats+ does its own, different counting
|
|
184
184
|
# -- see GenerationStats above.
|
|
185
185
|
def drive(impl, ctx, run, stats, captures, &block)
|
|
186
186
|
loop do
|
data/lib/hegel/version.rb
CHANGED
data/lib/tasks/libhegel.rake
CHANGED
|
@@ -13,16 +13,28 @@ require_relative "../hegel/libhegel_version"
|
|
|
13
13
|
# needs is fetched here ahead of time, not at resolution time.
|
|
14
14
|
module Hegel
|
|
15
15
|
module LibhegelFetch
|
|
16
|
-
|
|
16
|
+
# libhegel 0.47.2 is the first engine release from the hegeldev/hegel
|
|
17
|
+
# monorepo, tagged libhegel/<version>. hegeldev/hegel-rust holds the
|
|
18
|
+
# releases up to 0.47.1, tagged libhegel-v<version>, and gets no new
|
|
19
|
+
# release. hegeldev/hegel does not carry the assets of those older
|
|
20
|
+
# releases (measured on 2026-10-19: HTTP 404 for libhegel/0.47.1/hegel.h),
|
|
21
|
+
# so the version selects the repository.
|
|
22
|
+
MONOREPO_RELEASE_BASE = "https://github.com/hegeldev/hegel/releases/download"
|
|
23
|
+
MONOREPO_FIRST_VERSION = Gem::Version.new("0.47.2")
|
|
24
|
+
LEGACY_RELEASE_BASE = "https://github.com/hegeldev/hegel-rust/releases/download"
|
|
17
25
|
DEFAULT_ROOT = File.expand_path("../../tmp/libhegel", __dir__)
|
|
18
26
|
|
|
19
27
|
module_function
|
|
20
28
|
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
29
|
+
# In hegeldev/hegel-rust, the plain v<version> tags belong to the
|
|
30
|
+
# hegeltest crate, whose version numbers differ from the engine's. In
|
|
31
|
+
# hegeldev/hegel, the frontends/rust/<version> tags have the same role.
|
|
24
32
|
def release_base(version)
|
|
25
|
-
|
|
33
|
+
if Gem::Version.new(version) >= MONOREPO_FIRST_VERSION
|
|
34
|
+
"#{MONOREPO_RELEASE_BASE}/libhegel/#{version}"
|
|
35
|
+
else
|
|
36
|
+
"#{LEGACY_RELEASE_BASE}/libhegel-v#{version}"
|
|
37
|
+
end
|
|
26
38
|
end
|
|
27
39
|
|
|
28
40
|
# Fetches the host's pinned asset into <root>/<version>/<asset>,
|
|
@@ -7,8 +7,8 @@ require_relative "../hegel/locate"
|
|
|
7
7
|
require_relative "../hegel/libhegel_version"
|
|
8
8
|
require_relative "../hegel/errors"
|
|
9
9
|
|
|
10
|
-
# Packages this gem for the five platforms
|
|
11
|
-
#
|
|
10
|
+
# Packages this gem for the five platforms libhegel publishes a build
|
|
11
|
+
# for (see Hegel::Locate::ASSET_NAMES), on top of the platform-
|
|
12
12
|
# independent "ruby" gem `rake build` already produces. A platform gem
|
|
13
13
|
# differs from that ruby gem in exactly two ways: spec.platform names the
|
|
14
14
|
# platform, and lib/hegel/libhegel/ (Hegel::Locate::GEM_LIBHEGEL_DIR) carries
|
|
@@ -103,7 +103,7 @@ module Hegel
|
|
|
103
103
|
end
|
|
104
104
|
|
|
105
105
|
namespace :platform_gems do
|
|
106
|
-
desc "Build the hegeltest gem for every platform
|
|
106
|
+
desc "Build the hegeltest gem for every platform libhegel publishes a build for, " \
|
|
107
107
|
"from assets already fetched by `rake libhegel:fetch_all`"
|
|
108
108
|
task :build do
|
|
109
109
|
Hegel::PlatformGems.build_all.each { |path| puts "built: #{path}" }
|
data/sig/hegel.rbs
CHANGED
|
@@ -544,7 +544,7 @@ module Hegel
|
|
|
544
544
|
def string_generator_text: (untyped ctx, min_size: Integer, max_size: Integer, ?codec: String?, ?min_codepoint: Integer, ?max_codepoint: Integer) -> untyped
|
|
545
545
|
def string_generator_free: (untyped ctx, untyped generator) -> nil
|
|
546
546
|
def generate_string: (untyped ctx, untyped tc, untyped generator) -> String
|
|
547
|
-
def
|
|
547
|
+
def owned_string_free: (untyped ctx, untyped result) -> nil
|
|
548
548
|
def generate_bytes: (untyped ctx, untyped tc, Integer min_size, Integer max_size) -> String
|
|
549
549
|
def generate_bytes_result_free: (untyped ctx, untyped result) -> nil
|
|
550
550
|
def string_generator_regex: (untyped ctx, String pattern, bool fullmatch, ?untyped alphabet) -> untyped
|
|
@@ -494,12 +494,12 @@ expose) are not available in this binding; `codec:`, `min_codepoint:`, and
|
|
|
494
494
|
`max_codepoint:` are the alphabet controls it exposes today.
|
|
495
495
|
|
|
496
496
|
Other generators cover most character-set jobs. These results were measured
|
|
497
|
-
with libhegel 0.
|
|
497
|
+
with libhegel 0.47.1:
|
|
498
498
|
|
|
499
499
|
| Job | Generator | Measured output |
|
|
500
500
|
|---|---|---|
|
|
501
|
-
| Characters to include | `from_regex("[ぁ-んA-Z0-9]{1,5}", fullmatch: true)` | `"0"`, `"
|
|
502
|
-
| Characters to exclude | `from_regex("[^,\\t\\n]{0,6}", fullmatch: true)` | `""`, `"
|
|
501
|
+
| Characters to include | `from_regex("[ぁ-んA-Z0-9]{1,5}", fullmatch: true)` | `"0"`, `"D"`, `"Yもで"`; 30 draws all matched the pattern |
|
|
502
|
+
| Characters to exclude | `from_regex("[^,\\t\\n]{0,6}", fullmatch: true)` | `""`, `"RÈbÁ¡"`; 30 draws contained no comma, tab, or newline |
|
|
503
503
|
| A contiguous block | `text(min_codepoint: 0x300, max_codepoint: 0x36F)` | Every character of 100 draws was in U+0300 through U+036F |
|
|
504
504
|
| An arbitrary alphabet | `arrays(sampled_from(%w[ß a İ]), max_size: 4).map(&:join)` | 30 draws, each built only from `ß`, `a`, and `İ` |
|
|
505
505
|
|
|
@@ -512,14 +512,13 @@ inputs and reported this health check:
|
|
|
512
512
|
|
|
513
513
|
```
|
|
514
514
|
FailedHealthCheck: FilterTooMuch — it looks like this test is filtering out
|
|
515
|
-
too many inputs. 50 inputs were
|
|
516
|
-
inputs were generated. If this is expected, suppress the
|
|
517
|
-
suppress_health_check
|
|
515
|
+
too many inputs. 50 inputs were rejected by assume() or a filter while only
|
|
516
|
+
0 valid inputs were generated. If this is expected, suppress the
|
|
517
|
+
FilterTooMuch health check with the suppress_health_check setting.
|
|
518
518
|
```
|
|
519
519
|
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
[Settings](#settings).
|
|
520
|
+
This binding takes that setting as
|
|
521
|
+
`suppress_health_check: [:filter_too_much]`, listed in [Settings](#settings).
|
|
523
522
|
|
|
524
523
|
Unconfigured `text` draws from a much wider Unicode range. In 200 draws of
|
|
525
524
|
`text(min_size: 1, max_size: 4)`, the results included emoji, private-use
|
|
@@ -745,6 +744,13 @@ translated): `from_regex(/[a-z]{3}/)` raises `Hegel::Error` at draw time,
|
|
|
745
744
|
Regexp"`. Pass `my_regexp.source` explicitly for a pattern confirmed to
|
|
746
745
|
need no flags and to use only syntax the two grammars share.
|
|
747
746
|
|
|
747
|
+
A pattern that no string matches, such as `"(?!)"`, also raises
|
|
748
|
+
`Hegel::Error` at draw time. The engine writes the message, and it names
|
|
749
|
+
the pattern: `from_regex("(?!)")` raises
|
|
750
|
+
`"HEGEL_E_INVALID_ARG (-5): regex pattern \"(?!)\" matches no string the
|
|
751
|
+
generator can produce"`. Since libhegel 0.45.7 the engine makes this
|
|
752
|
+
check.
|
|
753
|
+
|
|
748
754
|
### `emails`
|
|
749
755
|
|
|
750
756
|
An RFC 5321/5322 email address `String`.
|
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.3.
|
|
4
|
+
version: 0.3.4
|
|
5
5
|
platform: aarch64-linux
|
|
6
6
|
authors:
|
|
7
7
|
- meganemura
|
|
@@ -70,6 +70,8 @@ files:
|
|
|
70
70
|
- docs/adr/0022-keep-microsecond-times-over-a-nanosecond-engine.md
|
|
71
71
|
- docs/adr/0023-leave-unset-settings-to-the-engines-profile.md
|
|
72
72
|
- docs/adr/0024-release-when-a-merge-changes-the-gem-version.md
|
|
73
|
+
- docs/adr/0025-copy-each-string-input-into-a-hegel-string-t.md
|
|
74
|
+
- docs/adr/0026-select-the-engine-release-repository-by-version.md
|
|
73
75
|
- docs/architecture.md
|
|
74
76
|
- lib/hegel.rb
|
|
75
77
|
- lib/hegel/draw_name.rb
|