hegeltest 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +103 -0
  3. data/README.md +72 -26
  4. data/Rakefile +81 -0
  5. data/docs/README.md +10 -0
  6. data/docs/adr/0014-name-a-drawn-value-only-when-the-draw-is-the-whole-assigned-value.md +74 -0
  7. data/docs/adr/0015-follow-the-hegeldev-interface-and-take-changes-upstream-first.md +101 -0
  8. data/docs/adr/0016-run-mutation-testing-with-mutineer.md +87 -0
  9. data/docs/adr/0017-raise-the-ruby-floor-to-3-4.md +43 -0
  10. data/docs/adr/0018-gate-mutation-testing-on-a-committed-baseline.md +74 -0
  11. data/docs/adr/0019-report-failures-from-the-cases-the-engine-stamps.md +70 -0
  12. data/docs/adr/0020-derive-span-labels-from-generator-names.md +64 -0
  13. data/docs/adr/0021-run-a-state-machine-in-rounds-with-its-own-step-count.md +59 -0
  14. data/docs/adr/0022-keep-microsecond-times-over-a-nanosecond-engine.md +45 -0
  15. data/docs/adr/0023-leave-unset-settings-to-the-engines-profile.md +50 -0
  16. data/docs/architecture.md +11 -6
  17. data/lib/hegel/draw_name.rb +24 -8
  18. data/lib/hegel/generator.rb +50 -9
  19. data/lib/hegel/generators.rb +134 -102
  20. data/lib/hegel/lib_hegel/real.rb +193 -94
  21. data/lib/hegel/lib_hegel.rb +30 -56
  22. data/lib/hegel/libhegel_version.rb +1 -1
  23. data/lib/hegel/report.rb +17 -8
  24. data/lib/hegel/runner.rb +153 -201
  25. data/lib/hegel/settings.rb +9 -14
  26. data/lib/hegel/state_machine.rb +15 -8
  27. data/lib/hegel/stateful/pool.rb +0 -2
  28. data/lib/hegel/stateful.rb +64 -36
  29. data/lib/hegel/syntax/methods.rb +3 -2
  30. data/lib/hegel/test_case.rb +29 -9
  31. data/lib/hegel/version.rb +1 -1
  32. data/lib/hegel.rb +11 -17
  33. data/lib/tasks/libhegel.rake +9 -2
  34. data/sig/hegel.rbs +60 -61
  35. data/skills/hegel-ruby/references/ruby/reference.md +147 -52
  36. metadata +12 -2
@@ -62,8 +62,8 @@ module Hegel
62
62
 
63
63
  # hegel_verbosity_t, named from hegel.h's enum of the same name. Passed
64
64
  # to hegel_settings_set_verbosity.
65
- HEGEL_VERBOSITY_QUIET = 0
66
- HEGEL_VERBOSITY_NORMAL = 1
65
+ HEGEL_VERBOSITY_NORMAL = 0
66
+ HEGEL_VERBOSITY_QUIET = 1
67
67
  HEGEL_VERBOSITY_VERBOSE = 2
68
68
  HEGEL_VERBOSITY_DEBUG = 3
69
69
 
@@ -86,58 +86,10 @@ module Hegel
86
86
  HEGEL_HC_TEST_CASES_TOO_LARGE = 4
87
87
  HEGEL_HC_LARGE_INITIAL_TEST_CASE = 8
88
88
 
