hegeltest 0.1.0 → 0.1.1
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 +21 -0
- data/README.md +43 -20
- data/docs/README.md +1 -0
- data/docs/adr/0014-name-a-drawn-value-only-when-the-draw-is-the-whole-assigned-value.md +74 -0
- data/lib/hegel/draw_name.rb +21 -7
- data/lib/hegel/test_case.rb +7 -1
- data/lib/hegel/version.rb +1 -1
- data/sig/hegel.rbs +2 -1
- data/skills/hegel-ruby/references/ruby/reference.md +57 -6
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5979893a1e1ad319ad6526c5f2ad6927961084c40006946c3f4e0d3bb6a9f269
|
|
4
|
+
data.tar.gz: b5c146a01b94e5832fac50b42c983c844a3b494849038c6776ae54d7fb23a787
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7f0d698a9810abb8c3c2d68e38ad947386948ca840d59339962378db721cd8a961009ce7a68d69577b0aed240fbc80d84b635971c2810bcd0cf827eed6a6368d
|
|
7
|
+
data.tar.gz: 111d8e783b4236b9b5105819b9b34e96d2bbb1a0c314a25dc6f093903e0652bedad4d70d9c8951c865e3bcdeb1947c67f425668b599bffce79c6825468c3ce3e
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.1.1] - 2026-08-31
|
|
4
|
+
|
|
5
|
+
The reference that ships inside the gem told a reader to install `hegeltest`
|
|
6
|
+
from git and to point `HEGEL_LIBHEGEL_PATH` at a local build. 0.1.0 shipped
|
|
7
|
+
that text, and 0.1.0 needs neither. A reader who followed it spent their first
|
|
8
|
+
minutes on a checkout and an engine build. The reference now says what the
|
|
9
|
+
README says.
|
|
10
|
+
|
|
11
|
+
A drawn value written inside a larger expression named itself after the
|
|
12
|
+
assignment target, so `term_start_date = Date.new(tc.draw(...), 2, 29)`
|
|
13
|
+
reported `term_start_date` beside a year. Such a draw now takes the generic
|
|
14
|
+
name, and `label:` names it when a name is wanted. See
|
|
15
|
+
[ADR 0014](docs/adr/0014-name-a-drawn-value-only-when-the-draw-is-the-whole-assigned-value.md).
|
|
16
|
+
|
|
17
|
+
The settings table names 100 as libhegel's own default for `test_cases`. The
|
|
18
|
+
README shows how to scope `Hegel::Syntax::Methods` to tagged example groups in
|
|
19
|
+
a suite that already exists, and how to share a group of draws through a plain
|
|
20
|
+
Ruby method. The `text` section says which generators cover the character-set
|
|
21
|
+
options it does not take, measured against libhegel 0.32.5.
|
|
22
|
+
|
|
3
23
|
## [0.1.0] - 2026-08-20
|
|
4
24
|
|
|
5
25
|
The first release.
|
|
@@ -19,4 +39,5 @@ The engine is called through the `ffi` gem. Each platform gem carries the
|
|
|
19
39
|
matching `libhegel` 0.32.5 build. The platform-independent gem carries none,
|
|
20
40
|
so a run on it needs `HEGEL_LIBHEGEL_PATH` pointing at a local build.
|
|
21
41
|
|
|
42
|
+
[0.1.1]: https://github.com/meganemura/hegel-ruby/releases/tag/v0.1.1
|
|
22
43
|
[0.1.0]: https://github.com/meganemura/hegel-ruby/releases/tag/v0.1.0
|
data/README.md
CHANGED
|
@@ -31,8 +31,7 @@ same process. There is no server and no Python dependency.
|
|
|
31
31
|
|
|
32
32
|
## Status
|
|
33
33
|
|
|
34
|
-
**
|
|
35
|
-
described below runs: Hegel finds a counterexample, shrinks it,
|
|
34
|
+
**Released as `hegeltest` 0.1.0.** Hegel finds a counterexample, shrinks it,
|
|
36
35
|
names the values it drew, and re-raises your own exception.
|
|
37
36
|
|
|
38
37
|
Work ran in three stages, and all three are done:
|
|
@@ -44,22 +43,18 @@ Work ran in three stages, and all three are done:
|
|
|
44
43
|
3. **The advanced features**: the example database, targeted testing,
|
|
45
44
|
stateful testing, phases, and health checks.
|
|
46
45
|
|
|
47
|
-
|
|
48
|
-
`rake platform_gems:build` produces one gem per platform, each carrying the
|
|
49
|
-
matching `libhegel` build. The arm64 macOS gem is verified to find that
|
|
50
|
-
engine inside itself and run a property with no `HEGEL_LIBHEGEL_PATH` set.
|
|
51
|
-
A `v*` tag publishes all six gems from CI. The Hegel maintainers permit a
|
|
52
|
-
third-party project to redistribute their release binaries
|
|
53
|
-
([hegeldev/hegel-rust#411]).
|
|
54
|
-
|
|
55
|
-
So running this today means cloning the repository:
|
|
46
|
+
Install it the way you install any gem:
|
|
56
47
|
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
bundle exec rake
|
|
48
|
+
```ruby
|
|
49
|
+
# Gemfile
|
|
50
|
+
gem "hegeltest"
|
|
61
51
|
```
|
|
62
52
|
|
|
53
|
+
Each platform gem carries the matching `libhegel` build, so an install
|
|
54
|
+
compiles nothing and needs no engine on the side. The Hegel maintainers
|
|
55
|
+
permit a third-party project to redistribute their release binaries
|
|
56
|
+
([hegeldev/hegel-rust#411]).
|
|
57
|
+
|
|
63
58
|
Every push to main runs the suite on Ruby 3.3, 3.4, and 4.0, across Linux x64
|
|
64
59
|
and arm64, macOS arm64, and Windows x64 and arm64. Windows arm64 starts at
|
|
65
60
|
Ruby 3.4, the oldest Ruby that RubyInstaller publishes an arm64 build for. A
|
|
@@ -79,13 +74,13 @@ pull request runs Linux x64 alone.
|
|
|
79
74
|
| Platforms | Linux amd64/arm64, macOS arm64, Windows amd64/arm64 |
|
|
80
75
|
| Engine delivery | One prebuilt `libhegel` per platform-specific gem, built by `rake platform_gems:build` |
|
|
81
76
|
|
|
82
|
-
`HEGEL_LIBHEGEL_PATH`
|
|
77
|
+
`HEGEL_LIBHEGEL_PATH` overrides the bundled engine with a local build.
|
|
83
78
|
|
|
84
79
|
The gem name follows the Rust implementation, whose published crate is
|
|
85
80
|
`hegeltest` and whose library is `hegel`. The name `hegel` on RubyGems.org
|
|
86
81
|
stays free for whoever publishes an official Ruby implementation.
|
|
87
82
|
|
|
88
|
-
macOS on Intel has no published `libhegel` artifact, so that platform
|
|
83
|
+
macOS on Intel has no published `libhegel` artifact, so that platform needs
|
|
89
84
|
`HEGEL_LIBHEGEL_PATH` and a local build.
|
|
90
85
|
|
|
91
86
|
## Quickstart
|
|
@@ -135,6 +130,33 @@ Including `Hegel::Syntax::Methods` makes the generators available without a
|
|
|
135
130
|
prefix, the way FactoryBot makes `create` available. The same generators stay
|
|
136
131
|
reachable as `Hegel::Generators.arrays(...)` without the include.
|
|
137
132
|
|
|
133
|
+
An existing RSpec suite can limit the 25 generator names to tagged example
|
|
134
|
+
groups. This keeps the names out of other groups that have their own helpers:
|
|
135
|
+
|
|
136
|
+
```ruby
|
|
137
|
+
config.include Hegel::Syntax::Methods, :hegel
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
A new project can include the methods in every group, as the Quickstart does.
|
|
141
|
+
|
|
142
|
+
Hegel draws values imperatively, so an ordinary Ruby method can share a group
|
|
143
|
+
of draws between properties. Give the method parameter a descriptive name,
|
|
144
|
+
which the default RuboCop naming rule accepts where a block parameter `tc`
|
|
145
|
+
needs no change:
|
|
146
|
+
|
|
147
|
+
```ruby
|
|
148
|
+
def draw_sorted_pair(test_case)
|
|
149
|
+
low = test_case.draw(integers(min_value: 0, max_value: 100))
|
|
150
|
+
high = test_case.draw(integers(min_value: low, max_value: 200))
|
|
151
|
+
[low, high]
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
Hegel.test do |tc|
|
|
155
|
+
low, high = draw_sorted_pair(tc)
|
|
156
|
+
expect(low).to be <= high
|
|
157
|
+
end
|
|
158
|
+
```
|
|
159
|
+
|
|
138
160
|
The generators are `arrays`, `binary`, `booleans`, `characters`, `composite`,
|
|
139
161
|
`dates`, `datetimes`, `deferred`, `domains`, `emails`, `floats`, `from_regex`,
|
|
140
162
|
`hashes`, `integers`, `ip_addresses`, `just`, `one_of`, `optional`,
|
|
@@ -241,9 +263,10 @@ Inside a test, `tc.note` records a message the failure report prints, and
|
|
|
241
263
|
## Development
|
|
242
264
|
|
|
243
265
|
```bash
|
|
244
|
-
bin/setup
|
|
245
|
-
bundle exec rake
|
|
246
|
-
|
|
266
|
+
bin/setup # install dependencies
|
|
267
|
+
bundle exec rake libhegel:fetch # download the pinned engine, checked against its SHA-256
|
|
268
|
+
bundle exec rake # run the tests and the linter
|
|
269
|
+
bin/console # open an interactive prompt
|
|
247
270
|
```
|
|
248
271
|
|
|
249
272
|
`just` recipes call the same Rake tasks, so `just test` and `just lint` work
|
data/docs/README.md
CHANGED
|
@@ -20,6 +20,7 @@ An index of the design records for `hegel-ruby`.
|
|
|
20
20
|
- [0011: Let the test case own every pool drawn from it](adr/0011-let-the-test-case-own-every-pool-drawn-from-it.md)
|
|
21
21
|
- [0012: Build a failure origin from the caller's own frame](adr/0012-build-a-failure-origin-from-the-callers-own-frame.md)
|
|
22
22
|
- [0013: Bind libhegel through the ffi gem](adr/0013-bind-libhegel-through-the-ffi-gem.md)
|
|
23
|
+
- [0014: Name a drawn value only when the draw is the whole assigned value](adr/0014-name-a-drawn-value-only-when-the-draw-is-the-whole-assigned-value.md)
|
|
23
24
|
|
|
24
25
|
A new decision gets a new record. A changed decision supersedes the old
|
|
25
26
|
record instead of editing it, so the history stays readable.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# 0014: Name a drawn value only when the draw is the whole assigned value
|
|
2
|
+
|
|
3
|
+
## Status
|
|
4
|
+
|
|
5
|
+
Accepted.
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
[ADR 0005](0005-name-drawn-values-from-the-callers-source-with-prism.md)
|
|
10
|
+
recovers a drawn value's name by parsing the caller's source and reading the
|
|
11
|
+
assignment that covers the draw's line. Its Consequences section lists three
|
|
12
|
+
cases it had not checked: a heredoc, several draws on one line, and a method
|
|
13
|
+
chain.
|
|
14
|
+
|
|
15
|
+
A first use of the gem outside this repository reached one of them. The
|
|
16
|
+
caller wrote a draw inside a larger expression:
|
|
17
|
+
|
|
18
|
+
```ruby
|
|
19
|
+
term_start_date = Date.new(tc.draw(integers(min_value: 1904, max_value: 1904)), 2, 29)
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Measured against libhegel 0.32.5, the failure report printed:
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
term_start_date = 1904
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The name says "a date". The value is a year. The reader misread that report
|
|
29
|
+
twice before adding a `label:`.
|
|
30
|
+
|
|
31
|
+
`Hegel::DrawName` already carries the rule this breaks. Its own
|
|
32
|
+
documentation states it: a wrong name misdirects a reader of the failure
|
|
33
|
+
report more than a missing one does, so an ambiguous line returns nil rather
|
|
34
|
+
than choosing between candidates. The module applied that rule to two
|
|
35
|
+
assignments written side by side, and applied the opposite rule to a draw
|
|
36
|
+
buried in one assignment's value.
|
|
37
|
+
|
|
38
|
+
A draw pulled out into a plain method is a separate case, and it already
|
|
39
|
+
worked. Measured on the same engine, a helper whose body reads
|
|
40
|
+
`year = tc.draw(integers(min_value: 1900, max_value: 1910))` reported
|
|
41
|
+
`year = 1900`. `Hegel::TestCase::DRAW_CALLER_DEPTH` lands on the line that
|
|
42
|
+
called the draw, which is the helper's own line, and the name there is the
|
|
43
|
+
one the caller wrote.
|
|
44
|
+
|
|
45
|
+
## Decision
|
|
46
|
+
|
|
47
|
+
Recover a name only when the assignment's value is the draw call itself: the
|
|
48
|
+
value node is a `Prism::CallNode` whose name is one of the methods that reach
|
|
49
|
+
`Hegel::TestCase#record_draw`. Any other value node returns nil, and
|
|
50
|
+
`Hegel::TestCase#name_for` falls back to the generic name.
|
|
51
|
+
|
|
52
|
+
The three method names stay in `Hegel::TestCase`, which defines them, and
|
|
53
|
+
`Hegel::DrawName.for` takes them as an argument. A fourth draw method then
|
|
54
|
+
has one place to be added.
|
|
55
|
+
|
|
56
|
+
## Consequences
|
|
57
|
+
|
|
58
|
+
A draw written inside a larger expression reports the generic name and its
|
|
59
|
+
value. The reader loses a name and keeps a value that agrees with it. A
|
|
60
|
+
caller who wants the name back passes `label:`, which
|
|
61
|
+
[ADR 0005](0005-name-drawn-values-from-the-callers-source-with-prism.md)
|
|
62
|
+
already gives precedence over the recovered name.
|
|
63
|
+
|
|
64
|
+
A method chain answers the question ADR 0005 left open, and answers it the
|
|
65
|
+
same way: `xs = tc.draw_integer(0, 10).to_s` reports the generic name,
|
|
66
|
+
because `xs` holds a String and the drawn value is an Integer.
|
|
67
|
+
|
|
68
|
+
A draw written inside another draw's arguments shares the enclosing
|
|
69
|
+
assignment's name, because the enclosing assignment's value is a draw call
|
|
70
|
+
and one line carries both draws. `label:` separates them.
|
|
71
|
+
|
|
72
|
+
`Hegel::DrawName` now answers a narrower question than its name suggests on
|
|
73
|
+
its own, so its documentation states the question it answers and the reason
|
|
74
|
+
for the narrowing.
|
data/lib/hegel/draw_name.rb
CHANGED
|
@@ -6,9 +6,10 @@ module Hegel
|
|
|
6
6
|
# Recovers a drawn value's variable name from the caller's own source (see
|
|
7
7
|
# docs/adr/0005), so a failure report can print `n = 501` instead of
|
|
8
8
|
# `draw = 501` when the caller never passed a label:. This module only
|
|
9
|
-
# answers "what name does path:lineno assign
|
|
10
|
-
#
|
|
11
|
-
#
|
|
9
|
+
# answers "what name does path:lineno assign the value a draw call there
|
|
10
|
+
# produced, when that call is the whole assigned value"; deciding whether
|
|
11
|
+
# to call it, what counts as a draw call, and what to fall back to when it
|
|
12
|
+
# answers nil, is Hegel::TestCase#name_for's job, not this one's.
|
|
12
13
|
module DrawName
|
|
13
14
|
# Node types #for treats as "this line names a drawn value". An
|
|
14
15
|
# explicit list, not every Prism::Node subclass whose name ends in
|
|
@@ -23,10 +24,20 @@ module Hegel
|
|
|
23
24
|
|
|
24
25
|
# The name +path+:+lineno+ assigns a drawn value to, or nil when that
|
|
25
26
|
# cannot be answered confidently: +path+ cannot be read, Prism cannot
|
|
26
|
-
# parse it,
|
|
27
|
+
# parse it, the assignments covering +lineno+ do not single one out, or
|
|
28
|
+
# the one they single out does not assign a draw call directly.
|
|
29
|
+
# +call_names+ is the set of method names that count as a draw call
|
|
30
|
+
# (Hegel::TestCase::DRAW_METHOD_NAMES); this module has no opinion of
|
|
31
|
+
# its own about which methods draw.
|
|
32
|
+
#
|
|
27
33
|
# A wrong name would misdirect a reader of the failure report more than
|
|
28
34
|
# a missing one would, so an ambiguous line returns nil rather than
|
|
29
|
-
# guessing between candidates.
|
|
35
|
+
# guessing between candidates. That is also why a singled-out
|
|
36
|
+
# assignment still answers nil when its value is built from a draw
|
|
37
|
+
# rather than being the draw itself, as in `xs = [tc.draw(integers)]`
|
|
38
|
+
# or `n = tc.draw(integers).abs`: the recovered name would describe the
|
|
39
|
+
# Array or the Integer#abs result, not the drawn value the report
|
|
40
|
+
# actually prints next to it.
|
|
30
41
|
#
|
|
31
42
|
# Several assignments can cover one line by nesting rather than by
|
|
32
43
|
# ambiguity. `error = assert_raises do ... n = tc.draw_integer(...) ...
|
|
@@ -35,13 +46,16 @@ module Hegel
|
|
|
35
46
|
# whenever another candidate sits inside it. Two assignments written
|
|
36
47
|
# side by side on one line contain neither the other, nothing singles
|
|
37
48
|
# one out, and the answer is nil.
|
|
38
|
-
def for(path, lineno)
|
|
49
|
+
def for(path, lineno, call_names)
|
|
39
50
|
program = parse(path)
|
|
40
51
|
return nil unless program
|
|
41
52
|
|
|
42
53
|
matches = assignment_nodes(program).select { |node| covers?(node.location, lineno) }
|
|
43
54
|
innermost = matches.reject { |node| matches.any? { |other| encloses?(node, other) } }
|
|
44
|
-
innermost.one?
|
|
55
|
+
return nil unless innermost.one?
|
|
56
|
+
|
|
57
|
+
node = innermost.first
|
|
58
|
+
(node.value.is_a?(Prism::CallNode) && call_names.include?(node.value.name)) ? node.name.to_s : nil
|
|
45
59
|
end
|
|
46
60
|
|
|
47
61
|
# Drops every cached parse. #for's only state; a fresh process would
|
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
|
|
@@ -517,7 +523,7 @@ module Hegel
|
|
|
517
523
|
return label if label
|
|
518
524
|
|
|
519
525
|
location = caller_locations(depth, 1)&.first
|
|
520
|
-
(location && DrawName.for(location.path, location.lineno)) || DEFAULT_DRAW_NAME
|
|
526
|
+
(location && DrawName.for(location.path, location.lineno, DRAW_METHOD_NAMES)) || DEFAULT_DRAW_NAME
|
|
521
527
|
end
|
|
522
528
|
end
|
|
523
529
|
end
|
data/lib/hegel/version.rb
CHANGED
data/sig/hegel.rbs
CHANGED
|
@@ -17,6 +17,7 @@ module Hegel
|
|
|
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
|
|
@@ -326,7 +327,7 @@ module Hegel
|
|
|
326
327
|
module DrawName
|
|
327
328
|
ASSIGNMENT_NODE_TYPES: Array[untyped]
|
|
328
329
|
|
|
329
|
-
def self.for: (String path, Integer lineno) -> String?
|
|
330
|
+
def self.for: (String path, Integer lineno, Array[Symbol] call_names) -> String?
|
|
330
331
|
def self.reset_cache: () -> void
|
|
331
332
|
def self.parse: (String path) -> untyped
|
|
332
333
|
def self.cache: () -> Hash[String, untyped]
|
|
@@ -13,16 +13,18 @@
|
|
|
13
13
|
|
|
14
14
|
## Setup
|
|
15
15
|
|
|
16
|
-
Hegel for Ruby is packaged as the `hegeltest` gem
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
`HEGEL_LIBHEGEL_PATH` pointing at a local build:
|
|
16
|
+
Hegel for Ruby is packaged as the `hegeltest` gem. Each platform gem carries
|
|
17
|
+
the matching `libhegel` engine, so an install compiles nothing and needs no
|
|
18
|
+
engine on the side:
|
|
20
19
|
|
|
21
20
|
```ruby
|
|
22
21
|
# Gemfile
|
|
23
|
-
gem "hegeltest"
|
|
22
|
+
gem "hegeltest"
|
|
24
23
|
```
|
|
25
24
|
|
|
25
|
+
A platform with no published `libhegel` build, such as macOS on Intel, needs
|
|
26
|
+
`HEGEL_LIBHEGEL_PATH` pointing at a local build instead.
|
|
27
|
+
|
|
26
28
|
Require it, then include the generator methods wherever tests draw values:
|
|
27
29
|
|
|
28
30
|
```ruby
|
|
@@ -127,7 +129,7 @@ end
|
|
|
127
129
|
|
|
128
130
|
| Keyword | Type | Default | Purpose |
|
|
129
131
|
|---|---|---|---|
|
|
130
|
-
| `test_cases` | `Integer` or `nil` | `nil` (libhegel's own default) | Number of test cases to run |
|
|
132
|
+
| `test_cases` | `Integer` or `nil` | `nil` (libhegel's own default: 100) | Number of test cases to run |
|
|
131
133
|
| `seed` | `Integer` or `nil` | `nil` (libhegel picks its own) | Fixed RNG seed; pair with `derandomize: true` for a reproducible sequence |
|
|
132
134
|
| `derandomize` | `true`/`false` or `nil` | `nil` (libhegel's own default) | Derive the seed deterministically from the test itself, instead of drawing a random one |
|
|
133
135
|
| `verbosity` | `Symbol` or `nil` | `nil` (libhegel's own default) | `:quiet`, `:normal`, `:verbose`, or `:debug` |
|
|
@@ -273,6 +275,22 @@ To reproduce this failure, pass the blob below to Hegel.test:
|
|
|
273
275
|
reproduce_failure: "AAEAAAAACgEAAAAG"
|
|
274
276
|
```
|
|
275
277
|
|
|
278
|
+
An ordinary Ruby method can share a related group of draws. Use a descriptive
|
|
279
|
+
method parameter name so that the default RuboCop naming rule accepts it:
|
|
280
|
+
|
|
281
|
+
```ruby
|
|
282
|
+
def draw_sorted_pair(test_case)
|
|
283
|
+
low = test_case.draw(integers(min_value: 0, max_value: 100))
|
|
284
|
+
high = test_case.draw(integers(min_value: low, max_value: 200))
|
|
285
|
+
[low, high]
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
Hegel.test do |tc|
|
|
289
|
+
low, high = draw_sorted_pair(tc)
|
|
290
|
+
raise "values are out of order" unless low <= high
|
|
291
|
+
end
|
|
292
|
+
```
|
|
293
|
+
|
|
276
294
|
`assume` discards a test case (the same way `reject` does) unless its
|
|
277
295
|
condition holds, read as ordinary Ruby truthiness rather than restricted to
|
|
278
296
|
`true`/`false`, so `tc.assume(hash[:key])` works directly:
|
|
@@ -450,6 +468,39 @@ min_size"`. `alphabet:`, `categories:`, `include_characters:`, and
|
|
|
450
468
|
expose) are not available in this binding; `codec:`, `min_codepoint:`, and
|
|
451
469
|
`max_codepoint:` are the alphabet controls it exposes today.
|
|
452
470
|
|
|
471
|
+
Other generators cover most character-set jobs. These results were measured
|
|
472
|
+
with libhegel 0.32.5 on this repository's main branch:
|
|
473
|
+
|
|
474
|
+
| Job | Generator | Measured output |
|
|
475
|
+
|---|---|---|
|
|
476
|
+
| Characters to include | `from_regex("[ぁ-んA-Z0-9]{1,5}", fullmatch: true)` | `"げLだづは"`, `"あぐA"` |
|
|
477
|
+
| Characters to exclude | `from_regex("[^,\\t\\n]{0,6}", fullmatch: true)` | `"Ë3ù*掆&"`, `"MßzæŜ\u0002"`; 30 draws contained no comma, tab, or newline |
|
|
478
|
+
| A contiguous block | `text(min_codepoint: 0x300, max_codepoint: 0x36F)` | Every character was in U+0300 through U+036F |
|
|
479
|
+
| An arbitrary alphabet | `arrays(sampled_from(%w[ß a İ]), max_size: 4).map(&:join)` | `"İaİß"`, `"ßßß"` |
|
|
480
|
+
|
|
481
|
+
The `categories:` option remains an open request. A category such as any `Lu`
|
|
482
|
+
or any `Mn` spans codepoint ranges that a codepoint bound cannot select. A
|
|
483
|
+
regular expression character class requires those ranges to be written out.
|
|
484
|
+
The `.filter` method is not a substitute. In a measured run of 50 test cases,
|
|
485
|
+
filtering `text` to `\p{Han}` produced no valid inputs. The engine filtered 50
|
|
486
|
+
inputs and reported this health check:
|
|
487
|
+
|
|
488
|
+
```
|
|
489
|
+
FailedHealthCheck: FilterTooMuch — it looks like this test is filtering out
|
|
490
|
+
too many inputs. 50 inputs were filtered out by assume() while only 0 valid
|
|
491
|
+
inputs were generated. If this is expected, suppress the check with
|
|
492
|
+
suppress_health_check = [HealthCheck::FilterTooMuch].
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
The engine writes that last sentence in its own naming. This binding takes
|
|
496
|
+
the same setting as `suppress_health_check: [:filter_too_much]`, listed in
|
|
497
|
+
[Settings](#settings).
|
|
498
|
+
|
|
499
|
+
Unconfigured `text` draws from a much wider Unicode range. In 200 draws of
|
|
500
|
+
`text(min_size: 1, max_size: 4)`, the results included emoji, private-use
|
|
501
|
+
characters, and control characters. The highest codepoint was U+10F11A. A
|
|
502
|
+
property that accepts any string needs no character-set options.
|
|
503
|
+
|
|
453
504
|
### `arrays(elements, min_size: 0, max_size: nil)`
|
|
454
505
|
|
|
455
506
|
An `Array` of values from the `elements` generator, with `[min_size,
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: hegeltest
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- meganemura
|
|
@@ -59,6 +59,7 @@ files:
|
|
|
59
59
|
- docs/adr/0011-let-the-test-case-own-every-pool-drawn-from-it.md
|
|
60
60
|
- docs/adr/0012-build-a-failure-origin-from-the-callers-own-frame.md
|
|
61
61
|
- docs/adr/0013-bind-libhegel-through-the-ffi-gem.md
|
|
62
|
+
- docs/adr/0014-name-a-drawn-value-only-when-the-draw-is-the-whole-assigned-value.md
|
|
62
63
|
- docs/architecture.md
|
|
63
64
|
- lib/hegel.rb
|
|
64
65
|
- lib/hegel/draw_name.rb
|