hegeltest 0.3.1-x86_64-linux → 0.4.0-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 +4 -4
- data/CHANGELOG.md +262 -0
- data/README.ja.md +408 -0
- data/README.md +209 -103
- data/Rakefile +6 -3
- data/docs/README.md +11 -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/adr/0027-report-each-runs-test-location-from-the-callers-frame.md +63 -0
- data/docs/adr/0028-expose-settings-profiles-as-a-keyword-and-module-functions.md +91 -0
- data/docs/adr/0029-keep-notes-local-and-defer-run-error-kinds.md +72 -0
- data/docs/adr/0030-represent-a-duration-as-integer-nanoseconds.md +48 -0
- data/docs/adr/0031-take-the-alphabet-options-from-hegel-rust.md +52 -0
- data/docs/adr/0032-suppress-span-closes-while-an-abandoned-recursion-attempt-unwinds.md +147 -0
- data/docs/adr/0033-cache-compiled-grammars-for-the-length-of-a-run.md +80 -0
- data/docs/adr/0034-repeat-a-test-body-with-a-ruby-side-indent.md +120 -0
- data/docs/adr/0035-defer-concurrent-state-machines-and-the-engine-printer.md +136 -0
- data/docs/architecture.md +33 -11
- data/lib/hegel/errors.rb +24 -0
- data/lib/hegel/generator.rb +52 -25
- data/lib/hegel/generators.rb +715 -45
- data/lib/hegel/grammar_cache.rb +36 -0
- data/lib/hegel/lib_hegel/real.rb +455 -120
- data/lib/hegel/lib_hegel.rb +33 -8
- data/lib/hegel/libhegel/libhegel-linux-amd64.so +0 -0
- data/lib/hegel/libhegel_version.rb +1 -1
- data/lib/hegel/locate.rb +10 -10
- data/lib/hegel/report.rb +13 -10
- data/lib/hegel/runner.rb +61 -27
- data/lib/hegel/settings.rb +92 -16
- data/lib/hegel/state_machine.rb +17 -4
- data/lib/hegel/stateful/pool.rb +52 -0
- data/lib/hegel/stateful.rb +2 -2
- data/lib/hegel/syntax/methods.rb +99 -15
- data/lib/hegel/test_case.rb +256 -35
- data/lib/hegel/test_location.rb +49 -0
- data/lib/hegel/version.rb +1 -1
- data/lib/hegel.rb +21 -5
- data/lib/tasks/libhegel.rake +17 -5
- data/lib/tasks/platform_gems.rake +3 -3
- data/sig/hegel.rbs +296 -43
- data/skills/hegel-ruby/references/ruby/reference.md +465 -43
- metadata +15 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 341eaae31f3446fb2282f5c0c1beca4d4589694894e3a28f69b4c76c0262f1dc
|
|
4
|
+
data.tar.gz: c384924a4c727e53820ba86169130758c06ab53ee6d3efd04b5b882294f13a53
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b55a89f13cb10ae1804daca37bf9e18e95a887acc609f11882859bd44a72c687d948994c7e79b05601b0594f688a62f41dc6e3e23f3ea6c8a4b9a6662c7eac57
|
|
7
|
+
data.tar.gz: d42ef433b5675fc0d21f9ae17f336445e65421352042b9162df886d95aded68aae70304f405e246c4560d8675c60554af5eaa929fabc8aec1c9614123a16137b
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,264 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.4.0] - 2026-10-21
|
|
6
|
+
|
|
7
|
+
### Engine: libhegel 0.48.0 (was 0.47.2)
|
|
8
|
+
|
|
9
|
+
The [libhegel 0.48.0 release notes](https://github.com/hegeldev/hegel/releases/tag/libhegel/0.48.0)
|
|
10
|
+
describe 0.48.0.
|
|
11
|
+
|
|
12
|
+
Each platform gem carries `libhegel` 0.48.0. The engine adds one function to
|
|
13
|
+
its C interface, `hegel_set_unicode_version`, and changes no other function.
|
|
14
|
+
These bindings do not call it yet, so the string generators continue to use
|
|
15
|
+
the newest Unicode tables of the engine, as before. A caller has nothing to
|
|
16
|
+
change in code. The engine move changes these results for a caller:
|
|
17
|
+
|
|
18
|
+
- With the default `report_multiple_failures: false`, a run can find a
|
|
19
|
+
different, smaller failure while it shrinks the first one. The run now
|
|
20
|
+
shrinks that failure too before it reports it. In 0.47.2, the run reported
|
|
21
|
+
that failure without shrinking it.
|
|
22
|
+
- `from_regex` accepts the end-of-string anchor `\z`. In 0.47.2, a pattern
|
|
23
|
+
with `\z` raised `Hegel::Error` with `HEGEL_E_INVALID_ARG`.
|
|
24
|
+
- `floats(min_value: Float::INFINITY, allow_infinity: false)` and
|
|
25
|
+
`floats(max_value: -Float::INFINITY, allow_infinity: false)` leave no value
|
|
26
|
+
to draw. The draw now raises `Hegel::Error` with `HEGEL_E_INVALID_ARG` and
|
|
27
|
+
a message that contains "leaves no value". In 0.47.2, the draw raised
|
|
28
|
+
`Hegel::Error` with `HEGEL_E_INTERNAL`.
|
|
29
|
+
|
|
30
|
+
### Profiles
|
|
31
|
+
|
|
32
|
+
`Hegel.test` takes `profile:`, the name of a settings profile to start the
|
|
33
|
+
run from: a shipped one, one in `hegel.toml`, or one you register. Left
|
|
34
|
+
`nil`, the run starts from the default profile as before, and a keyword
|
|
35
|
+
passed alongside still beats the profile.
|
|
36
|
+
|
|
37
|
+
`Hegel::Settings.register_profile(name, profile: nil, **keywords)`
|
|
38
|
+
registers a profile for the whole process, and
|
|
39
|
+
`Hegel::Settings.set_default_profile(name)` makes a profile the default
|
|
40
|
+
for every run; `""` clears it. They follow hegel-rust's
|
|
41
|
+
`Settings::register_profile` and `set_default_profile`. An unknown or
|
|
42
|
+
reserved name raises `Hegel::Error` with the engine's message. See
|
|
43
|
+
[ADR 0028](docs/adr/0028-expose-settings-profiles-as-a-keyword-and-module-functions.md).
|
|
44
|
+
|
|
45
|
+
### Settings
|
|
46
|
+
|
|
47
|
+
`Hegel.test` takes five more keywords, with the names and meanings of
|
|
48
|
+
hegel-rust's `Settings` methods. Each one left `nil` takes its value from
|
|
49
|
+
the engine's settings profile, like `test_cases:` and `seed:`.
|
|
50
|
+
|
|
51
|
+
- `stop_on_first_failure: false` keeps generating after a failure and
|
|
52
|
+
spends the whole `test_cases` budget before reporting.
|
|
53
|
+
- `print_blob: false` leaves the `reproduce_failure:` line out of the
|
|
54
|
+
failure report. Left `nil`, a profile or `HEGEL_PRINT_BLOB` decides.
|
|
55
|
+
- `show_statistics: true` makes the engine print, at the end of the run,
|
|
56
|
+
what `tc.event` and `tc.event_value` recorded.
|
|
57
|
+
- `nondeterminism_strictness:` takes `:quiet`, `:warn`, or `:error`.
|
|
58
|
+
`:error` ends a run whose test behaves differently on a replay with
|
|
59
|
+
`Hegel::Error`.
|
|
60
|
+
- `backend:` takes `:default` or `:urandom`.
|
|
61
|
+
|
|
62
|
+
### Test location
|
|
63
|
+
|
|
64
|
+
Every `Hegel.test` run now reports where its test lives to libhegel, with
|
|
65
|
+
`hegel_settings_set_test_location`. Under Antithesis the engine writes one
|
|
66
|
+
`always` assertion, "<class>::<function> passes properties", for each
|
|
67
|
+
location; elsewhere it uses nothing, and no keyword changes. The file is
|
|
68
|
+
relative to the working directory when it lies under it, and the class and
|
|
69
|
+
function come from the frame that called `Hegel.test`. A call inside a
|
|
70
|
+
block takes the name of the method holding the block, and a call outside
|
|
71
|
+
any method, such as an RSpec example, takes the file without `.rb` as the
|
|
72
|
+
class and `line <n>` as the function, so each example in a file has its
|
|
73
|
+
own name.
|
|
74
|
+
|
|
75
|
+
### Test cases
|
|
76
|
+
|
|
77
|
+
`tc.event(label)` and `tc.event_value(label, value)` record what a test
|
|
78
|
+
reached, for the statistics that `show_statistics: true` prints. They follow
|
|
79
|
+
hegel-rust's `TestCase#event` and `TestCase#event_value`.
|
|
80
|
+
|
|
81
|
+
### Generators
|
|
82
|
+
|
|
83
|
+
- `date_strings`, `time_strings`, and `datetime_strings` generate ISO 8601
|
|
84
|
+
date, time, and datetime Strings, with no options, as hegel-rust does.
|
|
85
|
+
A time keeps nanoseconds: nine fractional digits appear when the
|
|
86
|
+
nanosecond is not zero.
|
|
87
|
+
- `permutations`, `subsequences`, and `samples` draw reorderings, ordered
|
|
88
|
+
subsets, and samples of a given sequence, as hegel-rust does. Its
|
|
89
|
+
`with_replacement` and `without_replacement` setters are one keyword here,
|
|
90
|
+
`samples(replacement: true)`, which is the default.
|
|
91
|
+
- `durations(min_value: 0, max_value: 2**64 - 1)` draws a duration as an
|
|
92
|
+
`Integer` count of nanoseconds, as hegel-rust's `durations` does for a
|
|
93
|
+
`Duration`. It follows [ADR 0030](docs/adr/0030-represent-a-duration-as-integer-nanoseconds.md).
|
|
94
|
+
- `arrays(elements, unique: true)` draws an array with no two equal entries,
|
|
95
|
+
as hegel-rust's `vecs(...).unique(true)` does. An element equal to one
|
|
96
|
+
already drawn is rejected and drawn again, so `min_size` still counts
|
|
97
|
+
distinct entries.
|
|
98
|
+
- `floats(allow_subnormal: nil)` allows or rules out subnormal values, as
|
|
99
|
+
hegel-rust's `allow_subnormal` does. Left `nil`, it allows them whenever
|
|
100
|
+
the bounds admit one. A `floats` call whose bounds admit none, such as
|
|
101
|
+
`floats(min_value: 1.0, max_value: 2.0)`, now tells the engine so.
|
|
102
|
+
- `recursive(leaf, max_depth: 32, max_leaves: 100) { |subtrees| ... }`
|
|
103
|
+
draws a recursively defined value, such as a tree, through the engine's
|
|
104
|
+
recursion calls, as hegel-rust's `recursive` does. The engine decides each
|
|
105
|
+
leaf-or-branch choice and owns both budgets. An attempt that outgrows
|
|
106
|
+
`max_leaves` is drawn again from the root, and the spans it opened are
|
|
107
|
+
discarded, not kept. See
|
|
108
|
+
[ADR 0032](docs/adr/0032-suppress-span-closes-while-an-abandoned-recursion-attempt-unwinds.md).
|
|
109
|
+
|
|
110
|
+
### Text generators
|
|
111
|
+
|
|
112
|
+
`text` and `characters` take `categories:`, `exclude_categories:`,
|
|
113
|
+
`include_characters:`, and `exclude_characters:`, with the names hegel-rust
|
|
114
|
+
gives them. `text` also takes `alphabet:`, a `String` of the permitted
|
|
115
|
+
characters, and raises `Hegel::Error` at draw
|
|
116
|
+
time when it is combined with another character option. `from_regex` takes
|
|
117
|
+
`alphabet:`, a `characters` generator that limits every character of the
|
|
118
|
+
string, padding included. An unknown `codec:` now raises `"text: invalid
|
|
119
|
+
codec: ..."` (or `characters:`) before the engine is called, and a
|
|
120
|
+
surrogate category in `categories:` raises too.
|
|
121
|
+
|
|
122
|
+
### Grammar strings
|
|
123
|
+
|
|
124
|
+
`from_lark(grammar)` draws strings that a grammar written in the syntax of
|
|
125
|
+
the [Lark](https://lark-parser.readthedocs.io/en/stable/grammar.html)
|
|
126
|
+
parsing library accepts, as hegel-rust's `from_lark` does. It takes
|
|
127
|
+
`start:`, `explicit:` (a `Hash` from terminal name to a generator of the
|
|
128
|
+
values drawn for it), `alphabet:` (a `characters` generator),
|
|
129
|
+
`max_size:`, `max_depth:`, and `import_path:`. An invalid option raises
|
|
130
|
+
`Hegel::Error` at draw time, with hegel-rust's message where it has one.
|
|
131
|
+
A grammar is compiled on its first draw in a run and freed when the run
|
|
132
|
+
ends.
|
|
133
|
+
|
|
134
|
+
### Added
|
|
135
|
+
|
|
136
|
+
- `rule(name, weight: 1.0)` sets how often the engine picks a rule relative to
|
|
137
|
+
the machine's other rules, as `#[rule(weight = w)]` does in hegel-rust. A
|
|
138
|
+
weight that is not finite and positive raises `Hegel::Error` when the machine
|
|
139
|
+
runs. `Hegel::StateMachine.rule_definitions` now maps each name to a
|
|
140
|
+
`Hegel::StateMachine::Rule` with `block` and `weight`, where it mapped to the
|
|
141
|
+
block alone.
|
|
142
|
+
|
|
143
|
+
### Fixed
|
|
144
|
+
|
|
145
|
+
- A filter on `Pool#values_consumed` no longer loses the values it rejects. They
|
|
146
|
+
go back into the pool through `hegel_pool_restore` once the draw is over, and
|
|
147
|
+
only the accepted value is consumed, as in hegel-rust.
|
|
148
|
+
|
|
149
|
+
### Changed
|
|
150
|
+
|
|
151
|
+
Three defaults now match hegel-rust, which defines the interface. The results
|
|
152
|
+
change for a caller who relied on the old default.
|
|
153
|
+
|
|
154
|
+
- `from_regex` defaults to `fullmatch: true`, where it was `false`. The whole
|
|
155
|
+
drawn string matches the pattern, instead of merely containing a match. To
|
|
156
|
+
keep the old behaviour, pass `fullmatch: false`.
|
|
157
|
+
- `text` defaults `max_size` to 100, where it was unbounded. When `min_size`
|
|
158
|
+
is above 100, the default is `min_size + 100`. Strings drawn without a
|
|
159
|
+
`max_size` are shorter than before. To draw longer strings, pass the
|
|
160
|
+
larger `max_size` you need.
|
|
161
|
+
- `binary` defaults `max_size` the same way, to 100 or `min_size + 100`.
|
|
162
|
+
|
|
163
|
+
- A filter over a filter now spends 3 attempts between the two predicates, as
|
|
164
|
+
hegel-rust does, where each level used to spend 3 of its own.
|
|
165
|
+
- `Hegel::Generator#do_draw_filtered(tc, predicate)` is the draw behind
|
|
166
|
+
`#filter`. A generator whose draws have effects that a rejected value must
|
|
167
|
+
not keep overrides it.
|
|
168
|
+
|
|
169
|
+
### Repeat a test body
|
|
170
|
+
|
|
171
|
+
`tc.repeat { ... }` runs its block again and again, for as long as the
|
|
172
|
+
engine keeps the loop going, the way hegel-rust's `TestCase::repeat` does.
|
|
173
|
+
The engine chooses how many times it runs, and shrinks a failure toward
|
|
174
|
+
fewer iterations. Each iteration shows in the failure report as a
|
|
175
|
+
`// Repetition #N` line, with what the iteration recorded indented under
|
|
176
|
+
it. A `tc.assume` or `tc.reject` inside the block skips that iteration
|
|
177
|
+
instead of discarding the whole test case.
|
|
178
|
+
|
|
179
|
+
The loop does not return when it ends. It raises `Hegel::LoopDone`, which
|
|
180
|
+
`Hegel.test` counts as a passing test case. It descends from `Exception`,
|
|
181
|
+
like the other control exceptions, so a `rescue => e` in the body does not
|
|
182
|
+
catch it. A `break` in the block ends the loop and returns from
|
|
183
|
+
`tc.repeat`. See
|
|
184
|
+
[ADR 0034](docs/adr/0034-repeat-a-test-body-with-a-ruby-side-indent.md).
|
|
185
|
+
|
|
186
|
+
## [0.3.4] - 2026-10-20
|
|
187
|
+
|
|
188
|
+
### Engine: libhegel 0.47.2 (was 0.47.1)
|
|
189
|
+
|
|
190
|
+
The [libhegel 0.47.2 release notes](https://github.com/hegeldev/hegel/releases/tag/libhegel/0.47.2)
|
|
191
|
+
describe 0.47.2. It is the first engine release from the hegeldev/hegel
|
|
192
|
+
repository.
|
|
193
|
+
|
|
194
|
+
Each platform gem carries `libhegel` 0.47.2. The engine does not change its
|
|
195
|
+
C interface. A caller has nothing to change in code, and gets the same
|
|
196
|
+
results. The engine changes one text: the message of an internal engine
|
|
197
|
+
error now gives <https://github.com/hegeldev/hegel/issues> as the place to
|
|
198
|
+
report the problem, not the hegel-rust issue tracker.
|
|
199
|
+
|
|
200
|
+
### Development
|
|
201
|
+
|
|
202
|
+
- `rake libhegel:fetch` and `rake libhegel:fetch_all` download libhegel
|
|
203
|
+
0.47.2 and later from the hegeldev/hegel releases, and earlier versions
|
|
204
|
+
from the hegeldev/hegel-rust releases.
|
|
205
|
+
|
|
206
|
+
## [0.3.3] - 2026-10-17
|
|
207
|
+
|
|
208
|
+
### Engine: libhegel 0.47.1 (was 0.45.13)
|
|
209
|
+
|
|
210
|
+
The [libhegel 0.47.1 release notes](https://github.com/hegeldev/hegel-rust/releases/tag/libhegel-v0.47.1)
|
|
211
|
+
describe 0.47.1 alone. The
|
|
212
|
+
[engine changelog](https://github.com/hegeldev/hegel-rust/blob/libhegel-v0.47.1/hegel-c/CHANGELOG.md)
|
|
213
|
+
describes each engine release after 0.45.13, up to 0.47.1.
|
|
214
|
+
|
|
215
|
+
Each platform gem carries `libhegel` 0.47.1. The engine changes its C
|
|
216
|
+
interface: every string is now a `hegel_string_t`, a pointer and a length.
|
|
217
|
+
These bindings follow that change. A caller has nothing to change in code.
|
|
218
|
+
The engine move changes these results for a caller:
|
|
219
|
+
|
|
220
|
+
- A run with the default `report_multiple_failures: false` that finds more
|
|
221
|
+
than one distinct failure shrinks only the smallest of them and reports
|
|
222
|
+
it. Before, the engine shrank each failure and then reported one. The
|
|
223
|
+
example database keeps every distinct failure, as found. Such a run calls
|
|
224
|
+
the test body fewer times: for a body with two distinct failures and
|
|
225
|
+
`test_cases: 100`, 73 to 131 calls over 20 seeded runs, where 0.45.13
|
|
226
|
+
made 157 to 202. Both versions reported the same failure in each run.
|
|
227
|
+
- A string that holds a NUL character, such as a `target` label or a
|
|
228
|
+
`database:` path, reaches the engine whole. Before, it raised
|
|
229
|
+
`ArgumentError` ("string contains null byte") from the `ffi` gem.
|
|
230
|
+
|
|
231
|
+
## [0.3.2] - 2026-10-16
|
|
232
|
+
|
|
233
|
+
### Engine: libhegel 0.45.13 (was 0.45.7)
|
|
234
|
+
|
|
235
|
+
The [libhegel 0.45.13 release notes](https://github.com/hegeldev/hegel-rust/releases/tag/libhegel-v0.45.13)
|
|
236
|
+
describe 0.45.13 alone. The
|
|
237
|
+
[engine changelog](https://github.com/hegeldev/hegel-rust/blob/libhegel-v0.45.13/hegel-c/CHANGELOG.md)
|
|
238
|
+
describes each engine release after 0.45.7, up to 0.45.13.
|
|
239
|
+
|
|
240
|
+
Each platform gem carries `libhegel` 0.45.13. The engine only adds to its C
|
|
241
|
+
interface, and these bindings do not use the addition. A caller has nothing
|
|
242
|
+
to change in code. The engine changes these results for a caller:
|
|
243
|
+
|
|
244
|
+
- A run with a `seed:` can draw different values than it did before. The
|
|
245
|
+
engine changed how it copies values between spans that have the same
|
|
246
|
+
label, and how it draws an integer from a range wider than 128 bits.
|
|
247
|
+
- A failure that depends on an integer that is a multiple of a number, such
|
|
248
|
+
as 6, shrinks to the smallest such value more often. Before, the shrinker
|
|
249
|
+
could stop at a larger multiple, such as 78.
|
|
250
|
+
- A run of 1,000 test cases or more starts to use `target` scores soon
|
|
251
|
+
after it starts. Before, it started after half of the test cases.
|
|
252
|
+
- The `note:` line with the engine's nondeterminism caveat shows only for a
|
|
253
|
+
failure that the engine saw pass at least one time. Before, every failure
|
|
254
|
+
of a run that the engine treated as nondeterministic showed it.
|
|
255
|
+
- `reproduce_failure:` stops a replay when the test body draws differently
|
|
256
|
+
from the recorded choices. If no replay reproduces the failure, the run
|
|
257
|
+
raises `Hegel::Error` with the message that says that the blob did not
|
|
258
|
+
reproduce a failure, and the engine writes one line to standard error
|
|
259
|
+
that names the position where the draws left the recorded choices.
|
|
260
|
+
Before, the engine continued the replay with new draws and could report a
|
|
261
|
+
different failure as the failure of the blob.
|
|
262
|
+
|
|
5
263
|
## [0.3.1] - 2026-10-15
|
|
6
264
|
|
|
7
265
|
### Engine: libhegel 0.45.7 (was 0.45.4)
|
|
@@ -196,6 +454,10 @@ The engine is called through the `ffi` gem. Each platform gem carries the
|
|
|
196
454
|
matching `libhegel` 0.32.5 build. The platform-independent gem carries none,
|
|
197
455
|
so a run on it needs `HEGEL_LIBHEGEL_PATH` pointing at a local build.
|
|
198
456
|
|
|
457
|
+
[0.4.0]: https://github.com/meganemura/hegel-ruby/releases/tag/v0.4.0
|
|
458
|
+
[0.3.4]: https://github.com/meganemura/hegel-ruby/releases/tag/v0.3.4
|
|
459
|
+
[0.3.3]: https://github.com/meganemura/hegel-ruby/releases/tag/v0.3.3
|
|
460
|
+
[0.3.2]: https://github.com/meganemura/hegel-ruby/releases/tag/v0.3.2
|
|
199
461
|
[0.3.1]: https://github.com/meganemura/hegel-ruby/releases/tag/v0.3.1
|
|
200
462
|
[0.3.0]: https://github.com/meganemura/hegel-ruby/releases/tag/v0.3.0
|
|
201
463
|
[0.2.1]: https://github.com/meganemura/hegel-ruby/releases/tag/v0.2.1
|