hegeltest 0.1.1 → 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 +82 -0
- data/README.md +32 -9
- data/Rakefile +81 -0
- data/docs/README.md +9 -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 +3 -1
- 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 +22 -8
- data/lib/hegel/version.rb +1 -1
- data/lib/hegel.rb +11 -17
- data/lib/tasks/libhegel.rake +9 -2
- data/sig/hegel.rbs +58 -60
- data/skills/hegel-ruby/references/ruby/reference.md +96 -52
- metadata +11 -2
data/lib/hegel/generators.rb
CHANGED
|
@@ -22,7 +22,6 @@ module Hegel
|
|
|
22
22
|
# Hegel::Syntax::Methods#booleans. A boolean, true with probability +p+.
|
|
23
23
|
class BooleanGenerator < Generator
|
|
24
24
|
def initialize(p:)
|
|
25
|
-
super()
|
|
26
25
|
@p = p
|
|
27
26
|
end
|
|
28
27
|
|
|
@@ -45,7 +44,6 @@ module Hegel
|
|
|
45
44
|
INT64_MAX = (2**63) - 1
|
|
46
45
|
|
|
47
46
|
def initialize(min_value:, max_value:)
|
|
48
|
-
super()
|
|
49
47
|
@min_value = min_value
|
|
50
48
|
@max_value = max_value
|
|
51
49
|
end
|
|
@@ -64,22 +62,28 @@ module Hegel
|
|
|
64
62
|
end
|
|
65
63
|
|
|
66
64
|
# Hegel::Syntax::Methods#floats. A double in [min_value, max_value],
|
|
67
|
-
# unbounded
|
|
68
|
-
#
|
|
69
|
-
#
|
|
70
|
-
#
|
|
71
|
-
#
|
|
72
|
-
#
|
|
73
|
-
#
|
|
74
|
-
#
|
|
75
|
-
#
|
|
65
|
+
# unbounded by default.
|
|
66
|
+
#
|
|
67
|
+
# allow_nan and allow_infinity are three-valued: nil asks for the
|
|
68
|
+
# default the bounds imply, and true or false overrides it. Three
|
|
69
|
+
# implementations write that default as the same pair of expressions --
|
|
70
|
+
# hegel-rust's src/generators/numeric.rs, hegel-go's primitives.go, and
|
|
71
|
+
# hegel-typescript's src/generators/numeric.ts -- so an unbounded
|
|
72
|
+
# floats() draws NaN and both infinities, and a fully bounded one draws
|
|
73
|
+
# neither. docs/adr/0015 decides to follow that surface rather than the
|
|
74
|
+
# always-off one this generator carried through 0.1.1.
|
|
75
|
+
#
|
|
76
|
+
# The two checks below come with those defaults: hegel-rust and
|
|
77
|
+
# hegel-go each raise on the same two combinations, and the messages
|
|
78
|
+
# here carry the substring theirs do. smallest_nonzero_magnitude and
|
|
79
|
+
# allow_subnormal, which hegel-rust also exposes, stay unexposed; a
|
|
80
|
+
# keyword can be added later without breaking a caller.
|
|
76
81
|
class FloatGenerator < Generator
|
|
77
82
|
# hegel_generate_float's width; Ruby has one Float type, the 64-bit
|
|
78
83
|
# IEEE 754 double, so this is never anything else.
|
|
79
84
|
WIDTH = 64
|
|
80
85
|
|
|
81
86
|
def initialize(min_value:, max_value:, allow_nan:, allow_infinity:, exclude_min:, exclude_max:)
|
|
82
|
-
super()
|
|
83
87
|
@min_value = min_value
|
|
84
88
|
@max_value = max_value
|
|
85
89
|
@allow_nan = allow_nan
|
|
@@ -89,13 +93,25 @@ module Hegel
|
|
|
89
93
|
end
|
|
90
94
|
|
|
91
95
|
def do_draw(tc)
|
|
96
|
+
has_min = !@min_value.nil?
|
|
97
|
+
has_max = !@max_value.nil?
|
|
98
|
+
allow_nan = @allow_nan.nil? ? !has_min && !has_max : @allow_nan
|
|
99
|
+
allow_infinity = @allow_infinity.nil? ? !has_min || !has_max : @allow_infinity
|
|
100
|
+
|
|
101
|
+
if allow_nan && (has_min || has_max)
|
|
102
|
+
raise Hegel::Error, "floats: cannot have allow_nan=true with min_value or max_value"
|
|
103
|
+
end
|
|
104
|
+
if allow_infinity && has_min && has_max
|
|
105
|
+
raise Hegel::Error, "floats: cannot have allow_infinity=true with both min_value and max_value"
|
|
106
|
+
end
|
|
107
|
+
|
|
92
108
|
min_value = @min_value || -Float::INFINITY
|
|
93
109
|
max_value = @max_value || Float::INFINITY
|
|
94
110
|
raise Hegel::Error, "floats: max_value < min_value" if max_value < min_value
|
|
95
111
|
|
|
96
112
|
tc.generate_float(
|
|
97
113
|
WIDTH, min_value, max_value,
|
|
98
|
-
allow_nan:
|
|
114
|
+
allow_nan: allow_nan, allow_infinity: allow_infinity,
|
|
99
115
|
exclude_min: @exclude_min, exclude_max: @exclude_max,
|
|
100
116
|
smallest_nonzero_magnitude: LibHegel::HEGEL_FLOAT64_SMALLEST_NONZERO_MAGNITUDE_UNRESTRICTED
|
|
101
117
|
)
|
|
@@ -106,7 +122,6 @@ module Hegel
|
|
|
106
122
|
# characters, unbounded above by default.
|
|
107
123
|
class TextGenerator < Generator
|
|
108
124
|
def initialize(min_size:, max_size:, codec:, min_codepoint:, max_codepoint:)
|
|
109
|
-
super()
|
|
110
125
|
@min_size = min_size
|
|
111
126
|
@max_size = max_size
|
|
112
127
|
@codec = codec
|
|
@@ -140,25 +155,27 @@ module Hegel
|
|
|
140
155
|
# with [min_size, max_size] entries, unbounded above by default.
|
|
141
156
|
class ArrayGenerator < Generator
|
|
142
157
|
def initialize(elements, min_size:, max_size:)
|
|
143
|
-
super()
|
|
144
158
|
@elements = elements
|
|
145
159
|
@min_size = min_size
|
|
146
160
|
@max_size = max_size
|
|
161
|
+
@label = combined_label(@elements)
|
|
147
162
|
end
|
|
148
163
|
|
|
164
|
+
attr_reader :label
|
|
165
|
+
|
|
149
166
|
def do_draw(tc)
|
|
150
167
|
raise Hegel::Error, "arrays: min_size must not be negative" if @min_size.negative?
|
|
151
168
|
|
|
152
169
|
max_size = @max_size || LibHegel::HEGEL_COLLECTION_MAX_SIZE_UNBOUNDED
|
|
153
170
|
raise Hegel::Error, "arrays: max_size < min_size" if max_size < @min_size
|
|
154
171
|
|
|
155
|
-
#
|
|
156
|
-
#
|
|
157
|
-
#
|
|
158
|
-
#
|
|
159
|
-
#
|
|
160
|
-
#
|
|
161
|
-
tc.start_span(
|
|
172
|
+
# One span around the whole array, and one around each element
|
|
173
|
+
# (#draw_element below) labelled with the element generator's own
|
|
174
|
+
# label: the shrinker needs both to shrink a compound draw
|
|
175
|
+
# correctly. A missing or misplaced span here shows up as a
|
|
176
|
+
# larger-than-minimal counterexample, not a test failure (see
|
|
177
|
+
# docs/adr/0006).
|
|
178
|
+
tc.start_span(label)
|
|
162
179
|
begin
|
|
163
180
|
draw_elements(tc, max_size)
|
|
164
181
|
ensure
|
|
@@ -180,7 +197,7 @@ module Hegel
|
|
|
180
197
|
end
|
|
181
198
|
|
|
182
199
|
def draw_element(tc)
|
|
183
|
-
tc.start_span(
|
|
200
|
+
tc.start_span(@elements.label)
|
|
184
201
|
@elements.do_draw(tc)
|
|
185
202
|
ensure
|
|
186
203
|
tc.stop_span(discard: false)
|
|
@@ -193,7 +210,6 @@ module Hegel
|
|
|
193
210
|
# nothing here to retry or isolate).
|
|
194
211
|
class JustGenerator < Generator
|
|
195
212
|
def initialize(value)
|
|
196
|
-
super()
|
|
197
213
|
@value = value
|
|
198
214
|
end
|
|
199
215
|
|
|
@@ -206,14 +222,13 @@ module Hegel
|
|
|
206
222
|
# picked by drawing an index in [0, collection.size - 1].
|
|
207
223
|
class SampledFromGenerator < Generator
|
|
208
224
|
def initialize(collection)
|
|
209
|
-
super()
|
|
210
225
|
@collection = collection
|
|
211
226
|
end
|
|
212
227
|
|
|
213
228
|
def do_draw(tc)
|
|
214
229
|
raise Hegel::Error, "sampled_from: collection must not be empty" if @collection.empty?
|
|
215
230
|
|
|
216
|
-
tc.start_span(
|
|
231
|
+
tc.start_span(label)
|
|
217
232
|
begin
|
|
218
233
|
index = tc.generate_integer(0, @collection.size - 1)
|
|
219
234
|
@collection.to_a[index]
|
|
@@ -227,14 +242,16 @@ module Hegel
|
|
|
227
242
|
# by drawing an index in [0, generators.size - 1].
|
|
228
243
|
class OneOfGenerator < Generator
|
|
229
244
|
def initialize(generators)
|
|
230
|
-
super()
|
|
231
245
|
@generators = generators
|
|
246
|
+
@label = combined_label(*@generators)
|
|
232
247
|
end
|
|
233
248
|
|
|
249
|
+
attr_reader :label
|
|
250
|
+
|
|
234
251
|
def do_draw(tc)
|
|
235
252
|
raise Hegel::Error, "one_of: at least one generator is required" if @generators.empty?
|
|
236
253
|
|
|
237
|
-
tc.start_span(
|
|
254
|
+
tc.start_span(label)
|
|
238
255
|
begin
|
|
239
256
|
index = tc.generate_integer(0, @generators.size - 1)
|
|
240
257
|
@generators[index].do_draw(tc)
|
|
@@ -249,12 +266,14 @@ module Hegel
|
|
|
249
266
|
# milestone leaves generate_boolean at its own default probability.
|
|
250
267
|
class OptionalGenerator < Generator
|
|
251
268
|
def initialize(generator)
|
|
252
|
-
super()
|
|
253
269
|
@generator = generator
|
|
270
|
+
@label = combined_label(@generator)
|
|
254
271
|
end
|
|
255
272
|
|
|
273
|
+
attr_reader :label
|
|
274
|
+
|
|
256
275
|
def do_draw(tc)
|
|
257
|
-
tc.start_span(
|
|
276
|
+
tc.start_span(label)
|
|
258
277
|
begin
|
|
259
278
|
tc.generate_boolean ? @generator.do_draw(tc) : nil
|
|
260
279
|
ensure
|
|
@@ -264,18 +283,20 @@ module Hegel
|
|
|
264
283
|
end
|
|
265
284
|
|
|
266
285
|
# Hegel::Syntax::Methods#tuples. Draws each of +generators+ in order
|
|
267
|
-
# into an Array (Ruby has no tuple type; see docs/adr/0004).
|
|
268
|
-
#
|
|
269
|
-
#
|
|
270
|
-
#
|
|
286
|
+
# into an Array (Ruby has no tuple type; see docs/adr/0004). One span
|
|
287
|
+
# goes around the whole draw, unlike arrays/sets/hashes: each position
|
|
288
|
+
# is a distinct generator already, not repeated draws of one, so there
|
|
289
|
+
# is no per-element span to open.
|
|
271
290
|
class TupleGenerator < Generator
|
|
272
291
|
def initialize(generators)
|
|
273
|
-
super()
|
|
274
292
|
@generators = generators
|
|
293
|
+
@label = combined_label(*@generators)
|
|
275
294
|
end
|
|
276
295
|
|
|
296
|
+
attr_reader :label
|
|
297
|
+
|
|
277
298
|
def do_draw(tc)
|
|
278
|
-
tc.start_span(
|
|
299
|
+
tc.start_span(label)
|
|
279
300
|
begin
|
|
280
301
|
@generators.map { |generator| generator.do_draw(tc) }
|
|
281
302
|
ensure
|
|
@@ -288,19 +309,21 @@ module Hegel
|
|
|
288
309
|
# [min_size, max_size] entries, unbounded above by default.
|
|
289
310
|
class SetGenerator < Generator
|
|
290
311
|
def initialize(elements, min_size:, max_size:)
|
|
291
|
-
super()
|
|
292
312
|
@elements = elements
|
|
293
313
|
@min_size = min_size
|
|
294
314
|
@max_size = max_size
|
|
315
|
+
@label = combined_label(@elements)
|
|
295
316
|
end
|
|
296
317
|
|
|
318
|
+
attr_reader :label
|
|
319
|
+
|
|
297
320
|
def do_draw(tc)
|
|
298
321
|
raise Hegel::Error, "sets: min_size must not be negative" if @min_size.negative?
|
|
299
322
|
|
|
300
323
|
max_size = @max_size || LibHegel::HEGEL_COLLECTION_MAX_SIZE_UNBOUNDED
|
|
301
324
|
raise Hegel::Error, "sets: max_size < min_size" if max_size < @min_size
|
|
302
325
|
|
|
303
|
-
tc.start_span(
|
|
326
|
+
tc.start_span(label)
|
|
304
327
|
begin
|
|
305
328
|
draw_elements(tc, max_size)
|
|
306
329
|
ensure
|
|
@@ -334,7 +357,7 @@ module Hegel
|
|
|
334
357
|
end
|
|
335
358
|
|
|
336
359
|
def draw_element(tc)
|
|
337
|
-
tc.start_span(
|
|
360
|
+
tc.start_span(@elements.label)
|
|
338
361
|
@elements.do_draw(tc)
|
|
339
362
|
ensure
|
|
340
363
|
tc.stop_span(discard: false)
|
|
@@ -348,21 +371,26 @@ module Hegel
|
|
|
348
371
|
# nothing, so it is rejected the same way SetGenerator rejects a
|
|
349
372
|
# duplicate element.
|
|
350
373
|
class HashGenerator < Generator
|
|
374
|
+
ENTRY_LABEL = LibHegel.label_from_name("hegel-ruby.Hegel::Generators::HashGenerator.entry")
|
|
375
|
+
|
|
351
376
|
def initialize(keys, values, min_size:, max_size:)
|
|
352
|
-
super()
|
|
353
377
|
@keys = keys
|
|
354
378
|
@values = values
|
|
355
379
|
@min_size = min_size
|
|
356
380
|
@max_size = max_size
|
|
381
|
+
@label = combined_label(@keys, @values)
|
|
382
|
+
@entry_label = LibHegel.label_combine([ENTRY_LABEL, *[@keys, @values].map { |part| Generator.label_of(part) }])
|
|
357
383
|
end
|
|
358
384
|
|
|
385
|
+
attr_reader :label
|
|
386
|
+
|
|
359
387
|
def do_draw(tc)
|
|
360
388
|
raise Hegel::Error, "hashes: min_size must not be negative" if @min_size.negative?
|
|
361
389
|
|
|
362
390
|
max_size = @max_size || LibHegel::HEGEL_COLLECTION_MAX_SIZE_UNBOUNDED
|
|
363
391
|
raise Hegel::Error, "hashes: max_size < min_size" if max_size < @min_size
|
|
364
392
|
|
|
365
|
-
tc.start_span(
|
|
393
|
+
tc.start_span(label)
|
|
366
394
|
begin
|
|
367
395
|
draw_entries(tc, max_size)
|
|
368
396
|
ensure
|
|
@@ -390,12 +418,11 @@ module Hegel
|
|
|
390
418
|
end
|
|
391
419
|
end
|
|
392
420
|
|
|
393
|
-
# Draws the key and its value as one unit inside a single
|
|
394
|
-
#
|
|
395
|
-
#
|
|
396
|
-
# shrinker can retry the whole pair together.
|
|
421
|
+
# Draws the key and its value as one unit inside a single span, so the
|
|
422
|
+
# shrinker can retry the whole pair together. The entry label combines
|
|
423
|
+
# the key and value labels, as the hash's own label does.
|
|
397
424
|
def draw_entry(tc)
|
|
398
|
-
tc.start_span(
|
|
425
|
+
tc.start_span(@entry_label)
|
|
399
426
|
[@keys.do_draw(tc), @values.do_draw(tc)]
|
|
400
427
|
ensure
|
|
401
428
|
tc.stop_span(discard: false)
|
|
@@ -411,7 +438,6 @@ module Hegel
|
|
|
411
438
|
# delegated #do_draw is the whole draw, not a composition of several.
|
|
412
439
|
class CharactersGenerator < Generator
|
|
413
440
|
def initialize(codec:, min_codepoint:, max_codepoint:)
|
|
414
|
-
super()
|
|
415
441
|
@text = TextGenerator.new(
|
|
416
442
|
min_size: 1, max_size: 1, codec: codec,
|
|
417
443
|
min_codepoint: min_codepoint, max_codepoint: max_codepoint
|
|
@@ -427,7 +453,6 @@ module Hegel
|
|
|
427
453
|
# bytes, unbounded above by default.
|
|
428
454
|
class BinaryGenerator < Generator
|
|
429
455
|
def initialize(min_size:, max_size:)
|
|
430
|
-
super()
|
|
431
456
|
@min_size = min_size
|
|
432
457
|
@max_size = max_size
|
|
433
458
|
end
|
|
@@ -473,7 +498,6 @@ module Hegel
|
|
|
473
498
|
# passing here.
|
|
474
499
|
class FromRegexGenerator < Generator
|
|
475
500
|
def initialize(pattern, fullmatch:)
|
|
476
|
-
super()
|
|
477
501
|
@pattern = pattern
|
|
478
502
|
@fullmatch = fullmatch
|
|
479
503
|
end
|
|
@@ -514,7 +538,6 @@ module Hegel
|
|
|
514
538
|
# only repeat the engine's own validation with a worse message.
|
|
515
539
|
class DomainsGenerator < Generator
|
|
516
540
|
def initialize(max_length:)
|
|
517
|
-
super()
|
|
518
541
|
@max_length = max_length
|
|
519
542
|
end
|
|
520
543
|
|
|
@@ -529,16 +552,14 @@ module Hegel
|
|
|
529
552
|
# so it raises here rather than reaching hegel_generate_ipv4 or
|
|
530
553
|
# hegel_generate_ipv6 at all.
|
|
531
554
|
#
|
|
532
|
-
# Spanned
|
|
533
|
-
#
|
|
534
|
-
# (see the HEGEL_LABEL_* table): when both families are enabled this
|
|
555
|
+
# Spanned around the whole draw, the same way OptionalGenerator spans
|
|
556
|
+
# its own boolean-then-delegate draw: when both families are enabled this
|
|
535
557
|
# generator makes two native calls (the family choice, then the
|
|
536
558
|
# address) to produce one value, and the span is what lets the
|
|
537
559
|
# shrinker retry that pair together instead of the two calls
|
|
538
560
|
# separately.
|
|
539
561
|
class IpAddressesGenerator < Generator
|
|
540
562
|
def initialize(v4:, v6:)
|
|
541
|
-
super()
|
|
542
563
|
@v4 = v4
|
|
543
564
|
@v6 = v6
|
|
544
565
|
end
|
|
@@ -546,7 +567,7 @@ module Hegel
|
|
|
546
567
|
def do_draw(tc)
|
|
547
568
|
raise Hegel::Error, "ip_addresses: v4 and v6 must not both be false" if !@v4 && !@v6
|
|
548
569
|
|
|
549
|
-
tc.start_span(
|
|
570
|
+
tc.start_span(label)
|
|
550
571
|
begin
|
|
551
572
|
draw_address(tc)
|
|
552
573
|
ensure
|
|
@@ -576,19 +597,15 @@ module Hegel
|
|
|
576
597
|
# draws uniform random bits except the nil UUID, per the header; an
|
|
577
598
|
# explicit version forces the RFC 4122 version and variant nibbles.
|
|
578
599
|
#
|
|
579
|
-
# Opens no span:
|
|
580
|
-
#
|
|
581
|
-
#
|
|
582
|
-
#
|
|
583
|
-
#
|
|
584
|
-
#
|
|
585
|
-
#
|
|
586
|
-
# reason. A span belongs only around a generator that composes more
|
|
587
|
-
# than one native call into one draw (see IpAddressesGenerator, whose
|
|
588
|
-
# family choice and address draw are two calls under one span).
|
|
600
|
+
# Opens no span: hegel_generate_uuid is one native call, and the engine
|
|
601
|
+
# labels the spans around its own draws itself. BooleanGenerator,
|
|
602
|
+
# IntegerGenerator, and FloatGenerator each make exactly one native call
|
|
603
|
+
# the same way, and open no span of their own for the same reason. A
|
|
604
|
+
# span belongs only around a generator that composes more than one
|
|
605
|
+
# native call into one draw (see IpAddressesGenerator, whose family
|
|
606
|
+
# choice and address draw are two calls under one span).
|
|
589
607
|
class UuidsGenerator < Generator
|
|
590
608
|
def initialize(version:)
|
|
591
|
-
super()
|
|
592
609
|
@version = version
|
|
593
610
|
end
|
|
594
611
|
|
|
@@ -606,19 +623,12 @@ module Hegel
|
|
|
606
623
|
# own src/test_case.rs names this full_ranges::MIN_DATE/MAX_DATE, "what
|
|
607
624
|
# Hypothesis's dates() spans".
|
|
608
625
|
#
|
|
609
|
-
# Opens no span
|
|
610
|
-
# produce its own value, and the header's own comment on
|
|
611
|
-
# HEGEL_LABEL_REGEX ("callers normally never open this span themselves.
|
|
612
|
-
# Likewise for the other engine-side compound draws below") covers
|
|
613
|
-
# HEGEL_LABEL_DATE too, since it sits below REGEX in that same list --
|
|
614
|
-
# the same one-native-call, no-span-of-our-own reasoning UuidsGenerator's
|
|
615
|
-
# own comment already gives for HEGEL_LABEL_UUID.
|
|
626
|
+
# Opens no span, for the reason UuidsGenerator gives: one native call.
|
|
616
627
|
class DatesGenerator < Generator
|
|
617
628
|
MIN_DATE = Date.new(1, 1, 1)
|
|
618
629
|
MAX_DATE = Date.new(9999, 12, 31)
|
|
619
630
|
|
|
620
631
|
def initialize(min_value:, max_value:)
|
|
621
|
-
super()
|
|
622
632
|
@min_value = min_value
|
|
623
633
|
@max_value = max_value
|
|
624
634
|
end
|
|
@@ -635,6 +645,22 @@ module Hegel
|
|
|
635
645
|
end
|
|
636
646
|
end
|
|
637
647
|
|
|
648
|
+
# Since libhegel 0.36 the engine draws a time of day in whole
|
|
649
|
+
# nanoseconds. times and datetimes keep microseconds, because hegel-rust,
|
|
650
|
+
# hegel-cpp, and hegel-typescript do not agree on a finer precision. A
|
|
651
|
+
# bound's microsecond widens to every nanosecond inside it, and the
|
|
652
|
+
# drawn value rounds down, so every microsecond in range stays
|
|
653
|
+
# reachable, both bounds included.
|
|
654
|
+
def self.first_nanosecond(parts)
|
|
655
|
+
hour, minute, second, microsecond = parts
|
|
656
|
+
[hour, minute, second, microsecond * 1000]
|
|
657
|
+
end
|
|
658
|
+
|
|
659
|
+
def self.last_nanosecond(parts)
|
|
660
|
+
hour, minute, second, microsecond = parts
|
|
661
|
+
[hour, minute, second, (microsecond * 1000) + 999]
|
|
662
|
+
end
|
|
663
|
+
|
|
638
664
|
# Hegel::Syntax::Methods#times. A time of day String, "HH:MM:SS.ffffff",
|
|
639
665
|
# in [min_value, max_value] (also "HH:MM:SS.ffffff" Strings), defaulting
|
|
640
666
|
# to the conventional full day (00:00:00.000000 through 23:59:59.999999
|
|
@@ -651,9 +677,7 @@ module Hegel
|
|
|
651
677
|
# representation, both for symmetry with the return value and because
|
|
652
678
|
# it is the only representation available on the input side either.
|
|
653
679
|
#
|
|
654
|
-
# Opens no span, for the
|
|
655
|
-
# native call, HEGEL_LABEL_TIME sits below HEGEL_LABEL_REGEX in the same
|
|
656
|
-
# header list).
|
|
680
|
+
# Opens no span, for the reason UuidsGenerator gives: one native call.
|
|
657
681
|
class TimesGenerator < Generator
|
|
658
682
|
MIDNIGHT = "00:00:00.000000"
|
|
659
683
|
LAST_MICROSECOND = "23:59:59.999999"
|
|
@@ -663,7 +687,6 @@ module Hegel
|
|
|
663
687
|
FORMAT = /\A(\d{2}):(\d{2}):(\d{2})\.(\d{6})\z/
|
|
664
688
|
|
|
665
689
|
def initialize(min_value:, max_value:)
|
|
666
|
-
super()
|
|
667
690
|
@min_value = min_value
|
|
668
691
|
@max_value = max_value
|
|
669
692
|
end
|
|
@@ -673,8 +696,10 @@ module Hegel
|
|
|
673
696
|
max_parts = parse(@max_value || LAST_MICROSECOND, "max_value")
|
|
674
697
|
raise Hegel::Error, "times: max_value < min_value" if (max_parts <=> min_parts).negative?
|
|
675
698
|
|
|
676
|
-
hour, minute, second,
|
|
677
|
-
|
|
699
|
+
hour, minute, second, nanosecond = tc.generate_time(
|
|
700
|
+
Generators.first_nanosecond(min_parts), Generators.last_nanosecond(max_parts)
|
|
701
|
+
)
|
|
702
|
+
format("%02d:%02d:%02d.%06d", hour, minute, second, nanosecond / 1000)
|
|
678
703
|
end
|
|
679
704
|
|
|
680
705
|
private
|
|
@@ -682,7 +707,7 @@ module Hegel
|
|
|
682
707
|
# +value+'s hour/minute/second/microsecond as an Array of Integers,
|
|
683
708
|
# or raises if it is not a String matching FORMAT. This layer does
|
|
684
709
|
# not range-check the parsed fields (an hour of 99, say): measured
|
|
685
|
-
# against libhegel 0.
|
|
710
|
+
# against libhegel 0.45.0, hegel_generate_time already returns
|
|
686
711
|
# HEGEL_E_INVALID_ARG for an invalid time, translated by
|
|
687
712
|
# LibHegel.check! once #do_draw calls tc.generate_time, the same
|
|
688
713
|
# division of labor DomainsGenerator follows for its own
|
|
@@ -713,15 +738,12 @@ module Hegel
|
|
|
713
738
|
# converting to UTC first, so a caller who wants a specific wall-clock
|
|
714
739
|
# bound does not have to convert it themselves.
|
|
715
740
|
#
|
|
716
|
-
# Opens no span, for the
|
|
717
|
-
# native call, HEGEL_LABEL_DATETIME sits below HEGEL_LABEL_REGEX in the
|
|
718
|
-
# same header list).
|
|
741
|
+
# Opens no span, for the reason UuidsGenerator gives: one native call.
|
|
719
742
|
class DatetimesGenerator < Generator
|
|
720
743
|
MIN_DATETIME = Time.utc(1, 1, 1, 0, 0, 0, 0)
|
|
721
744
|
MAX_DATETIME = Time.utc(9999, 12, 31, 23, 59, 59, 999_999)
|
|
722
745
|
|
|
723
746
|
def initialize(min_value:, max_value:)
|
|
724
|
-
super()
|
|
725
747
|
@min_value = min_value
|
|
726
748
|
@max_value = max_value
|
|
727
749
|
end
|
|
@@ -731,13 +753,18 @@ module Hegel
|
|
|
731
753
|
max_value = @max_value || MAX_DATETIME
|
|
732
754
|
raise Hegel::Error, "datetimes: max_value < min_value" if max_value < min_value
|
|
733
755
|
|
|
734
|
-
|
|
756
|
+
# A drawn value has whole microseconds, so a bound with a fraction of
|
|
757
|
+
# one narrows to the whole microseconds inside the range. The lower
|
|
758
|
+
# bound rounds up, in its own zone; the upper bound's #usec below
|
|
759
|
+
# already drops its fraction.
|
|
760
|
+
min_value = min_value.ceil(6)
|
|
761
|
+
date, (hour, minute, second, nanosecond) = tc.generate_datetime(
|
|
735
762
|
[min_value.year, min_value.month, min_value.day],
|
|
736
|
-
[min_value.hour, min_value.min, min_value.sec, min_value.usec],
|
|
763
|
+
Generators.first_nanosecond([min_value.hour, min_value.min, min_value.sec, min_value.usec]),
|
|
737
764
|
[max_value.year, max_value.month, max_value.day],
|
|
738
|
-
[max_value.hour, max_value.min, max_value.sec, max_value.usec]
|
|
765
|
+
Generators.last_nanosecond([max_value.hour, max_value.min, max_value.sec, max_value.usec])
|
|
739
766
|
)
|
|
740
|
-
Time.utc(*date,
|
|
767
|
+
Time.utc(*date, hour, minute, second, nanosecond / 1000)
|
|
741
768
|
end
|
|
742
769
|
end
|
|
743
770
|
|
|
@@ -748,25 +775,23 @@ module Hegel
|
|
|
748
775
|
# a draw surface (BlockTestCase below) and can call #draw on it any
|
|
749
776
|
# number of times.
|
|
750
777
|
#
|
|
751
|
-
#
|
|
752
|
-
#
|
|
753
|
-
#
|
|
754
|
-
# or more draws, each free to depend on values the block already
|
|
755
|
-
# built, folded into a single result. No composite-specific label
|
|
756
|
-
# exists in the table this binding draws from (see lib_hegel.rb); if a
|
|
757
|
-
# true flat_map combinator is added later it can share this label, or
|
|
758
|
-
# the table can grow a dedicated one (hegel.h documents that a library
|
|
759
|
-
# may mint its own stable u64).
|
|
778
|
+
# One span goes around the block's draws. Its label names the block by
|
|
779
|
+
# where it is written, so two different composites do not share a
|
|
780
|
+
# label, the way hegel-rust's compose! labels each body by its source.
|
|
760
781
|
class CompositeGenerator < Generator
|
|
761
782
|
def initialize(&block)
|
|
762
|
-
super()
|
|
763
783
|
@block = block
|
|
784
|
+
@label = LibHegel.label_combine(
|
|
785
|
+
[self.class.label, LibHegel.label_from_name(Array(@block&.source_location).join(":"))]
|
|
786
|
+
)
|
|
764
787
|
end
|
|
765
788
|
|
|
789
|
+
attr_reader :label
|
|
790
|
+
|
|
766
791
|
def do_draw(tc)
|
|
767
792
|
raise Hegel::Error, "composite: block is required" unless @block
|
|
768
793
|
|
|
769
|
-
tc.start_span(
|
|
794
|
+
tc.start_span(label)
|
|
770
795
|
begin
|
|
771
796
|
@block.call(BlockTestCase.new(tc))
|
|
772
797
|
ensure
|
|
@@ -829,7 +854,7 @@ module Hegel
|
|
|
829
854
|
#
|
|
830
855
|
# Opens no span: #do_draw makes no native call of its own, only
|
|
831
856
|
# forwarding to whatever #set installed (one_of, above, already opens
|
|
832
|
-
# its own
|
|
857
|
+
# its own span around that draw). This is the same
|
|
833
858
|
# "a generator opens a span only around more than one native call it
|
|
834
859
|
# makes itself" rule UuidsGenerator's own comment gives for a
|
|
835
860
|
# generator that makes exactly one -- applied here to a generator that
|
|
@@ -838,7 +863,6 @@ module Hegel
|
|
|
838
863
|
# Deferred) opens none either.
|
|
839
864
|
class DeferredGenerator < Generator
|
|
840
865
|
def initialize
|
|
841
|
-
super
|
|
842
866
|
@inner = nil
|
|
843
867
|
end
|
|
844
868
|
|
|
@@ -855,6 +879,14 @@ module Hegel
|
|
|
855
879
|
@inner = generator
|
|
856
880
|
end
|
|
857
881
|
|
|
882
|
+
# The installed generator's label, or this class's own before #set.
|
|
883
|
+
# A generator built from this one before #set keeps the class label,
|
|
884
|
+
# which is how a self-referential definition ends its own cycle, as
|
|
885
|
+
# hegel-rust's DeferredGenerator does.
|
|
886
|
+
def label
|
|
887
|
+
@inner ? @inner.label : self.class.label
|
|
888
|
+
end
|
|
889
|
+
|
|
858
890
|
def do_draw(tc)
|
|
859
891
|
raise Hegel::Error, "deferred: draw called before set" unless @inner
|
|
860
892
|
|