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.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +103 -0
  3. data/README.md +72 -26
  4. data/Rakefile +81 -0
  5. data/docs/README.md +10 -0
  6. data/docs/adr/0014-name-a-drawn-value-only-when-the-draw-is-the-whole-assigned-value.md +74 -0
  7. data/docs/adr/0015-follow-the-hegeldev-interface-and-take-changes-upstream-first.md +101 -0
  8. data/docs/adr/0016-run-mutation-testing-with-mutineer.md +87 -0
  9. data/docs/adr/0017-raise-the-ruby-floor-to-3-4.md +43 -0
  10. data/docs/adr/0018-gate-mutation-testing-on-a-committed-baseline.md +74 -0
  11. data/docs/adr/0019-report-failures-from-the-cases-the-engine-stamps.md +70 -0
  12. data/docs/adr/0020-derive-span-labels-from-generator-names.md +64 -0
  13. data/docs/adr/0021-run-a-state-machine-in-rounds-with-its-own-step-count.md +59 -0
  14. data/docs/adr/0022-keep-microsecond-times-over-a-nanosecond-engine.md +45 -0
  15. data/docs/adr/0023-leave-unset-settings-to-the-engines-profile.md +50 -0
  16. data/docs/architecture.md +11 -6
  17. data/lib/hegel/draw_name.rb +24 -8
  18. data/lib/hegel/generator.rb +50 -9
  19. data/lib/hegel/generators.rb +134 -102
  20. data/lib/hegel/lib_hegel/real.rb +193 -94
  21. data/lib/hegel/lib_hegel.rb +30 -56
  22. data/lib/hegel/libhegel_version.rb +1 -1
  23. data/lib/hegel/report.rb +17 -8
  24. data/lib/hegel/runner.rb +153 -201
  25. data/lib/hegel/settings.rb +9 -14
  26. data/lib/hegel/state_machine.rb +15 -8
  27. data/lib/hegel/stateful/pool.rb +0 -2
  28. data/lib/hegel/stateful.rb +64 -36
  29. data/lib/hegel/syntax/methods.rb +3 -2
  30. data/lib/hegel/test_case.rb +29 -9
  31. data/lib/hegel/version.rb +1 -1
  32. data/lib/hegel.rb +11 -17
  33. data/lib/tasks/libhegel.rake +9 -2
  34. data/sig/hegel.rbs +60 -61
  35. data/skills/hegel-ruby/references/ruby/reference.md +147 -52
  36. 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:` `stateful_step_count:` `output:` `reproduce_failure:`
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, which has not reached
17
- RubyGems.org yet. Installing it from git works for the Ruby code. A git
18
- checkout carries no `libhegel` engine, so a run also needs
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", git: "https://github.com/meganemura/hegel-ruby"
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 3 test cases (0 discarded):
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 exact failing case without a full run:
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 single case the blob encodes, instead of running a full property |
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`, `suppress_health_check`, and `stateful_step_count`: do
145
- not call the matching libhegel setter, and let the engine's own default
146
- apply. `database`/`database_key` and `report_multiple_failures` each follow
147
- their own rule instead, covered below.
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`, which is not libhegel's own
177
- default (`true`). With the default `false`, a run stops at its first failing
178
- example and re-raises that failure's own exception, class and backtrace
179
- intact, so a host framework reports it as its own. `report_multiple_failures:
180
- true` keeps generating afterwards to surface other distinct bugs, and then
181
- raises `Hegel::Error` naming the count instead of any one of the individual
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 one, already-shrunk replay that
296
- produces the report, so it is the cheaper choice when building the message
297
- itself costs something. Passing both, or neither, raises `Hegel::Error`:
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: false, allow_infinity: false, exclude_min: false, exclude_max: false)`
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 (the full finite range) by
407
- default. Unlike hegel-rust's and hegel-typescript's `floats()`, `allow_nan`
408
- and `allow_infinity` both default to **`false`** here even when fully
409
- unbounded (see [Gotchas](#gotchas)).
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(allow_nan: true)) # can now draw NaN
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
- `max_value < min_value` raises `Hegel::Error` at draw time:
423
- `"floats: max_value < min_value"`.
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Ք\u0014੐C"`; 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 once before the first rule, and again after every rule
931
- application that completes without its own assumption failing.
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 1 test case (0 discarded):
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: "AXiclcaxDQAACMMwd0Xi/3dh4QCGKG6E2tzywQAOIgBh"
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. **`floats` defaults `allow_nan: false, allow_infinity: false`, even when
1134
- fully unbounded.** hegel-rust's and hegel-typescript's `floats()` default
1135
- both to `true` when neither bound is set. This binding always starts
1136
- both off; pass `allow_nan: true` and/or `allow_infinity: true`
1137
- deliberately if the code under test needs to see them.
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.3, 3.4, and 4.0, across Linux x64
1208
- and arm64, macOS arm64, and Windows x64 and arm64. Windows arm64 starts
1209
- at Ruby 3.4, the oldest Ruby that RubyInstaller publishes an arm64 build
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.1.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.3.0
113
+ version: 3.4.0
104
114
  required_rubygems_version: !ruby/object:Gem::Requirement
105
115
  requirements:
106
116
  - - ">="