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
data/lib/hegel/stateful.rb
CHANGED
|
@@ -21,64 +21,84 @@ module Hegel
|
|
|
21
21
|
module Stateful
|
|
22
22
|
module_function
|
|
23
23
|
|
|
24
|
+
# The span label of one round, which at concurrency 1 runs one rule.
|
|
25
|
+
ROUND_LABEL = LibHegel.label_from_name("hegel-ruby.Hegel::Stateful.round")
|
|
26
|
+
|
|
27
|
+
# hegel-rust, hegel-go, and hegel-java all default a machine's step count
|
|
28
|
+
# to 50. The engine has no default of its own.
|
|
29
|
+
DEFAULT_STEP_COUNT = 50
|
|
30
|
+
|
|
24
31
|
# +machine+ is a Hegel::StateMachine instance; +tc+ the running
|
|
25
|
-
# Hegel::TestCase.
|
|
32
|
+
# Hegel::TestCase. +step_count+ is the most rounds, and so the most
|
|
33
|
+
# rules, one test case runs. It must be at least 1, or the engine
|
|
34
|
+
# rejects it.
|
|
26
35
|
#
|
|
27
36
|
# Raises Hegel::Error before making any libhegel call when +machine+
|
|
28
37
|
# declares no rules: hegel.h documents hegel_new_state_machine's
|
|
29
38
|
# rule_names as required to be non-empty, and there is nothing useful to
|
|
30
39
|
# run without one.
|
|
31
|
-
def run(machine, tc)
|
|
40
|
+
def run(machine, tc, step_count: DEFAULT_STEP_COUNT)
|
|
32
41
|
rules = machine.class.rule_definitions
|
|
33
42
|
raise Hegel::Error, "hegel: #{machine.class} has no rules; declare at least one with `rule`" if rules.empty?
|
|
34
43
|
|
|
35
44
|
invariants = machine.class.invariant_definitions
|
|
36
|
-
|
|
37
|
-
|
|
45
|
+
state_machine = tc.new_state_machine(
|
|
46
|
+
rules.keys, invariants.keys, invariants.values.map(&:always_run), step_count
|
|
47
|
+
)
|
|
38
48
|
begin
|
|
39
49
|
tc.note { "Initial invariant check." }
|
|
40
|
-
run_invariants(machine, invariants, tc)
|
|
41
|
-
drive(machine, rules,
|
|
50
|
+
run_invariants(machine, invariants.values, tc)
|
|
51
|
+
drive(machine, rules, invariants.values, state_machine, tc)
|
|
52
|
+
tc.note { "Final invariant check." }
|
|
53
|
+
run_invariants(machine, invariants.values, tc)
|
|
42
54
|
ensure
|
|
43
55
|
tc.state_machine_free(state_machine)
|
|
44
56
|
end
|
|
45
57
|
end
|
|
46
58
|
|
|
47
|
-
#
|
|
48
|
-
#
|
|
49
|
-
#
|
|
50
|
-
#
|
|
51
|
-
#
|
|
52
|
-
#
|
|
53
|
-
#
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
# own `run`, which never closes that last span) both shrink the same
|
|
57
|
-
# failure to the same 3-step counterexample every time, 20 runs each.
|
|
58
|
-
# This keeps hegel-rust's own choice -- an unclosed span at DONE --
|
|
59
|
-
# since nothing measured favours the extra stop_span call.
|
|
60
|
-
def drive(machine, rules, rule_names, invariants, state_machine, tc)
|
|
59
|
+
# Runs rounds until libhegel ends the machine. Each round asks for a
|
|
60
|
+
# group first, which the header requires before the first rule too, then
|
|
61
|
+
# pulls rules until the round ends. Between rounds, each invariant runs
|
|
62
|
+
# only when the engine samples it (see
|
|
63
|
+
# Hegel::TestCase#state_machine_should_check_invariant), as hegel-rust,
|
|
64
|
+
# hegel-go, and hegel-java do. The initial and final checks in #run are
|
|
65
|
+
# the ones that always happen.
|
|
66
|
+
def drive(machine, rules, invariants, state_machine, tc)
|
|
67
|
+
rule_names = rules.keys
|
|
61
68
|
steps_attempted = 0
|
|
62
69
|
loop do
|
|
63
|
-
tc.start_span(
|
|
64
|
-
|
|
65
|
-
|
|
70
|
+
tc.start_span(ROUND_LABEL)
|
|
71
|
+
if tc.state_machine_next_group(state_machine) == LibHegel::HEGEL_STATE_MACHINE_DONE
|
|
72
|
+
tc.stop_span(discard: false)
|
|
73
|
+
break
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
rejected = false
|
|
77
|
+
loop do
|
|
78
|
+
rule_index = tc.state_machine_next_rule(state_machine)
|
|
79
|
+
break if rule_index == LibHegel::HEGEL_STATE_MACHINE_DONE
|
|
66
80
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
81
|
+
name = rule_names[rule_index]
|
|
82
|
+
steps_attempted += 1
|
|
83
|
+
tc.note { "Step #{steps_attempted}: #{name}" }
|
|
84
|
+
rejected = true unless apply_rule(machine, rules.fetch(name), state_machine, tc)
|
|
85
|
+
end
|
|
86
|
+
tc.stop_span(discard: rejected)
|
|
87
|
+
run_sampled_invariants(machine, invariants, state_machine, tc)
|
|
71
88
|
end
|
|
72
89
|
end
|
|
73
90
|
|
|
91
|
+
# Returns false when the rule stopped on a failed assumption, and true
|
|
92
|
+
# when it completed.
|
|
93
|
+
#
|
|
74
94
|
# standard:disable Lint/RescueException -- deliberate, the same reason
|
|
75
95
|
# Hegel::Runner.classify's own `rescue Exception` is: Hegel::AssumeFailed
|
|
76
96
|
# and Hegel::StopTest both descend from Exception, not StandardError, so
|
|
77
97
|
# only `rescue Exception` sees every path a rule can take. Every branch
|
|
78
98
|
# other than AssumeFailed re-raises what it caught unchanged -- this
|
|
79
99
|
# never reclassifies an exception or swallows one, it only guarantees
|
|
80
|
-
# the span closes first, so a half-applied rule is never left
|
|
81
|
-
# when the exception unwinds past this method.
|
|
100
|
+
# the round's span closes first, so a half-applied rule is never left
|
|
101
|
+
# mid-span when the exception unwinds past this method.
|
|
82
102
|
#
|
|
83
103
|
# Hegel::FATAL_EXCEPTIONS goes first and closes no span. They say the
|
|
84
104
|
# process is ending, so the span has no reader left to matter to, and
|
|
@@ -89,32 +109,40 @@ module Hegel
|
|
|
89
109
|
#
|
|
90
110
|
# tc.assume(false) inside a rule is not the same event as one raised
|
|
91
111
|
# directly inside a Hegel.test block: it rejects only this rule (told to
|
|
92
|
-
# libhegel via #state_machine_rule_rejected, so the rejected
|
|
112
|
+
# libhegel via #state_machine_rule_rejected, so the rejected round
|
|
93
113
|
# does not count toward the step budget) and the loop keeps going, where
|
|
94
114
|
# Hegel::Runner.classify's own AssumeFailed handling discards the whole
|
|
95
115
|
# test case. Hegel::Runner.classify never sees this one: it is caught
|
|
96
116
|
# and handled right here.
|
|
97
|
-
def apply_rule(machine, block,
|
|
117
|
+
def apply_rule(machine, block, state_machine, tc)
|
|
98
118
|
machine.instance_exec(tc, &block)
|
|
119
|
+
true
|
|
99
120
|
rescue *Hegel::FATAL_EXCEPTIONS
|
|
100
121
|
raise
|
|
101
122
|
rescue Hegel::AssumeFailed
|
|
102
123
|
tc.state_machine_rule_rejected(state_machine)
|
|
103
|
-
tc.stop_span(discard: true)
|
|
104
124
|
tc.note { "Rule stopped early due to violated assumption." }
|
|
125
|
+
false
|
|
105
126
|
rescue Exception
|
|
106
127
|
tc.stop_span(discard: false)
|
|
107
128
|
raise
|
|
108
|
-
else
|
|
109
|
-
tc.stop_span(discard: false)
|
|
110
|
-
run_invariants(machine, invariants, tc)
|
|
111
129
|
end
|
|
112
130
|
# standard:enable Lint/RescueException
|
|
113
131
|
|
|
114
132
|
# Runs every invariant, in declaration order, via #instance_exec -- same
|
|
115
133
|
# argument contract as a rule block (Hegel::StateMachine.invariant).
|
|
116
134
|
def run_invariants(machine, invariants, tc)
|
|
117
|
-
invariants.
|
|
135
|
+
invariants.each { |invariant| machine.instance_exec(tc, &invariant.block) }
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Runs each invariant the engine samples at this join point, in
|
|
139
|
+
# declaration order.
|
|
140
|
+
def run_sampled_invariants(machine, invariants, state_machine, tc)
|
|
141
|
+
invariants.each_with_index do |invariant, index|
|
|
142
|
+
next unless tc.state_machine_should_check_invariant(state_machine, index)
|
|
143
|
+
|
|
144
|
+
machine.instance_exec(tc, &invariant.block)
|
|
145
|
+
end
|
|
118
146
|
end
|
|
119
147
|
end
|
|
120
148
|
end
|
data/lib/hegel/syntax/methods.rb
CHANGED
|
@@ -25,8 +25,9 @@ module Hegel
|
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
# A double in [min_value, max_value]. allow_nan and allow_infinity
|
|
28
|
-
#
|
|
29
|
-
|
|
28
|
+
# default to what the bounds imply: NaN when the caller passed
|
|
29
|
+
# neither bound, an infinity when the caller left either side open.
|
|
30
|
+
def floats(min_value: nil, max_value: nil, allow_nan: nil, allow_infinity: nil, exclude_min: false,
|
|
30
31
|
exclude_max: false)
|
|
31
32
|
Generators::FloatGenerator.new(
|
|
32
33
|
min_value: min_value, max_value: max_value, allow_nan: allow_nan, allow_infinity: allow_infinity,
|
data/lib/hegel/test_case.rb
CHANGED
|
@@ -25,6 +25,12 @@ module Hegel
|
|
|
25
25
|
# #name_for's fallback when a draw has no better name (see below).
|
|
26
26
|
DEFAULT_DRAW_NAME = "draw"
|
|
27
27
|
|
|
28
|
+
# The method names that reach #record_draw: #draw_integer, #draw_boolean,
|
|
29
|
+
# and #draw below. Hegel::DrawName.for takes this as an argument rather
|
|
30
|
+
# than knowing the list itself, so that class -- not DrawName -- stays
|
|
31
|
+
# the one place a fourth draw method needs to be added.
|
|
32
|
+
DRAW_METHOD_NAMES = [:draw, :draw_integer, :draw_boolean].freeze
|
|
33
|
+
|
|
28
34
|
# Frames from #name_for's own caller_locations call up to the user's own
|
|
29
35
|
# source line: #record_draw's call to #name_for (1), the public
|
|
30
36
|
# draw_integer/draw_boolean/draw call to #record_draw (2), and the
|
|
@@ -166,6 +172,9 @@ module Hegel
|
|
|
166
172
|
# BooleanGenerator's own primitive; #draw_boolean is this plus
|
|
167
173
|
# recording.
|
|
168
174
|
def generate_boolean(p = 0.5)
|
|
175
|
+
# has_forced is false, so the engine ignores the forced argument's value.
|
|
176
|
+
# .mutineer.yml ignores that mutant by id, since a line marker would also
|
|
177
|
+
# hide the killed mutant of has_forced.
|
|
169
178
|
@impl.generate_boolean(@ctx, @handle, p, false, false)
|
|
170
179
|
end
|
|
171
180
|
|
|
@@ -214,8 +223,8 @@ module Hegel
|
|
|
214
223
|
@impl.target(@ctx, @handle, value, label)
|
|
215
224
|
end
|
|
216
225
|
|
|
217
|
-
# hegel_start_span, labelled with
|
|
218
|
-
#
|
|
226
|
+
# hegel_start_span, labelled with a generator's label (see
|
|
227
|
+
# Hegel::Generator#label). Every compound Hegel::Generator (map, filter,
|
|
219
228
|
# arrays) opens one of these around its own draw; see #stop_span.
|
|
220
229
|
def start_span(label)
|
|
221
230
|
@impl.start_span(@ctx, @handle, label)
|
|
@@ -422,25 +431,36 @@ module Hegel
|
|
|
422
431
|
# here rather than on that module, the same reason every other native
|
|
423
432
|
# call is a method on this class: Hegel::Stateful never touches @impl or
|
|
424
433
|
# @ctx directly, only this handle-scoped surface.
|
|
425
|
-
def new_state_machine(rule_names, invariant_names)
|
|
426
|
-
@impl.new_state_machine(@ctx, @handle, rule_names, invariant_names)
|
|
434
|
+
def new_state_machine(rule_names, invariant_names, always_check, step_count)
|
|
435
|
+
@impl.new_state_machine(@ctx, @handle, rule_names, invariant_names, always_check, step_count)
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
# hegel_state_machine_next_group: starts the next round, or returns
|
|
439
|
+
# LibHegel::HEGEL_STATE_MACHINE_DONE once the machine is done.
|
|
440
|
+
def state_machine_next_group(state_machine)
|
|
441
|
+
@impl.state_machine_next_group(@ctx, @handle, state_machine)
|
|
427
442
|
end
|
|
428
443
|
|
|
429
444
|
# hegel_state_machine_next_rule: the index (into the +rule_names+
|
|
430
|
-
# #new_state_machine was given) of the next rule to run, or
|
|
431
|
-
# LibHegel::HEGEL_STATE_MACHINE_DONE once
|
|
432
|
-
# is spent.
|
|
445
|
+
# #new_state_machine was given) of the next rule to run this round, or
|
|
446
|
+
# LibHegel::HEGEL_STATE_MACHINE_DONE once the round is over.
|
|
433
447
|
def state_machine_next_rule(state_machine)
|
|
434
448
|
@impl.state_machine_next_rule(@ctx, @handle, state_machine)
|
|
435
449
|
end
|
|
436
450
|
|
|
437
451
|
# hegel_state_machine_rule_rejected: tells libhegel the rule most
|
|
438
452
|
# recently returned by #state_machine_next_rule stopped early on a
|
|
439
|
-
# failed assumption, so
|
|
453
|
+
# failed assumption, so its round does not count toward the step budget.
|
|
440
454
|
def state_machine_rule_rejected(state_machine)
|
|
441
455
|
@impl.state_machine_rule_rejected(@ctx, @handle, state_machine)
|
|
442
456
|
end
|
|
443
457
|
|
|
458
|
+
# hegel_state_machine_should_check_invariant: whether to run the
|
|
459
|
+
# invariant at +index+ at this join point.
|
|
460
|
+
def state_machine_should_check_invariant(state_machine, index)
|
|
461
|
+
@impl.state_machine_should_check_invariant(@ctx, @handle, state_machine, index)
|
|
462
|
+
end
|
|
463
|
+
|
|
444
464
|
# hegel_state_machine_free. Takes no test-case handle, unlike every
|
|
445
465
|
# other #state_machine_* method here: the header documents a
|
|
446
466
|
# state-machine handle as freeable through any handle of the same
|
|
@@ -517,7 +537,7 @@ module Hegel
|
|
|
517
537
|
return label if label
|
|
518
538
|
|
|
519
539
|
location = caller_locations(depth, 1)&.first
|
|
520
|
-
(location && DrawName.for(location.path, location.lineno)) || DEFAULT_DRAW_NAME
|
|
540
|
+
(location && DrawName.for(location.path, location.lineno, DRAW_METHOD_NAMES)) || DEFAULT_DRAW_NAME
|
|
521
541
|
end
|
|
522
542
|
end
|
|
523
543
|
end
|
data/lib/hegel/version.rb
CHANGED
data/lib/hegel.rb
CHANGED
|
@@ -31,11 +31,11 @@ module Hegel
|
|
|
31
31
|
# +test_cases+, +seed+, +derandomize+, +verbosity+, +phases+, and
|
|
32
32
|
# +suppress_health_check+ all default to nil, which means the same thing
|
|
33
33
|
# for each of them: do not call the matching libhegel setter, and let the
|
|
34
|
-
# engine's
|
|
35
|
-
# keyword-to-setter mapping, the verbosity
|
|
36
|
-
# +phases+/+suppress_health_check+ Symbols
|
|
37
|
-
# an Array; an empty Array raises
|
|
38
|
-
# meaning "none"). +verbosity: :quiet+ also silences the failure report
|
|
34
|
+
# engine's resolved settings profile apply instead (see docs/adr/0023).
|
|
35
|
+
# See Hegel::Settings for the keyword-to-setter mapping, the verbosity
|
|
36
|
+
# Symbols it accepts, and the +phases+/+suppress_health_check+ Symbols
|
|
37
|
+
# each of those two accepts (as an Array; an empty Array raises
|
|
38
|
+
# Hegel::Error rather than silently meaning "none"). +verbosity: :quiet+ also silences the failure report
|
|
39
39
|
# itself, not just libhegel's own progress output.
|
|
40
40
|
#
|
|
41
41
|
# +database+ and +database_key+ opt a run into libhegel's example
|
|
@@ -51,14 +51,8 @@ module Hegel
|
|
|
51
51
|
# behind it.
|
|
52
52
|
#
|
|
53
53
|
# +report_multiple_failures+ defaults to false, not nil, unlike every
|
|
54
|
-
# keyword above: see Hegel::Runner.run's own comment for why
|
|
55
|
-
#
|
|
56
|
-
#
|
|
57
|
-
# +stateful_step_count+ bounds how many rules a Hegel::Stateful.run call
|
|
58
|
-
# applies per test case; left nil (the default) leaves libhegel's own
|
|
59
|
-
# default of 50 in place. hegel.h documents it as needing to be at least
|
|
60
|
-
# 1; like +tc.target+'s label, that requirement is left to the engine
|
|
61
|
-
# rather than re-checked here.
|
|
54
|
+
# keyword above: see Hegel::Runner.run's own comment for why it is always
|
|
55
|
+
# passed.
|
|
62
56
|
#
|
|
63
57
|
# +output+ (default $stderr) is where a failure report is written; a
|
|
64
58
|
# caller passes its own IO to capture that report instead (tests do).
|
|
@@ -73,12 +67,12 @@ module Hegel
|
|
|
73
67
|
# (#default_impl) only runs when +impl+ is not given, so a test that does
|
|
74
68
|
# pass one never opens the native library at all.
|
|
75
69
|
def test(test_cases: nil, seed: nil, derandomize: nil, verbosity: nil, database: nil, database_key: nil,
|
|
76
|
-
phases: nil, suppress_health_check: nil, report_multiple_failures: false,
|
|
77
|
-
|
|
70
|
+
phases: nil, suppress_health_check: nil, report_multiple_failures: false, output: $stderr,
|
|
71
|
+
reproduce_failure: nil, impl: default_impl, &block)
|
|
78
72
|
Runner.run(impl: impl, test_cases: test_cases, seed: seed, derandomize: derandomize, verbosity: verbosity,
|
|
79
73
|
database: database, database_key: database_key, phases: phases, suppress_health_check: suppress_health_check,
|
|
80
|
-
report_multiple_failures: report_multiple_failures,
|
|
81
|
-
|
|
74
|
+
report_multiple_failures: report_multiple_failures, output: output, reproduce_failure: reproduce_failure,
|
|
75
|
+
&block)
|
|
82
76
|
end
|
|
83
77
|
|
|
84
78
|
# The Hegel::LibHegel::Real instance #test uses by default, built once and
|
data/lib/tasks/libhegel.rake
CHANGED
|
@@ -18,6 +18,13 @@ module Hegel
|
|
|
18
18
|
|
|
19
19
|
module_function
|
|
20
20
|
|
|
21
|
+
# Since libhegel 0.42.1, hegel-rust tags an engine release
|
|
22
|
+
# libhegel-v<version>. The plain v<version> tags belong to its hegeltest
|
|
23
|
+
# crate, whose version numbers differ from the engine's.
|
|
24
|
+
def release_base(version)
|
|
25
|
+
"#{RELEASE_BASE}/libhegel-v#{version}"
|
|
26
|
+
end
|
|
27
|
+
|
|
21
28
|
# Fetches the host's pinned asset into <root>/<version>/<asset>,
|
|
22
29
|
# skipping the download if it is already there. `downloader` is
|
|
23
30
|
# injectable so the orchestration (skip-if-present, checksum parsing,
|
|
@@ -29,7 +36,7 @@ module Hegel
|
|
|
29
36
|
dest = File.join(root, version, asset)
|
|
30
37
|
return dest if File.file?(dest)
|
|
31
38
|
|
|
32
|
-
base =
|
|
39
|
+
base = release_base(version)
|
|
33
40
|
bytes = downloader.call("#{base}/#{asset}")
|
|
34
41
|
checksum_line = downloader.call("#{base}/#{asset}.sha256")
|
|
35
42
|
verify_and_install(bytes, expected_sha256(checksum_line), dest)
|
|
@@ -66,7 +73,7 @@ module Hegel
|
|
|
66
73
|
# each published platform just to have asset_name recompute the asset
|
|
67
74
|
# name it already is.
|
|
68
75
|
def fetch_all_assets(version: Hegel::LIBHEGEL_VERSION, root: DEFAULT_ROOT, downloader: method(:http_get))
|
|
69
|
-
base =
|
|
76
|
+
base = release_base(version)
|
|
70
77
|
Hegel::Locate::ASSET_NAMES.values.map do |asset|
|
|
71
78
|
dest = File.join(root, version, asset)
|
|
72
79
|
next dest if File.file?(dest)
|
data/sig/hegel.rbs
CHANGED
|
@@ -12,11 +12,12 @@ module Hegel
|
|
|
12
12
|
class AssumeFailed < Exception
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
-
def self.test: (?test_cases: Integer?, ?seed: Integer?, ?derandomize: bool?, ?verbosity: Symbol?, ?database: String?, ?database_key: String?, ?phases: Array[Symbol]?, ?suppress_health_check: Array[Symbol]?, ?report_multiple_failures: bool, ?
|
|
15
|
+
def self.test: (?test_cases: Integer?, ?seed: Integer?, ?derandomize: bool?, ?verbosity: Symbol?, ?database: String?, ?database_key: String?, ?phases: Array[Symbol]?, ?suppress_health_check: Array[Symbol]?, ?report_multiple_failures: bool, ?output: untyped, ?reproduce_failure: String?, ?impl: untyped) { (TestCase) -> void } -> nil
|
|
16
16
|
def self.default_impl: () -> untyped
|
|
17
17
|
|
|
18
18
|
class TestCase
|
|
19
19
|
DEFAULT_DRAW_NAME: String
|
|
20
|
+
DRAW_METHOD_NAMES: Array[Symbol]
|
|
20
21
|
DRAW_CALLER_DEPTH: Integer
|
|
21
22
|
|
|
22
23
|
def initialize: (untyped impl, untyped ctx, untyped handle, ?record: bool) -> void
|
|
@@ -61,14 +62,19 @@ module Hegel
|
|
|
61
62
|
def pool_generate: (untyped pool, bool consume) -> Integer
|
|
62
63
|
def pool_free: (untyped pool) -> nil
|
|
63
64
|
def free_pools: () -> void
|
|
64
|
-
def new_state_machine: (Array[String] rule_names, Array[String] invariant_names) -> untyped
|
|
65
|
+
def new_state_machine: (Array[String] rule_names, Array[String] invariant_names, Array[bool] always_check, Integer step_count) -> untyped
|
|
66
|
+
def state_machine_next_group: (untyped state_machine) -> Integer
|
|
65
67
|
def state_machine_next_rule: (untyped state_machine) -> Integer
|
|
66
68
|
def state_machine_rule_rejected: (untyped state_machine) -> nil
|
|
69
|
+
def state_machine_should_check_invariant: (untyped state_machine, Integer index) -> bool
|
|
67
70
|
def state_machine_free: (untyped state_machine) -> nil
|
|
68
71
|
end
|
|
69
72
|
|
|
70
73
|
class Generator
|
|
74
|
+
def self.label: () -> Integer
|
|
75
|
+
def self.label_of: (untyped part) -> Integer
|
|
71
76
|
def do_draw: (untyped tc) -> untyped
|
|
77
|
+
def label: () -> Integer
|
|
72
78
|
def map: () { (untyped) -> untyped } -> Generator
|
|
73
79
|
def filter: () { (untyped) -> boolish } -> Generator
|
|
74
80
|
|
|
@@ -86,6 +92,9 @@ module Hegel
|
|
|
86
92
|
end
|
|
87
93
|
|
|
88
94
|
module Generators
|
|
95
|
+
def self.first_nanosecond: (Array[Integer] parts) -> Array[Integer]
|
|
96
|
+
def self.last_nanosecond: (Array[Integer] parts) -> Array[Integer]
|
|
97
|
+
|
|
89
98
|
class BooleanGenerator < Generator
|
|
90
99
|
def initialize: (p: Float) -> void
|
|
91
100
|
def do_draw: (untyped tc) -> bool
|
|
@@ -102,7 +111,7 @@ module Hegel
|
|
|
102
111
|
class FloatGenerator < Generator
|
|
103
112
|
WIDTH: Integer
|
|
104
113
|
|
|
105
|
-
def initialize: (min_value: Float?, max_value: Float?, allow_nan: bool
|
|
114
|
+
def initialize: (min_value: Float?, max_value: Float?, allow_nan: bool?, allow_infinity: bool?, exclude_min: bool, exclude_max: bool) -> void
|
|
106
115
|
def do_draw: (untyped tc) -> Float
|
|
107
116
|
end
|
|
108
117
|
|
|
@@ -157,6 +166,7 @@ module Hegel
|
|
|
157
166
|
end
|
|
158
167
|
|
|
159
168
|
class HashGenerator < Generator
|
|
169
|
+
ENTRY_LABEL: Integer
|
|
160
170
|
def initialize: (Generator keys, Generator values, min_size: Integer, max_size: Integer?) -> void
|
|
161
171
|
def do_draw: (untyped tc) -> Hash[untyped, untyped]
|
|
162
172
|
|
|
@@ -262,16 +272,26 @@ module Hegel
|
|
|
262
272
|
include Syntax::Methods
|
|
263
273
|
|
|
264
274
|
def self.rule: (Symbol | String name) { (TestCase) -> untyped } -> void
|
|
265
|
-
def self.invariant: (Symbol | String name) { (TestCase) -> untyped } -> void
|
|
275
|
+
def self.invariant: (Symbol | String name, ?always_run: bool) { (TestCase) -> untyped } -> void
|
|
266
276
|
def self.rule_definitions: () -> Hash[String, Proc]
|
|
267
|
-
def self.invariant_definitions: () -> Hash[String,
|
|
277
|
+
def self.invariant_definitions: () -> Hash[String, Invariant]
|
|
278
|
+
|
|
279
|
+
class Invariant
|
|
280
|
+
attr_reader block: Proc
|
|
281
|
+
attr_reader always_run: bool
|
|
282
|
+
def initialize: (block: Proc, always_run: bool) -> void
|
|
283
|
+
end
|
|
268
284
|
end
|
|
269
285
|
|
|
270
286
|
module Stateful
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
def self.
|
|
287
|
+
ROUND_LABEL: Integer
|
|
288
|
+
DEFAULT_STEP_COUNT: Integer
|
|
289
|
+
|
|
290
|
+
def self.run: (StateMachine machine, TestCase tc, ?step_count: Integer) -> void
|
|
291
|
+
def self.drive: (StateMachine machine, Hash[String, Proc] rules, Array[StateMachine::Invariant] invariants, untyped state_machine, TestCase tc) -> void
|
|
292
|
+
def self.apply_rule: (StateMachine machine, Proc block, untyped state_machine, TestCase tc) -> bool
|
|
293
|
+
def self.run_invariants: (StateMachine machine, Array[StateMachine::Invariant] invariants, TestCase tc) -> void
|
|
294
|
+
def self.run_sampled_invariants: (StateMachine machine, Array[StateMachine::Invariant] invariants, untyped state_machine, TestCase tc) -> void
|
|
275
295
|
|
|
276
296
|
class Pool
|
|
277
297
|
def initialize: (TestCase tc) -> void
|
|
@@ -297,7 +317,7 @@ module Hegel
|
|
|
297
317
|
module Methods
|
|
298
318
|
def booleans: (?p: Float) -> Generators::BooleanGenerator
|
|
299
319
|
def integers: (?min_value: Integer?, ?max_value: Integer?) -> Generators::IntegerGenerator
|
|
300
|
-
def floats: (?min_value: Float?, ?max_value: Float?, ?allow_nan: bool
|
|
320
|
+
def floats: (?min_value: Float?, ?max_value: Float?, ?allow_nan: bool?, ?allow_infinity: bool?, ?exclude_min: bool, ?exclude_max: bool) -> Generators::FloatGenerator
|
|
301
321
|
def text: (?min_size: Integer, ?max_size: Integer?, ?codec: String?, ?min_codepoint: Integer?, ?max_codepoint: Integer?) -> Generators::TextGenerator
|
|
302
322
|
def arrays: (Generator elements, ?min_size: Integer, ?max_size: Integer?) -> Generators::ArrayGenerator
|
|
303
323
|
def just: (untyped value) -> Generators::JustGenerator
|
|
@@ -326,7 +346,7 @@ module Hegel
|
|
|
326
346
|
module DrawName
|
|
327
347
|
ASSIGNMENT_NODE_TYPES: Array[untyped]
|
|
328
348
|
|
|
329
|
-
def self.for: (String path, Integer lineno) -> String?
|
|
349
|
+
def self.for: (String path, Integer lineno, Array[Symbol] call_names) -> String?
|
|
330
350
|
def self.reset_cache: () -> void
|
|
331
351
|
def self.parse: (String path) -> untyped
|
|
332
352
|
def self.cache: () -> Hash[String, untyped]
|
|
@@ -337,11 +357,12 @@ module Hegel
|
|
|
337
357
|
|
|
338
358
|
module Report
|
|
339
359
|
class Failure
|
|
340
|
-
def initialize: (test_cases: Integer, discarded: Integer, entries: Array[[:draw, String, untyped] | [:note, untyped]], blob: String) -> void
|
|
360
|
+
def initialize: (test_cases: Integer, discarded: Integer, entries: Array[[:draw, String, untyped] | [:note, untyped]], blob: String?, caveat: String?) -> void
|
|
341
361
|
def test_cases: () -> Integer
|
|
342
362
|
def discarded: () -> Integer
|
|
343
363
|
def entries: () -> Array[[:draw, String, untyped] | [:note, untyped]]
|
|
344
|
-
def blob: () -> String
|
|
364
|
+
def blob: () -> String?
|
|
365
|
+
def caveat: () -> String?
|
|
345
366
|
end
|
|
346
367
|
|
|
347
368
|
def self.assign_names: (Array[[:draw, String, untyped] | [:note, untyped]] entries) -> Array[[:draw, String, untyped] | [:note, untyped]]
|
|
@@ -354,7 +375,7 @@ module Hegel
|
|
|
354
375
|
PHASE_CODES: Hash[Symbol, Integer]
|
|
355
376
|
HEALTH_CHECK_CODES: Hash[Symbol, Integer]
|
|
356
377
|
|
|
357
|
-
def self.apply: (untyped impl, untyped ctx, untyped settings, test_cases: Integer?, seed: Integer?, derandomize: bool?, verbosity: Symbol?, database: String?, database_key: String?, phases: Array[Symbol]?, suppress_health_check: Array[Symbol]?, report_multiple_failures: bool
|
|
378
|
+
def self.apply: (untyped impl, untyped ctx, untyped settings, test_cases: Integer?, seed: Integer?, derandomize: bool?, verbosity: Symbol?, database: String?, database_key: String?, phases: Array[Symbol]?, suppress_health_check: Array[Symbol]?, report_multiple_failures: bool) -> void
|
|
358
379
|
def self.apply_verbosity: (untyped impl, untyped ctx, untyped settings, Symbol verbosity) -> void
|
|
359
380
|
def self.apply_database: (untyped impl, untyped ctx, untyped settings, database: String?, database_key: String?) -> void
|
|
360
381
|
def self.apply_phases: (untyped impl, untyped ctx, untyped settings, Array[Symbol] phases) -> void
|
|
@@ -370,25 +391,31 @@ module Hegel
|
|
|
370
391
|
INSTALLED_GEM_DIRS: Array[String]
|
|
371
392
|
STDLIB_DIR: String
|
|
372
393
|
|
|
394
|
+
class Capture
|
|
395
|
+
attr_reader exception: Exception
|
|
396
|
+
attr_reader entries: Array[[:draw, String, untyped] | [:note, untyped]]?
|
|
397
|
+
def initialize: (Exception exception, Array[[:draw, String, untyped] | [:note, untyped]]? entries) -> void
|
|
398
|
+
end
|
|
399
|
+
|
|
373
400
|
class GenerationStats
|
|
374
401
|
def initialize: () -> void
|
|
375
402
|
def record: (Integer status, String? origin) -> void
|
|
376
403
|
def for: (String? origin) -> [Integer, Integer]
|
|
377
404
|
end
|
|
378
405
|
|
|
379
|
-
def self.run: (impl: untyped, ?test_cases: Integer?, ?seed: Integer?, ?derandomize: bool?, ?verbosity: Symbol?, ?database: String?, ?database_key: String?, ?phases: Array[Symbol]?, ?suppress_health_check: Array[Symbol]?, ?report_multiple_failures: bool, ?
|
|
380
|
-
def self.run_and_finish: (untyped impl, untyped ctx, untyped
|
|
381
|
-
def self.drive: (untyped impl, untyped ctx, untyped run, GenerationStats stats) { (TestCase) -> void } -> void
|
|
382
|
-
def self.with_test_case: [R] (untyped impl, untyped ctx, untyped tc
|
|
383
|
-
def self.run_case: (untyped impl, untyped ctx, untyped tc, GenerationStats stats) { (TestCase) -> void } -> void
|
|
384
|
-
def self.
|
|
385
|
-
def self.
|
|
386
|
-
def self.
|
|
387
|
-
def self.
|
|
406
|
+
def self.run: (impl: untyped, ?test_cases: Integer?, ?seed: Integer?, ?derandomize: bool?, ?verbosity: Symbol?, ?database: String?, ?database_key: String?, ?phases: Array[Symbol]?, ?suppress_health_check: Array[Symbol]?, ?report_multiple_failures: bool, ?output: untyped, ?reproduce_failure: String?) { (TestCase) -> void } -> nil
|
|
407
|
+
def self.run_and_finish: (untyped impl, untyped ctx, untyped run, reproducing: bool, quiet: bool, output: untyped) { (TestCase) -> void } -> nil
|
|
408
|
+
def self.drive: (untyped impl, untyped ctx, untyped run, GenerationStats stats, Hash[String, Capture] captures) { (TestCase) -> void } -> void
|
|
409
|
+
def self.with_test_case: [R] (untyped impl, untyped ctx, untyped tc) { (TestCase) -> R } -> R
|
|
410
|
+
def self.run_case: (untyped impl, untyped ctx, untyped tc, GenerationStats stats, Hash[String, Capture] captures) { (TestCase) -> void } -> void
|
|
411
|
+
def self.keep_capture: (Hash[String, Capture] captures, String origin, Exception exception, Array[[:draw, String, untyped] | [:note, untyped]]? entries) -> void
|
|
412
|
+
def self.finish: (untyped impl, untyped ctx, untyped result, GenerationStats stats, Hash[String, Capture] captures, reproducing: bool, quiet: bool, output: untyped) -> nil
|
|
413
|
+
def self.report_failures: (untyped impl, untyped ctx, untyped result, GenerationStats stats, Hash[String, Capture] captures, quiet: bool, output: untyped) -> bot
|
|
414
|
+
def self.failure_report: (untyped impl, untyped ctx, untyped failure, GenerationStats stats, Hash[String, Capture] captures) -> [Exception, Report::Failure]
|
|
388
415
|
def self.classify: (TestCase test_case) { (TestCase) -> void } -> [Integer, String?, Exception?, Array[[:draw, String, untyped] | [:note, untyped]]?]
|
|
389
416
|
def self.origin_for: (Exception exception) -> String
|
|
390
417
|
def self.infrastructure?: (String path) -> bool
|
|
391
|
-
def self.
|
|
418
|
+
def self.not_reproduced_message: () -> String
|
|
392
419
|
def self.multiple_failures_message: (Integer count) -> String
|
|
393
420
|
end
|
|
394
421
|
|
|
@@ -432,8 +459,8 @@ module Hegel
|
|
|
432
459
|
HEGEL_RUN_STATUS_PASSED: Integer
|
|
433
460
|
HEGEL_RUN_STATUS_FAILED: Integer
|
|
434
461
|
HEGEL_RUN_STATUS_ERROR: Integer
|
|
435
|
-
HEGEL_VERBOSITY_QUIET: Integer
|
|
436
462
|
HEGEL_VERBOSITY_NORMAL: Integer
|
|
463
|
+
HEGEL_VERBOSITY_QUIET: Integer
|
|
437
464
|
HEGEL_VERBOSITY_VERBOSE: Integer
|
|
438
465
|
HEGEL_VERBOSITY_DEBUG: Integer
|
|
439
466
|
HEGEL_PHASE_EXPLICIT: Integer
|
|
@@ -446,39 +473,6 @@ module Hegel
|
|
|
446
473
|
HEGEL_HC_TOO_SLOW: Integer
|
|
447
474
|
HEGEL_HC_TEST_CASES_TOO_LARGE: Integer
|
|
448
475
|
HEGEL_HC_LARGE_INITIAL_TEST_CASE: Integer
|
|
449
|
-
HEGEL_LABEL_LIST: Integer
|
|
450
|
-
HEGEL_LABEL_LIST_ELEMENT: Integer
|
|
451
|
-
HEGEL_LABEL_SET: Integer
|
|
452
|
-
HEGEL_LABEL_SET_ELEMENT: Integer
|
|
453
|
-
HEGEL_LABEL_MAP: Integer
|
|
454
|
-
HEGEL_LABEL_MAP_ENTRY: Integer
|
|
455
|
-
HEGEL_LABEL_TUPLE: Integer
|
|
456
|
-
HEGEL_LABEL_ONE_OF: Integer
|
|
457
|
-
HEGEL_LABEL_OPTIONAL: Integer
|
|
458
|
-
HEGEL_LABEL_FIXED_DICT: Integer
|
|
459
|
-
HEGEL_LABEL_FLAT_MAP: Integer
|
|
460
|
-
HEGEL_LABEL_FILTER: Integer
|
|
461
|
-
HEGEL_LABEL_MAPPED: Integer
|
|
462
|
-
HEGEL_LABEL_SAMPLED_FROM: Integer
|
|
463
|
-
HEGEL_LABEL_ENUM_VARIANT: Integer
|
|
464
|
-
HEGEL_LABEL_FEATURE_FLAG: Integer
|
|
465
|
-
HEGEL_LABEL_REGEX: Integer
|
|
466
|
-
HEGEL_LABEL_EMAIL: Integer
|
|
467
|
-
HEGEL_LABEL_URL: Integer
|
|
468
|
-
HEGEL_LABEL_DOMAIN: Integer
|
|
469
|
-
HEGEL_LABEL_DATE: Integer
|
|
470
|
-
HEGEL_LABEL_TIME: Integer
|
|
471
|
-
HEGEL_LABEL_DATETIME: Integer
|
|
472
|
-
HEGEL_LABEL_UUID: Integer
|
|
473
|
-
HEGEL_LABEL_IP_ADDRESS: Integer
|
|
474
|
-
HEGEL_LABEL_INTEGER: Integer
|
|
475
|
-
HEGEL_LABEL_FLOAT: Integer
|
|
476
|
-
HEGEL_LABEL_BOOLEAN: Integer
|
|
477
|
-
HEGEL_LABEL_BYTES: Integer
|
|
478
|
-
HEGEL_LABEL_STRING: Integer
|
|
479
|
-
HEGEL_LABEL_STATEFUL_RULE: Integer
|
|
480
|
-
HEGEL_LABEL_FRESH_ID: Integer
|
|
481
|
-
HEGEL_LABEL_SET_CHOICE: Integer
|
|
482
476
|
HEGEL_STATE_MACHINE_DONE: Integer
|
|
483
477
|
HEGEL_COLLECTION_MAX_SIZE_UNBOUNDED: Integer
|
|
484
478
|
HEGEL_FLOAT64_SMALLEST_NONZERO_MAGNITUDE_UNRESTRICTED: Float
|
|
@@ -487,6 +481,8 @@ module Hegel
|
|
|
487
481
|
def self.with_context: [R] (untyped impl) { (untyped) -> R } -> R
|
|
488
482
|
def self.check!: (untyped impl, untyped ctx, Integer code) -> void
|
|
489
483
|
def self.warn_on_version_mismatch: (untyped impl, untyped ctx, ?io: untyped) -> void
|
|
484
|
+
def self.label_from_name: (String name) -> Integer
|
|
485
|
+
def self.label_combine: (Array[Integer] labels) -> Integer
|
|
490
486
|
def self.encode_integer_le: (Integer n) -> String
|
|
491
487
|
def self.decode_integer_le: (String bytes) -> Integer
|
|
492
488
|
|
|
@@ -503,12 +499,13 @@ module Hegel
|
|
|
503
499
|
def settings_set_seed: (untyped ctx, untyped s, Integer seed, bool has_seed) -> nil
|
|
504
500
|
def settings_set_derandomize: (untyped ctx, untyped s, bool derandomize) -> nil
|
|
505
501
|
def settings_set_database: (untyped ctx, untyped s, String? database) -> nil
|
|
506
|
-
def settings_set_stateful_step_count: (untyped ctx, untyped s, Integer n) -> nil
|
|
507
502
|
def settings_set_report_multiple_failures: (untyped ctx, untyped s, bool yes) -> nil
|
|
508
503
|
def settings_set_database_key: (untyped ctx, untyped s, String? key) -> nil
|
|
509
504
|
def settings_set_phases: (untyped ctx, untyped s, Integer phases) -> nil
|
|
510
505
|
def settings_set_suppress_health_check: (untyped ctx, untyped s, Integer checks) -> nil
|
|
511
506
|
def run_start: (untyped ctx, untyped settings) -> untyped
|
|
507
|
+
def run_start_blob: (untyped ctx, untyped settings, String blob) -> untyped
|
|
508
|
+
def test_case_should_capture: (untyped ctx, untyped tc) -> bool
|
|
512
509
|
def next_test_case: (untyped ctx, untyped run) -> untyped?
|
|
513
510
|
def run_free: (untyped ctx, untyped run) -> nil
|
|
514
511
|
def test_case_free: (untyped ctx, untyped tc) -> nil
|
|
@@ -526,7 +523,7 @@ module Hegel
|
|
|
526
523
|
def failure_free: (untyped ctx, untyped f) -> nil
|
|
527
524
|
def failure_origin: (untyped ctx, untyped f) -> String
|
|
528
525
|
def failure_reproduction_blob: (untyped ctx, untyped f) -> String?
|
|
529
|
-
def
|
|
526
|
+
def failure_caveat: (untyped ctx, untyped f) -> String?
|
|
530
527
|
def start_span: (untyped ctx, untyped tc, Integer label) -> nil
|
|
531
528
|
def stop_span: (untyped ctx, untyped tc, bool discard) -> nil
|
|
532
529
|
def new_collection: (untyped ctx, untyped tc, Integer min_size, Integer max_size) -> untyped
|
|
@@ -537,9 +534,11 @@ module Hegel
|
|
|
537
534
|
def pool_add: (untyped ctx, untyped tc, untyped pool) -> Integer
|
|
538
535
|
def pool_generate: (untyped ctx, untyped tc, untyped pool, bool consume) -> Integer
|
|
539
536
|
def pool_free: (untyped ctx, untyped pool) -> nil
|
|
540
|
-
def new_state_machine: (untyped ctx, untyped tc, Array[String] rule_names, Array[String] invariant_names) -> untyped
|
|
537
|
+
def new_state_machine: (untyped ctx, untyped tc, Array[String] rule_names, Array[String] invariant_names, Array[bool] always_check, Integer step_count) -> untyped
|
|
538
|
+
def state_machine_next_group: (untyped ctx, untyped tc, untyped state_machine) -> Integer
|
|
541
539
|
def state_machine_next_rule: (untyped ctx, untyped tc, untyped state_machine) -> Integer
|
|
542
540
|
def state_machine_rule_rejected: (untyped ctx, untyped tc, untyped state_machine) -> nil
|
|
541
|
+
def state_machine_should_check_invariant: (untyped ctx, untyped tc, untyped state_machine, Integer index) -> bool
|
|
543
542
|
def state_machine_free: (untyped ctx, untyped state_machine) -> nil
|
|
544
543
|
def generate_float: (untyped ctx, untyped tc, Integer width, Float min_value, Float max_value, bool allow_nan, bool allow_infinity, bool exclude_min, bool exclude_max, Float smallest_nonzero_magnitude) -> Float
|
|
545
544
|
def string_generator_text: (untyped ctx, min_size: Integer, max_size: Integer, ?codec: String?, ?min_codepoint: Integer, ?max_codepoint: Integer) -> untyped
|