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/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
|
@@ -172,6 +172,9 @@ module Hegel
|
|
|
172
172
|
# BooleanGenerator's own primitive; #draw_boolean is this plus
|
|
173
173
|
# recording.
|
|
174
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.
|
|
175
178
|
@impl.generate_boolean(@ctx, @handle, p, false, false)
|
|
176
179
|
end
|
|
177
180
|
|
|
@@ -220,8 +223,8 @@ module Hegel
|
|
|
220
223
|
@impl.target(@ctx, @handle, value, label)
|
|
221
224
|
end
|
|
222
225
|
|
|
223
|
-
# hegel_start_span, labelled with
|
|
224
|
-
#
|
|
226
|
+
# hegel_start_span, labelled with a generator's label (see
|
|
227
|
+
# Hegel::Generator#label). Every compound Hegel::Generator (map, filter,
|
|
225
228
|
# arrays) opens one of these around its own draw; see #stop_span.
|
|
226
229
|
def start_span(label)
|
|
227
230
|
@impl.start_span(@ctx, @handle, label)
|
|
@@ -428,25 +431,36 @@ module Hegel
|
|
|
428
431
|
# here rather than on that module, the same reason every other native
|
|
429
432
|
# call is a method on this class: Hegel::Stateful never touches @impl or
|
|
430
433
|
# @ctx directly, only this handle-scoped surface.
|
|
431
|
-
def new_state_machine(rule_names, invariant_names)
|
|
432
|
-
@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)
|
|
433
442
|
end
|
|
434
443
|
|
|
435
444
|
# hegel_state_machine_next_rule: the index (into the +rule_names+
|
|
436
|
-
# #new_state_machine was given) of the next rule to run, or
|
|
437
|
-
# LibHegel::HEGEL_STATE_MACHINE_DONE once
|
|
438
|
-
# 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.
|
|
439
447
|
def state_machine_next_rule(state_machine)
|
|
440
448
|
@impl.state_machine_next_rule(@ctx, @handle, state_machine)
|
|
441
449
|
end
|
|
442
450
|
|
|
443
451
|
# hegel_state_machine_rule_rejected: tells libhegel the rule most
|
|
444
452
|
# recently returned by #state_machine_next_rule stopped early on a
|
|
445
|
-
# failed assumption, so
|
|
453
|
+
# failed assumption, so its round does not count toward the step budget.
|
|
446
454
|
def state_machine_rule_rejected(state_machine)
|
|
447
455
|
@impl.state_machine_rule_rejected(@ctx, @handle, state_machine)
|
|
448
456
|
end
|
|
449
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
|
+
|
|
450
464
|
# hegel_state_machine_free. Takes no test-case handle, unlike every
|
|
451
465
|
# other #state_machine_* method here: the header documents a
|
|
452
466
|
# state-machine handle as freeable through any handle of the same
|
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,7 +12,7 @@ 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
|
|
@@ -62,14 +62,19 @@ module Hegel
|
|
|
62
62
|
def pool_generate: (untyped pool, bool consume) -> Integer
|
|
63
63
|
def pool_free: (untyped pool) -> nil
|
|
64
64
|
def free_pools: () -> void
|
|
65
|
-
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
|
|
66
67
|
def state_machine_next_rule: (untyped state_machine) -> Integer
|
|
67
68
|
def state_machine_rule_rejected: (untyped state_machine) -> nil
|
|
69
|
+
def state_machine_should_check_invariant: (untyped state_machine, Integer index) -> bool
|
|
68
70
|
def state_machine_free: (untyped state_machine) -> nil
|
|
69
71
|
end
|
|
70
72
|
|
|
71
73
|
class Generator
|
|
74
|
+
def self.label: () -> Integer
|
|
75
|
+
def self.label_of: (untyped part) -> Integer
|
|
72
76
|
def do_draw: (untyped tc) -> untyped
|
|
77
|
+
def label: () -> Integer
|
|
73
78
|
def map: () { (untyped) -> untyped } -> Generator
|
|
74
79
|
def filter: () { (untyped) -> boolish } -> Generator
|
|
75
80
|
|
|
@@ -87,6 +92,9 @@ module Hegel
|
|
|
87
92
|
end
|
|
88
93
|
|
|
89
94
|
module Generators
|
|
95
|
+
def self.first_nanosecond: (Array[Integer] parts) -> Array[Integer]
|
|
96
|
+
def self.last_nanosecond: (Array[Integer] parts) -> Array[Integer]
|
|
97
|
+
|
|
90
98
|
class BooleanGenerator < Generator
|
|
91
99
|
def initialize: (p: Float) -> void
|
|
92
100
|
def do_draw: (untyped tc) -> bool
|
|
@@ -103,7 +111,7 @@ module Hegel
|
|
|
103
111
|
class FloatGenerator < Generator
|
|
104
112
|
WIDTH: Integer
|
|
105
113
|
|
|
106
|
-
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
|
|
107
115
|
def do_draw: (untyped tc) -> Float
|
|
108
116
|
end
|
|
109
117
|
|
|
@@ -158,6 +166,7 @@ module Hegel
|
|
|
158
166
|
end
|
|
159
167
|
|
|
160
168
|
class HashGenerator < Generator
|
|
169
|
+
ENTRY_LABEL: Integer
|
|
161
170
|
def initialize: (Generator keys, Generator values, min_size: Integer, max_size: Integer?) -> void
|
|
162
171
|
def do_draw: (untyped tc) -> Hash[untyped, untyped]
|
|
163
172
|
|
|
@@ -263,16 +272,26 @@ module Hegel
|
|
|
263
272
|
include Syntax::Methods
|
|
264
273
|
|
|
265
274
|
def self.rule: (Symbol | String name) { (TestCase) -> untyped } -> void
|
|
266
|
-
def self.invariant: (Symbol | String name) { (TestCase) -> untyped } -> void
|
|
275
|
+
def self.invariant: (Symbol | String name, ?always_run: bool) { (TestCase) -> untyped } -> void
|
|
267
276
|
def self.rule_definitions: () -> Hash[String, Proc]
|
|
268
|
-
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
|
|
269
284
|
end
|
|
270
285
|
|
|
271
286
|
module Stateful
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
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
|
|
276
295
|
|
|
277
296
|
class Pool
|
|
278
297
|
def initialize: (TestCase tc) -> void
|
|
@@ -298,7 +317,7 @@ module Hegel
|
|
|
298
317
|
module Methods
|
|
299
318
|
def booleans: (?p: Float) -> Generators::BooleanGenerator
|
|
300
319
|
def integers: (?min_value: Integer?, ?max_value: Integer?) -> Generators::IntegerGenerator
|
|
301
|
-
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
|
|
302
321
|
def text: (?min_size: Integer, ?max_size: Integer?, ?codec: String?, ?min_codepoint: Integer?, ?max_codepoint: Integer?) -> Generators::TextGenerator
|
|
303
322
|
def arrays: (Generator elements, ?min_size: Integer, ?max_size: Integer?) -> Generators::ArrayGenerator
|
|
304
323
|
def just: (untyped value) -> Generators::JustGenerator
|
|
@@ -338,11 +357,12 @@ module Hegel
|
|
|
338
357
|
|
|
339
358
|
module Report
|
|
340
359
|
class Failure
|
|
341
|
-
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
|
|
342
361
|
def test_cases: () -> Integer
|
|
343
362
|
def discarded: () -> Integer
|
|
344
363
|
def entries: () -> Array[[:draw, String, untyped] | [:note, untyped]]
|
|
345
|
-
def blob: () -> String
|
|
364
|
+
def blob: () -> String?
|
|
365
|
+
def caveat: () -> String?
|
|
346
366
|
end
|
|
347
367
|
|
|
348
368
|
def self.assign_names: (Array[[:draw, String, untyped] | [:note, untyped]] entries) -> Array[[:draw, String, untyped] | [:note, untyped]]
|
|
@@ -355,7 +375,7 @@ module Hegel
|
|
|
355
375
|
PHASE_CODES: Hash[Symbol, Integer]
|
|
356
376
|
HEALTH_CHECK_CODES: Hash[Symbol, Integer]
|
|
357
377
|
|
|
358
|
-
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
|
|
359
379
|
def self.apply_verbosity: (untyped impl, untyped ctx, untyped settings, Symbol verbosity) -> void
|
|
360
380
|
def self.apply_database: (untyped impl, untyped ctx, untyped settings, database: String?, database_key: String?) -> void
|
|
361
381
|
def self.apply_phases: (untyped impl, untyped ctx, untyped settings, Array[Symbol] phases) -> void
|
|
@@ -371,25 +391,31 @@ module Hegel
|
|
|
371
391
|
INSTALLED_GEM_DIRS: Array[String]
|
|
372
392
|
STDLIB_DIR: String
|
|
373
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
|
+
|
|
374
400
|
class GenerationStats
|
|
375
401
|
def initialize: () -> void
|
|
376
402
|
def record: (Integer status, String? origin) -> void
|
|
377
403
|
def for: (String? origin) -> [Integer, Integer]
|
|
378
404
|
end
|
|
379
405
|
|
|
380
|
-
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, ?
|
|
381
|
-
def self.run_and_finish: (untyped impl, untyped ctx, untyped
|
|
382
|
-
def self.drive: (untyped impl, untyped ctx, untyped run, GenerationStats stats) { (TestCase) -> void } -> void
|
|
383
|
-
def self.with_test_case: [R] (untyped impl, untyped ctx, untyped tc
|
|
384
|
-
def self.run_case: (untyped impl, untyped ctx, untyped tc, GenerationStats stats) { (TestCase) -> void } -> void
|
|
385
|
-
def self.
|
|
386
|
-
def self.
|
|
387
|
-
def self.
|
|
388
|
-
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]
|
|
389
415
|
def self.classify: (TestCase test_case) { (TestCase) -> void } -> [Integer, String?, Exception?, Array[[:draw, String, untyped] | [:note, untyped]]?]
|
|
390
416
|
def self.origin_for: (Exception exception) -> String
|
|
391
417
|
def self.infrastructure?: (String path) -> bool
|
|
392
|
-
def self.
|
|
418
|
+
def self.not_reproduced_message: () -> String
|
|
393
419
|
def self.multiple_failures_message: (Integer count) -> String
|
|
394
420
|
end
|
|
395
421
|
|
|
@@ -433,8 +459,8 @@ module Hegel
|
|
|
433
459
|
HEGEL_RUN_STATUS_PASSED: Integer
|
|
434
460
|
HEGEL_RUN_STATUS_FAILED: Integer
|
|
435
461
|
HEGEL_RUN_STATUS_ERROR: Integer
|
|
436
|
-
HEGEL_VERBOSITY_QUIET: Integer
|
|
437
462
|
HEGEL_VERBOSITY_NORMAL: Integer
|
|
463
|
+
HEGEL_VERBOSITY_QUIET: Integer
|
|
438
464
|
HEGEL_VERBOSITY_VERBOSE: Integer
|
|
439
465
|
HEGEL_VERBOSITY_DEBUG: Integer
|
|
440
466
|
HEGEL_PHASE_EXPLICIT: Integer
|
|
@@ -447,39 +473,6 @@ module Hegel
|
|
|
447
473
|
HEGEL_HC_TOO_SLOW: Integer
|
|
448
474
|
HEGEL_HC_TEST_CASES_TOO_LARGE: Integer
|
|
449
475
|
HEGEL_HC_LARGE_INITIAL_TEST_CASE: Integer
|
|
450
|
-
HEGEL_LABEL_LIST: Integer
|
|
451
|
-
HEGEL_LABEL_LIST_ELEMENT: Integer
|
|
452
|
-
HEGEL_LABEL_SET: Integer
|
|
453
|
-
HEGEL_LABEL_SET_ELEMENT: Integer
|
|
454
|
-
HEGEL_LABEL_MAP: Integer
|
|
455
|
-
HEGEL_LABEL_MAP_ENTRY: Integer
|
|
456
|
-
HEGEL_LABEL_TUPLE: Integer
|
|
457
|
-
HEGEL_LABEL_ONE_OF: Integer
|
|
458
|
-
HEGEL_LABEL_OPTIONAL: Integer
|
|
459
|
-
HEGEL_LABEL_FIXED_DICT: Integer
|
|
460
|
-
HEGEL_LABEL_FLAT_MAP: Integer
|
|
461
|
-
HEGEL_LABEL_FILTER: Integer
|
|
462
|
-
HEGEL_LABEL_MAPPED: Integer
|
|
463
|
-
HEGEL_LABEL_SAMPLED_FROM: Integer
|
|
464
|
-
HEGEL_LABEL_ENUM_VARIANT: Integer
|
|
465
|
-
HEGEL_LABEL_FEATURE_FLAG: Integer
|
|
466
|
-
HEGEL_LABEL_REGEX: Integer
|
|
467
|
-
HEGEL_LABEL_EMAIL: Integer
|
|
468
|
-
HEGEL_LABEL_URL: Integer
|
|
469
|
-
HEGEL_LABEL_DOMAIN: Integer
|
|
470
|
-
HEGEL_LABEL_DATE: Integer
|
|
471
|
-
HEGEL_LABEL_TIME: Integer
|
|
472
|
-
HEGEL_LABEL_DATETIME: Integer
|
|
473
|
-
HEGEL_LABEL_UUID: Integer
|
|
474
|
-
HEGEL_LABEL_IP_ADDRESS: Integer
|
|
475
|
-
HEGEL_LABEL_INTEGER: Integer
|
|
476
|
-
HEGEL_LABEL_FLOAT: Integer
|
|
477
|
-
HEGEL_LABEL_BOOLEAN: Integer
|
|
478
|
-
HEGEL_LABEL_BYTES: Integer
|
|
479
|
-
HEGEL_LABEL_STRING: Integer
|
|
480
|
-
HEGEL_LABEL_STATEFUL_RULE: Integer
|
|
481
|
-
HEGEL_LABEL_FRESH_ID: Integer
|
|
482
|
-
HEGEL_LABEL_SET_CHOICE: Integer
|
|
483
476
|
HEGEL_STATE_MACHINE_DONE: Integer
|
|
484
477
|
HEGEL_COLLECTION_MAX_SIZE_UNBOUNDED: Integer
|
|
485
478
|
HEGEL_FLOAT64_SMALLEST_NONZERO_MAGNITUDE_UNRESTRICTED: Float
|
|
@@ -488,6 +481,8 @@ module Hegel
|
|
|
488
481
|
def self.with_context: [R] (untyped impl) { (untyped) -> R } -> R
|
|
489
482
|
def self.check!: (untyped impl, untyped ctx, Integer code) -> void
|
|
490
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
|
|
491
486
|
def self.encode_integer_le: (Integer n) -> String
|
|
492
487
|
def self.decode_integer_le: (String bytes) -> Integer
|
|
493
488
|
|
|
@@ -504,12 +499,13 @@ module Hegel
|
|
|
504
499
|
def settings_set_seed: (untyped ctx, untyped s, Integer seed, bool has_seed) -> nil
|
|
505
500
|
def settings_set_derandomize: (untyped ctx, untyped s, bool derandomize) -> nil
|
|
506
501
|
def settings_set_database: (untyped ctx, untyped s, String? database) -> nil
|
|
507
|
-
def settings_set_stateful_step_count: (untyped ctx, untyped s, Integer n) -> nil
|
|
508
502
|
def settings_set_report_multiple_failures: (untyped ctx, untyped s, bool yes) -> nil
|
|
509
503
|
def settings_set_database_key: (untyped ctx, untyped s, String? key) -> nil
|
|
510
504
|
def settings_set_phases: (untyped ctx, untyped s, Integer phases) -> nil
|
|
511
505
|
def settings_set_suppress_health_check: (untyped ctx, untyped s, Integer checks) -> nil
|
|
512
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
|
|
513
509
|
def next_test_case: (untyped ctx, untyped run) -> untyped?
|
|
514
510
|
def run_free: (untyped ctx, untyped run) -> nil
|
|
515
511
|
def test_case_free: (untyped ctx, untyped tc) -> nil
|
|
@@ -527,7 +523,7 @@ module Hegel
|
|
|
527
523
|
def failure_free: (untyped ctx, untyped f) -> nil
|
|
528
524
|
def failure_origin: (untyped ctx, untyped f) -> String
|
|
529
525
|
def failure_reproduction_blob: (untyped ctx, untyped f) -> String?
|
|
530
|
-
def
|
|
526
|
+
def failure_caveat: (untyped ctx, untyped f) -> String?
|
|
531
527
|
def start_span: (untyped ctx, untyped tc, Integer label) -> nil
|
|
532
528
|
def stop_span: (untyped ctx, untyped tc, bool discard) -> nil
|
|
533
529
|
def new_collection: (untyped ctx, untyped tc, Integer min_size, Integer max_size) -> untyped
|
|
@@ -538,9 +534,11 @@ module Hegel
|
|
|
538
534
|
def pool_add: (untyped ctx, untyped tc, untyped pool) -> Integer
|
|
539
535
|
def pool_generate: (untyped ctx, untyped tc, untyped pool, bool consume) -> Integer
|
|
540
536
|
def pool_free: (untyped ctx, untyped pool) -> nil
|
|
541
|
-
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
|
|
542
539
|
def state_machine_next_rule: (untyped ctx, untyped tc, untyped state_machine) -> Integer
|
|
543
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
|
|
544
542
|
def state_machine_free: (untyped ctx, untyped state_machine) -> nil
|
|
545
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
|
|
546
544
|
def string_generator_text: (untyped ctx, min_size: Integer, max_size: Integer, ?codec: String?, ?min_codepoint: Integer, ?max_codepoint: Integer) -> untyped
|