hegeltest 0.1.0 → 0.2.0
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 +72 -26
- data/Rakefile +81 -0
- data/docs/README.md +10 -0
- data/docs/adr/0014-name-a-drawn-value-only-when-the-draw-is-the-whole-assigned-value.md +74 -0
- data/docs/adr/0015-follow-the-hegeldev-interface-and-take-changes-upstream-first.md +101 -0
- data/docs/adr/0016-run-mutation-testing-with-mutineer.md +87 -0
- data/docs/adr/0017-raise-the-ruby-floor-to-3-4.md +43 -0
- data/docs/adr/0018-gate-mutation-testing-on-a-committed-baseline.md +74 -0
- data/docs/adr/0019-report-failures-from-the-cases-the-engine-stamps.md +70 -0
- data/docs/adr/0020-derive-span-labels-from-generator-names.md +64 -0
- data/docs/adr/0021-run-a-state-machine-in-rounds-with-its-own-step-count.md +59 -0
- data/docs/adr/0022-keep-microsecond-times-over-a-nanosecond-engine.md +45 -0
- data/docs/adr/0023-leave-unset-settings-to-the-engines-profile.md +50 -0
- data/docs/architecture.md +11 -6
- data/lib/hegel/draw_name.rb +24 -8
- data/lib/hegel/generator.rb +50 -9
- data/lib/hegel/generators.rb +134 -102
- data/lib/hegel/lib_hegel/real.rb +193 -94
- data/lib/hegel/lib_hegel.rb +30 -56
- data/lib/hegel/libhegel_version.rb +1 -1
- data/lib/hegel/report.rb +17 -8
- data/lib/hegel/runner.rb +153 -201
- data/lib/hegel/settings.rb +9 -14
- data/lib/hegel/state_machine.rb +15 -8
- data/lib/hegel/stateful/pool.rb +0 -2
- data/lib/hegel/stateful.rb +64 -36
- data/lib/hegel/syntax/methods.rb +3 -2
- data/lib/hegel/test_case.rb +29 -9
- data/lib/hegel/version.rb +1 -1
- data/lib/hegel.rb +11 -17
- data/lib/tasks/libhegel.rake +9 -2
- data/sig/hegel.rbs +60 -61
- data/skills/hegel-ruby/references/ruby/reference.md +147 -52
- metadata +12 -2
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
- [Setup](#setup)
|
|
6
6
|
- [Test Structure](#test-structure): `Hegel.test`, its block, return value, failure behavior
|
|
7
|
-
- [Settings](#settings): `test_cases:` `seed:` `derandomize:` `verbosity:` `database:` `database_key:` `phases:` `suppress_health_check:` `report_multiple_failures:` `
|
|
7
|
+
- [Settings](#settings): `test_cases:` `seed:` `derandomize:` `verbosity:` `database:` `database_key:` `phases:` `suppress_health_check:` `report_multiple_failures:` `output:` `reproduce_failure:`
|
|
8
8
|
- [TestCase Methods](#testcase-methods): `draw`, `draw_integer`, `draw_boolean`, `assume`, `reject`, `note`, `target`
|
|
9
9
|
- [Generator Reference](#generator-reference): `booleans`, `integers`, `floats`, `text`, `arrays`, `just`, `sampled_from`, `one_of`, `optional`, `tuples`, `sets`, `hashes`, `characters`, `binary`, `from_regex`, `emails`, `urls`, `domains`, `ip_addresses`, `uuids`, `dates`, `times`, `datetimes`, `composite`, `deferred`
|
|
10
10
|
- [Stateful Testing](#stateful-testing): `Hegel::StateMachine`, `rule`, `invariant`, `Hegel::Stateful.run`, `Hegel::Stateful::Pool`
|
|
@@ -13,16 +13,18 @@
|
|
|
13
13
|
|
|
14
14
|
## Setup
|
|
15
15
|
|
|
16
|
-
Hegel for Ruby is packaged as the `hegeltest` gem
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
`HEGEL_LIBHEGEL_PATH` pointing at a local build:
|
|
16
|
+
Hegel for Ruby is packaged as the `hegeltest` gem. Each platform gem carries
|
|
17
|
+
the matching `libhegel` engine, so an install compiles nothing and needs no
|
|
18
|
+
engine on the side:
|
|
20
19
|
|
|
21
20
|
```ruby
|
|
22
21
|
# Gemfile
|
|
23
|
-
gem "hegeltest"
|
|
22
|
+
gem "hegeltest"
|
|
24
23
|
```
|
|
25
24
|
|
|
25
|
+
A platform with no published `libhegel` build, such as macOS on Intel, needs
|
|
26
|
+
`HEGEL_LIBHEGEL_PATH` pointing at a local build instead.
|
|
27
|
+
|
|
26
28
|
Require it, then include the generator methods wherever tests draw values:
|
|
27
29
|
|
|
28
30
|
```ruby
|
|
@@ -102,7 +104,7 @@ output.string
|
|
|
102
104
|
`output.string` holds:
|
|
103
105
|
|
|
104
106
|
```
|
|
105
|
-
Falsified after
|
|
107
|
+
Falsified after 12 test cases (0 discarded):
|
|
106
108
|
|
|
107
109
|
xs = [0, 0]
|
|
108
110
|
|
|
@@ -112,7 +114,9 @@ To reproduce this failure, pass the blob below to Hegel.test:
|
|
|
112
114
|
|
|
113
115
|
Hegel names each drawn value (`xs` above) by reading the line the `draw`
|
|
114
116
|
call was written on. Pass the blob back through `reproduce_failure:` to
|
|
115
|
-
replay that
|
|
117
|
+
replay that failure without a full run. The engine replays it until a
|
|
118
|
+
replay fails, within its own budget, and raises `Hegel::Error` when none
|
|
119
|
+
does:
|
|
116
120
|
|
|
117
121
|
```ruby
|
|
118
122
|
Hegel.test(reproduce_failure: "AXicY2VgYGBkZOBiZEBhMAAAAd8AIQ==", verbosity: :quiet) do |tc|
|
|
@@ -121,13 +125,24 @@ Hegel.test(reproduce_failure: "AXicY2VgYGBkZOBiZEBhMAAAAd8AIQ==", verbosity: :qu
|
|
|
121
125
|
end
|
|
122
126
|
```
|
|
123
127
|
|
|
128
|
+
A body that fails only some of the time is reported like any other failure,
|
|
129
|
+
with one more line from the engine that says how reliably it reproduced:
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
note: nondeterministic failure, confirmed: failed 6 of 20 replays at confirmation and 1 of 3 at report time
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
The blob of such a failure still works with `reproduce_failure:`. A failure
|
|
136
|
+
the engine could not confirm comes with no blob, and its report leaves out
|
|
137
|
+
the reproduction line.
|
|
138
|
+
|
|
124
139
|
## Settings
|
|
125
140
|
|
|
126
141
|
`Hegel.test` takes these keywords, all optional:
|
|
127
142
|
|
|
128
143
|
| Keyword | Type | Default | Purpose |
|
|
129
144
|
|---|---|---|---|
|
|
130
|
-
| `test_cases` | `Integer` or `nil` | `nil` (libhegel's own default) | Number of test cases to run |
|
|
145
|
+
| `test_cases` | `Integer` or `nil` | `nil` (libhegel's own default: 100) | Number of test cases to run |
|
|
131
146
|
| `seed` | `Integer` or `nil` | `nil` (libhegel picks its own) | Fixed RNG seed; pair with `derandomize: true` for a reproducible sequence |
|
|
132
147
|
| `derandomize` | `true`/`false` or `nil` | `nil` (libhegel's own default) | Derive the seed deterministically from the test itself, instead of drawing a random one |
|
|
133
148
|
| `verbosity` | `Symbol` or `nil` | `nil` (libhegel's own default) | `:quiet`, `:normal`, `:verbose`, or `:debug` |
|
|
@@ -136,15 +151,19 @@ end
|
|
|
136
151
|
| `phases` | `Array` of `Symbol` or `nil` | `nil` (libhegel's own default: every phase) | Which run phases to enable: `:explicit`, `:reuse`, `:generate`, `:target`, `:shrink` |
|
|
137
152
|
| `suppress_health_check` | `Array` of `Symbol` or `nil` | `nil` (no suppression) | Which health checks to turn off: `:filter_too_much`, `:too_slow`, `:test_cases_too_large`, `:large_initial_test_case` |
|
|
138
153
|
| `report_multiple_failures` | `true`/`false` | `false` | `true` summarizes every distinct failure into one `Hegel::Error` instead of re-raising a single failure's own exception |
|
|
139
|
-
| `stateful_step_count` | `Integer` or `nil` | `nil` (libhegel's own default: 50) | Steps per test case a `Hegel::Stateful.run` call applies, for a stateful test |
|
|
140
154
|
| `output` | `IO` | `$stderr` | Where a failure report is written |
|
|
141
|
-
| `reproduce_failure` | `String` or `nil` | `nil` | Replays the
|
|
155
|
+
| `reproduce_failure` | `String` or `nil` | `nil` | Replays the blob as a run until a replay fails, instead of running a full property |
|
|
142
156
|
|
|
143
157
|
`nil` means the same thing for `test_cases`, `seed`, `derandomize`,
|
|
144
|
-
`verbosity`, `phases`,
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
158
|
+
`verbosity`, `phases`, and `suppress_health_check`: do not call the matching
|
|
159
|
+
libhegel setter, and let the engine's settings profile decide. That is
|
|
160
|
+
libhegel's own default, unless a `hegel.toml` or a `HEGEL_*` environment
|
|
161
|
+
variable (such as `HEGEL_TEST_CASES`) sets the value. On a CI server the
|
|
162
|
+
engine picks its `ci` profile, which derandomizes the run, turns the example
|
|
163
|
+
database off, and suppresses the `too_slow` health check. A keyword passed
|
|
164
|
+
explicitly wins over all of these. `database`/`database_key` and
|
|
165
|
+
`report_multiple_failures` each follow their own rule instead, covered
|
|
166
|
+
below.
|
|
148
167
|
|
|
149
168
|
`database_key:` is the switch that turns libhegel's example database on;
|
|
150
169
|
`database:` only chooses where it writes, and means nothing without a key.
|
|
@@ -160,7 +179,9 @@ Hegel.test(database: "/tmp/wherever") { |tc| tc.draw(integers) }
|
|
|
160
179
|
Give `database_key:` a value unique to the property under test. Two
|
|
161
180
|
`Hegel.test` calls that share a key share one replay scope, so an unrelated
|
|
162
181
|
property can read or overwrite what this one stored. Left at their shared
|
|
163
|
-
default (`nil`, `nil`), a run stores nothing.
|
|
182
|
+
default (`nil`, `nil`), a run stores nothing. With a key and no
|
|
183
|
+
`database:`, the profile chooses the directory, and the `ci` profile turns
|
|
184
|
+
the database off; pass `database:` to store on CI too.
|
|
164
185
|
|
|
165
186
|
`phases:` and `suppress_health_check:` each take an `Array` of `Symbol`, and
|
|
166
187
|
each raises `Hegel::Error` at run time for an empty one. An empty `Array`
|
|
@@ -173,13 +194,13 @@ Hegel.test(phases: []) { |tc| tc.draw(integers) }
|
|
|
173
194
|
# :reuse, :generate, :target, :shrink], got an empty Array"
|
|
174
195
|
```
|
|
175
196
|
|
|
176
|
-
`report_multiple_failures:` defaults to `false`,
|
|
177
|
-
|
|
178
|
-
example and re-raises that failure's own
|
|
179
|
-
intact, so a host framework reports it as
|
|
180
|
-
true` keeps generating afterwards to
|
|
181
|
-
raises `Hegel::Error` naming the count
|
|
182
|
-
exceptions:
|
|
197
|
+
`report_multiple_failures:` defaults to `false`, and is always passed, so a
|
|
198
|
+
profile cannot turn it on by accident. With the default `false`, a run
|
|
199
|
+
stops at its first failing example and re-raises that failure's own
|
|
200
|
+
exception, class and backtrace intact, so a host framework reports it as
|
|
201
|
+
its own. `report_multiple_failures: true` keeps generating afterwards to
|
|
202
|
+
surface other distinct bugs, and then raises `Hegel::Error` naming the count
|
|
203
|
+
instead of any one of the individual exceptions:
|
|
183
204
|
|
|
184
205
|
```ruby
|
|
185
206
|
Hegel.test(test_cases: 10, report_multiple_failures: true, verbosity: :quiet) do |tc|
|
|
@@ -193,11 +214,6 @@ end
|
|
|
193
214
|
# raises Hegel::Error, "Property-based test failed with 2 distinct failures."
|
|
194
215
|
```
|
|
195
216
|
|
|
196
|
-
`stateful_step_count:` bounds how many rules one `Hegel::Stateful.run` call
|
|
197
|
-
applies per test case (see [Stateful Testing](#stateful-testing)); the
|
|
198
|
-
engine documents its own default as 50, and requires the value be at least
|
|
199
|
-
1.
|
|
200
|
-
|
|
201
217
|
`seed:` and `derandomize: true` together make a run reproducible:
|
|
202
218
|
|
|
203
219
|
```ruby
|
|
@@ -273,6 +289,22 @@ To reproduce this failure, pass the blob below to Hegel.test:
|
|
|
273
289
|
reproduce_failure: "AAEAAAAACgEAAAAG"
|
|
274
290
|
```
|
|
275
291
|
|
|
292
|
+
An ordinary Ruby method can share a related group of draws. Use a descriptive
|
|
293
|
+
method parameter name so that the default RuboCop naming rule accepts it:
|
|
294
|
+
|
|
295
|
+
```ruby
|
|
296
|
+
def draw_sorted_pair(test_case)
|
|
297
|
+
low = test_case.draw(integers(min_value: 0, max_value: 100))
|
|
298
|
+
high = test_case.draw(integers(min_value: low, max_value: 200))
|
|
299
|
+
[low, high]
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
Hegel.test do |tc|
|
|
303
|
+
low, high = draw_sorted_pair(tc)
|
|
304
|
+
raise "values are out of order" unless low <= high
|
|
305
|
+
end
|
|
306
|
+
```
|
|
307
|
+
|
|
276
308
|
`assume` discards a test case (the same way `reject` does) unless its
|
|
277
309
|
condition holds, read as ordinary Ruby truthiness rather than restricted to
|
|
278
310
|
`true`/`false`, so `tc.assume(hash[:key])` works directly:
|
|
@@ -292,9 +324,11 @@ body; `assume`/`reject` discard from anywhere in the block.
|
|
|
292
324
|
|
|
293
325
|
`note` records a message for the eventual failure report, interleaved with
|
|
294
326
|
draws in the order both were called. Pass a message directly, or a block.
|
|
295
|
-
The block form is evaluated only on the
|
|
296
|
-
|
|
297
|
-
|
|
327
|
+
The block form is evaluated only on the cases the engine stamps for the
|
|
328
|
+
report: its final replay of each failure, the replays that first confirm
|
|
329
|
+
one, every replay of a `reproduce_failure:` blob, and, for a
|
|
330
|
+
nondeterministic body, the replays that confirm it. So it is the cheaper
|
|
331
|
+
choice when building the message itself costs something. Passing both, or neither, raises `Hegel::Error`:
|
|
298
332
|
|
|
299
333
|
```ruby
|
|
300
334
|
output = StringIO.new
|
|
@@ -401,12 +435,15 @@ Hegel.test(verbosity: :quiet) { |tc| tc.draw(integers(min_value: 10**30, max_val
|
|
|
401
435
|
# => nil
|
|
402
436
|
```
|
|
403
437
|
|
|
404
|
-
### `floats(min_value: nil, max_value: nil, allow_nan:
|
|
438
|
+
### `floats(min_value: nil, max_value: nil, allow_nan: nil, allow_infinity: nil, exclude_min: false, exclude_max: false)`
|
|
405
439
|
|
|
406
|
-
A double in `[min_value, max_value]`, unbounded
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
440
|
+
A double in `[min_value, max_value]`, unbounded by default.
|
|
441
|
+
|
|
442
|
+
`allow_nan` defaults to `true` when the caller passes neither bound, `false`
|
|
443
|
+
otherwise. `allow_infinity` defaults to `true` when the caller leaves either
|
|
444
|
+
side open, `false` when both bounds are set. hegel-rust and
|
|
445
|
+
hegel-typescript's `floats()` default the same way. Pass `true` or `false` to
|
|
446
|
+
override either (see [Gotchas](#gotchas)).
|
|
410
447
|
|
|
411
448
|
```ruby
|
|
412
449
|
Hegel.test(test_cases: 30, verbosity: :quiet) do |tc|
|
|
@@ -415,12 +452,18 @@ Hegel.test(test_cases: 30, verbosity: :quiet) do |tc|
|
|
|
415
452
|
end
|
|
416
453
|
|
|
417
454
|
Hegel.test(test_cases: 200, verbosity: :quiet) do |tc|
|
|
418
|
-
tc.draw(floats
|
|
455
|
+
tc.draw(floats) # unbounded: draws NaN and both infinities
|
|
456
|
+
tc.draw(floats(allow_nan: false, allow_infinity: false)) # finite doubles only
|
|
419
457
|
end
|
|
420
458
|
```
|
|
421
459
|
|
|
422
|
-
|
|
423
|
-
|
|
460
|
+
Three combinations raise `Hegel::Error` at draw time:
|
|
461
|
+
|
|
462
|
+
- `max_value < min_value`: `"floats: max_value < min_value"`
|
|
463
|
+
- `allow_nan: true` with either bound:
|
|
464
|
+
`"floats: cannot have allow_nan=true with min_value or max_value"`
|
|
465
|
+
- `allow_infinity: true` with both bounds:
|
|
466
|
+
`"floats: cannot have allow_infinity=true with both min_value and max_value"`
|
|
424
467
|
|
|
425
468
|
### `text(min_size: 0, max_size: nil, codec: nil, min_codepoint: nil, max_codepoint: nil)`
|
|
426
469
|
|
|
@@ -450,6 +493,39 @@ min_size"`. `alphabet:`, `categories:`, `include_characters:`, and
|
|
|
450
493
|
expose) are not available in this binding; `codec:`, `min_codepoint:`, and
|
|
451
494
|
`max_codepoint:` are the alphabet controls it exposes today.
|
|
452
495
|
|
|
496
|
+
Other generators cover most character-set jobs. These results were measured
|
|
497
|
+
with libhegel 0.45.0:
|
|
498
|
+
|
|
499
|
+
| Job | Generator | Measured output |
|
|
500
|
+
|---|---|---|
|
|
501
|
+
| Characters to include | `from_regex("[ぁ-んA-Z0-9]{1,5}", fullmatch: true)` | `"ぁ"`, `"Hわ9ごう"`; 30 draws all matched the pattern |
|
|
502
|
+
| Characters to exclude | `from_regex("[^,\\t\\n]{0,6}", fullmatch: true)` | `""`, `"\u008FBՔ\u0014C"`; 30 draws contained no comma, tab, or newline |
|
|
503
|
+
| A contiguous block | `text(min_codepoint: 0x300, max_codepoint: 0x36F)` | Every character of 100 draws was in U+0300 through U+036F |
|
|
504
|
+
| An arbitrary alphabet | `arrays(sampled_from(%w[ß a İ]), max_size: 4).map(&:join)` | 30 draws, each built only from `ß`, `a`, and `İ` |
|
|
505
|
+
|
|
506
|
+
The `categories:` option remains an open request. A category such as any `Lu`
|
|
507
|
+
or any `Mn` spans codepoint ranges that a codepoint bound cannot select. A
|
|
508
|
+
regular expression character class requires those ranges to be written out.
|
|
509
|
+
The `.filter` method is not a substitute. In a measured run of 50 test cases,
|
|
510
|
+
filtering `text` to `\p{Han}` produced no valid inputs. The engine filtered 50
|
|
511
|
+
inputs and reported this health check:
|
|
512
|
+
|
|
513
|
+
```
|
|
514
|
+
FailedHealthCheck: FilterTooMuch — it looks like this test is filtering out
|
|
515
|
+
too many inputs. 50 inputs were filtered out by assume() while only 0 valid
|
|
516
|
+
inputs were generated. If this is expected, suppress the check with
|
|
517
|
+
suppress_health_check = [HealthCheck::FilterTooMuch].
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
The engine writes that last sentence in its own naming. This binding takes
|
|
521
|
+
the same setting as `suppress_health_check: [:filter_too_much]`, listed in
|
|
522
|
+
[Settings](#settings).
|
|
523
|
+
|
|
524
|
+
Unconfigured `text` draws from a much wider Unicode range. In 200 draws of
|
|
525
|
+
`text(min_size: 1, max_size: 4)`, the results included emoji, private-use
|
|
526
|
+
characters, and control characters. The highest codepoint was U+10A78F. A
|
|
527
|
+
property that accepts any string needs no character-set options.
|
|
528
|
+
|
|
453
529
|
### `arrays(elements, min_size: 0, max_size: nil)`
|
|
454
530
|
|
|
455
531
|
An `Array` of values from the `elements` generator, with `[min_size,
|
|
@@ -927,8 +1003,28 @@ instance variables directly, and calls a generator method (`integers`,
|
|
|
927
1003
|
surrounding class can once it includes `Hegel::Syntax::Methods`;
|
|
928
1004
|
`Hegel::StateMachine` already includes that module itself.
|
|
929
1005
|
|
|
930
|
-
An invariant runs
|
|
931
|
-
|
|
1006
|
+
An invariant runs before the first rule and after the last. Between rules,
|
|
1007
|
+
the engine samples it with probability 1 / `step_count`, so it runs about
|
|
1008
|
+
once in a test case that runs every step. `invariant(name, always_run:
|
|
1009
|
+
true, &block)` checks it after every rule instead:
|
|
1010
|
+
|
|
1011
|
+
```ruby
|
|
1012
|
+
class CountingMachine < Hegel::StateMachine
|
|
1013
|
+
rule(:step) { |_tc| }
|
|
1014
|
+
invariant(:cheap, always_run: true) { |_tc| }
|
|
1015
|
+
invariant(:expensive) { |_tc| }
|
|
1016
|
+
end
|
|
1017
|
+
```
|
|
1018
|
+
|
|
1019
|
+
`Hegel::Stateful.run(machine, tc, step_count: 50)` takes the most rules one
|
|
1020
|
+
test case runs. It defaults to 50, and the engine rejects a value below 1:
|
|
1021
|
+
|
|
1022
|
+
```ruby
|
|
1023
|
+
Hegel.test(test_cases: 1, verbosity: :quiet) do |tc|
|
|
1024
|
+
Hegel::Stateful.run(CountingMachine.new, tc, step_count: 0)
|
|
1025
|
+
end
|
|
1026
|
+
# raises Hegel::Error, naming HEGEL_E_INVALID_ARG
|
|
1027
|
+
```
|
|
932
1028
|
|
|
933
1029
|
A rule or invariant reports a failure the same way any other test-case body
|
|
934
1030
|
does: raise. This library brings no assertion methods of its own. Include
|
|
@@ -1017,7 +1113,7 @@ output.string
|
|
|
1017
1113
|
```
|
|
1018
1114
|
|
|
1019
1115
|
```
|
|
1020
|
-
Falsified after
|
|
1116
|
+
Falsified after 2 test cases (0 discarded):
|
|
1021
1117
|
|
|
1022
1118
|
Initial invariant check.
|
|
1023
1119
|
Step 1: push
|
|
@@ -1028,7 +1124,7 @@ Falsified after 1 test case (0 discarded):
|
|
|
1028
1124
|
draw_3 = 0
|
|
1029
1125
|
|
|
1030
1126
|
To reproduce this failure, pass the blob below to Hegel.test:
|
|
1031
|
-
reproduce_failure: "
|
|
1127
|
+
reproduce_failure: "AXicjYghEgAACIOkmvz/a9Ww5RE4jqmjeYPCJ30sEXQAaw=="
|
|
1032
1128
|
```
|
|
1033
1129
|
|
|
1034
1130
|
The shrunk report names each step by its rule (`Step 1: push`), and shrinks
|
|
@@ -1130,11 +1226,11 @@ result # => nil
|
|
|
1130
1226
|
|
|
1131
1227
|
## Gotchas
|
|
1132
1228
|
|
|
1133
|
-
1.
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1229
|
+
1. **Float defaults include NaN and infinity when unbounded.** `floats` with
|
|
1230
|
+
no bounds draws `NaN` and both infinities. If your code does not handle
|
|
1231
|
+
them, pass `allow_nan: false, allow_infinity: false` -- but consider
|
|
1232
|
+
whether the code *should* handle them first. A bound turns each one off:
|
|
1233
|
+
NaN needs both bounds absent, an infinity needs one side open.
|
|
1138
1234
|
|
|
1139
1235
|
2. **`integers`'s default range, when a bound is omitted, is still the
|
|
1140
1236
|
signed 64-bit range (`-2**63..(2**63 - 1)`), even though arbitrary
|
|
@@ -1204,7 +1300,6 @@ result # => nil
|
|
|
1204
1300
|
generator's own draw, discards the whole test case.
|
|
1205
1301
|
|
|
1206
1302
|
10. **The suite runs against the real engine on Linux, macOS, and Windows.**
|
|
1207
|
-
Every push to main runs it on Ruby 3.
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
for. Alpine (musl) is the platform this binding has not run on.
|
|
1303
|
+
Every push to main runs it on Ruby 3.4 and 4.0, across Linux x64 and
|
|
1304
|
+
arm64, macOS arm64, and Windows x64 and arm64. Alpine (musl) is the
|
|
1305
|
+
platform this binding has not run on.
|
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
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- meganemura
|
|
@@ -59,6 +59,16 @@ files:
|
|
|
59
59
|
- docs/adr/0011-let-the-test-case-own-every-pool-drawn-from-it.md
|
|
60
60
|
- docs/adr/0012-build-a-failure-origin-from-the-callers-own-frame.md
|
|
61
61
|
- docs/adr/0013-bind-libhegel-through-the-ffi-gem.md
|
|
62
|
+
- docs/adr/0014-name-a-drawn-value-only-when-the-draw-is-the-whole-assigned-value.md
|
|
63
|
+
- docs/adr/0015-follow-the-hegeldev-interface-and-take-changes-upstream-first.md
|
|
64
|
+
- docs/adr/0016-run-mutation-testing-with-mutineer.md
|
|
65
|
+
- docs/adr/0017-raise-the-ruby-floor-to-3-4.md
|
|
66
|
+
- docs/adr/0018-gate-mutation-testing-on-a-committed-baseline.md
|
|
67
|
+
- docs/adr/0019-report-failures-from-the-cases-the-engine-stamps.md
|
|
68
|
+
- docs/adr/0020-derive-span-labels-from-generator-names.md
|
|
69
|
+
- docs/adr/0021-run-a-state-machine-in-rounds-with-its-own-step-count.md
|
|
70
|
+
- docs/adr/0022-keep-microsecond-times-over-a-nanosecond-engine.md
|
|
71
|
+
- docs/adr/0023-leave-unset-settings-to-the-engines-profile.md
|
|
62
72
|
- docs/architecture.md
|
|
63
73
|
- lib/hegel.rb
|
|
64
74
|
- lib/hegel/draw_name.rb
|
|
@@ -100,7 +110,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
100
110
|
requirements:
|
|
101
111
|
- - ">="
|
|
102
112
|
- !ruby/object:Gem::Version
|
|
103
|
-
version: 3.
|
|
113
|
+
version: 3.4.0
|
|
104
114
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
105
115
|
requirements:
|
|
106
116
|
- - ">="
|