89
- # hegel_label_t, named from hegel.h's enum of the same name. Passed to
90
- # hegel_start_span to identify what kind of structure a span groups.
91
- # Copied through HEGEL_LABEL_SET_CHOICE (value 33). The header
92
- # describes the last two, HEGEL_LABEL_FRESH_ID and
93
- # HEGEL_LABEL_SET_CHOICE, as spans the engine opens itself around a
94
- # hegel_pool_add / hegel_pool_generate call; a caller never passes
95
- # either to hegel_start_span.
96
- #
97
- # The header documents that "Libraries may use any stable u64 to
98
- # define their own spans." A caller building its own compound
99
- # generator on top of this boundary can pick any u64 that does not
100
- # collide with the reserved values below.
101
- HEGEL_LABEL_LIST = 1
102
- HEGEL_LABEL_LIST_ELEMENT = 2
103
- HEGEL_LABEL_SET = 3
104
- HEGEL_LABEL_SET_ELEMENT = 4
105
- HEGEL_LABEL_MAP = 5
106
- HEGEL_LABEL_MAP_ENTRY = 6
107
- HEGEL_LABEL_TUPLE = 7
108
- HEGEL_LABEL_ONE_OF = 8
109
- HEGEL_LABEL_OPTIONAL = 9
110
- HEGEL_LABEL_FIXED_DICT = 10
111
- HEGEL_LABEL_FLAT_MAP = 11
112
- HEGEL_LABEL_FILTER = 12
113
- HEGEL_LABEL_MAPPED = 13
114
- HEGEL_LABEL_SAMPLED_FROM = 14
115
- HEGEL_LABEL_ENUM_VARIANT = 15
116
- HEGEL_LABEL_FEATURE_FLAG = 16
117
- HEGEL_LABEL_REGEX = 17
118
- HEGEL_LABEL_EMAIL = 18
119
- HEGEL_LABEL_URL = 19
120
- HEGEL_LABEL_DOMAIN = 20
121
- HEGEL_LABEL_DATE = 21
122
- HEGEL_LABEL_TIME = 22
123
- HEGEL_LABEL_DATETIME = 23
124
- HEGEL_LABEL_UUID = 24
125
- HEGEL_LABEL_IP_ADDRESS = 25
126
- HEGEL_LABEL_INTEGER = 26
127
- HEGEL_LABEL_FLOAT = 27
128
- HEGEL_LABEL_BOOLEAN = 28
129
- HEGEL_LABEL_BYTES = 29
130
- HEGEL_LABEL_STRING = 30
131
- HEGEL_LABEL_STATEFUL_RULE = 31
132
- HEGEL_LABEL_FRESH_ID = 32
133
- HEGEL_LABEL_SET_CHOICE = 33
134
-
135
89
  # HEGEL_STATE_MACHINE_DONE, named from hegel.h's #define of the same
136
- # name. hegel_state_machine_next_rule writes this to its
137
- # out_rule_index parameter once the current test case's step budget
138
- # is exhausted; see LibHegel::Real#state_machine_next_rule for why
139
- # that raw sentinel is returned rather than translated to nil.
140
- HEGEL_STATE_MACHINE_DONE = -1
90
+ # name, INT64_MIN. hegel_state_machine_next_group writes it once the
91
+ # machine is done, and hegel_state_machine_next_rule once the round is.
92
+ HEGEL_STATE_MACHINE_DONE = -(2**63)
141
93
 
142
94
  # hegel_new_collection's max_size accepts UINT64_MAX to mean "no upper
143
95
  # bound", in the header's own words. Ruby has no fixed-width integer
@@ -162,7 +114,7 @@ module Hegel
162
114
  generate_boolean generate_integer generate_integer_big
163
115
  run_result run_result_free run_result_status run_result_error
164
116
  run_result_failure_count run_result_failure failure_free failure_origin
165
- failure_reproduction_blob test_case_from_blob
117
+ failure_reproduction_blob failure_caveat run_start_blob test_case_should_capture
166
118
  start_span stop_span
167
119
  new_collection collection_more collection_reject collection_free
168
120
  generate_float
@@ -172,10 +124,11 @@ module Hegel
172
124
  generate_ipv4 generate_ipv6 generate_uuid
173
125
  generate_date generate_time generate_datetime
174
126
  settings_set_phases settings_set_suppress_health_check settings_set_report_multiple_failures
175
- settings_set_database_key settings_set_stateful_step_count
127
+ settings_set_database_key
176
128
  target
177
129
  new_pool pool_add pool_generate pool_free
178
- new_state_machine state_machine_next_rule state_machine_rule_rejected state_machine_free
130
+ new_state_machine state_machine_next_group state_machine_next_rule state_machine_rule_rejected
131
+ state_machine_should_check_invariant state_machine_free
179
132
  ].freeze
180
133
 
181
134
  module_function
@@ -234,6 +187,27 @@ module Hegel
234
187
  MESSAGE
235
188
  end
236
189
 
190
+ # A span label is an opaque u64 that names the generator which opened
191
+ # the span. The engine treats two spans with one label as draws of one
192
+ # generator, and swaps or reorders them when it shrinks. hegel.h defines
193
+ # hegel_label_from_name as 64-bit FNV-1a over the name's bytes and lets
194
+ # a binding compute it itself. hegel_label_combine is the same hash over
195
+ # each label's eight little-endian bytes in order, which hegel-rust
196
+ # computes the same way. Computing both here saves a native call per
197
+ # generator built.
198
+ def label_from_name(name)
199
+ fnv1a(name.b.bytes)
200
+ end
201
+
202
+ def label_combine(labels)
203
+ fnv1a(labels.pack("Q<*").bytes)
204
+ end
205
+
206
+ def fnv1a(bytes)
207
+ bytes.reduce(0xcbf29ce484222325) { |hash, byte| ((hash ^ byte) * 0x100000001b3) & 0xffff_ffff_ffff_ffff }
208
+ end
209
+ private_class_method :fnv1a
210
+
237
211
  # hegel_generate_integer_big's own documented convention for
238
212
  # min_value/max_value/out_value: two's-complement little-endian signed
239
213
  # byte buffers. Pure Ruby arithmetic, no native marshalling, so this is
@@ -5,5 +5,5 @@ module Hegel
5
5
  # Hegel::VERSION (the gem's own release): hegel-go and hegel-typescript keep
6
6
  # the two separate too, so bumping the pinned engine can land as its own
7
7
  # commit without also releasing a new gem version.
8
- LIBHEGEL_VERSION = "0.32.5"
8
+ LIBHEGEL_VERSION = "0.45.0"
9
9
  end
data/lib/hegel/report.rb CHANGED
@@ -14,11 +14,14 @@ module Hegel
14
14
  # the generation phase's own counts up to that failure's first
15
15
  # appearance (see Hegel::Runner::GenerationStats), not the shrink
16
16
  # phase's. +entries+ is the [:draw, name, value] / [:note, message] list
17
- # Hegel::TestCase recorded on the final replay that produced this
17
+ # Hegel::TestCase recorded on the stamped case that produced this
18
18
  # failure, in call order, still un-#inspect'd/un-#to_s'd (see
19
19
  # Hegel::TestCase#record_draw and #note for why). +blob+ is the string
20
- # Hegel.test(reproduce_failure:) accepts to replay this same failure.
21
- Failure = Struct.new(:test_cases, :discarded, :entries, :blob)
20
+ # Hegel.test(reproduce_failure:) accepts to replay this same failure, or
21
+ # nil when the engine produced none. +caveat+ is the engine's own account
22
+ # of how reliably a nondeterministic failure reproduced, or nil for a
23
+ # deterministic one.
24
+ Failure = Struct.new(:test_cases, :discarded, :entries, :blob, :caveat)
22
25
 
23
26
  module_function
24
27
 
@@ -50,8 +53,11 @@ module Hegel
50
53
 
51
54
  # Renders one failure's block: its "Falsified after" header, its
52
55
  # entries in call order (:draw #inspect'd, :note #to_s'd -- both here,
53
- # on report assembly, not when Hegel::TestCase recorded them), and how
54
- # to reproduce it. A :note shares the :draw lines' 2-space indent
56
+ # on report assembly, not when Hegel::TestCase recorded them), the
57
+ # engine's caveat when it gave one, and how to reproduce it when there is
58
+ # a blob. hegel-rust prints the caveat as a "note:" line and leaves the
59
+ # reproduction line out for a failure with no blob, and this report does
60
+ # the same. A :note shares the :draw lines' 2-space indent
55
61
  # deliberately: both belong to the same block, and a different indent
56
62
  # would read as a different kind of thing instead of the same
57
63
  # call-order list.
@@ -67,9 +73,12 @@ module Hegel
67
73
  lines << " #{message}"
68
74
  end
69
75
  end
70
- lines << ""
71
- lines << "To reproduce this failure, pass the blob below to Hegel.test:"
72
- lines << " reproduce_failure: #{failure.blob.inspect}"
76
+ lines.push("", "note: #{failure.caveat}") if failure.caveat
77
+ if failure.blob
78
+ lines << ""
79
+ lines << "To reproduce this failure, pass the blob below to Hegel.test:"
80
+ lines << " reproduce_failure: #{failure.blob.inspect}"
81
+ end
73
82
  lines.join("\n")
74
83
  end
75
84