hegeltest 0.3.4 → 0.4.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 +221 -0
- data/README.ja.md +408 -0
- data/README.md +208 -102
- data/Rakefile +6 -3
- data/docs/README.md +11 -0
- data/docs/adr/0027-report-each-runs-test-location-from-the-callers-frame.md +63 -0
- data/docs/adr/0028-expose-settings-profiles-as-a-keyword-and-module-functions.md +91 -0
- data/docs/adr/0029-keep-notes-local-and-defer-run-error-kinds.md +72 -0
- data/docs/adr/0030-represent-a-duration-as-integer-nanoseconds.md +48 -0
- data/docs/adr/0031-take-the-alphabet-options-from-hegel-rust.md +52 -0
- data/docs/adr/0032-suppress-span-closes-while-an-abandoned-recursion-attempt-unwinds.md +147 -0
- data/docs/adr/0033-cache-compiled-grammars-for-the-length-of-a-run.md +80 -0
- data/docs/adr/0034-repeat-a-test-body-with-a-ruby-side-indent.md +120 -0
- data/docs/adr/0035-defer-concurrent-state-machines-and-the-engine-printer.md +136 -0
- data/docs/adr/0036-follow-rubys-unicode-version.md +50 -0
- data/docs/adr/0037-send-a-maximum-at-the-engine-default-sentinel-as-no-bound.md +48 -0
- data/docs/architecture.md +16 -1
- data/lib/hegel/errors.rb +24 -0
- data/lib/hegel/generator.rb +52 -25
- data/lib/hegel/generators.rb +715 -45
- data/lib/hegel/grammar_cache.rb +36 -0
- data/lib/hegel/lib_hegel/real.rb +333 -19
- data/lib/hegel/lib_hegel.rb +63 -6
- data/lib/hegel/libhegel_version.rb +1 -1
- data/lib/hegel/report.rb +13 -10
- data/lib/hegel/runner.rb +61 -27
- data/lib/hegel/settings.rb +92 -16
- data/lib/hegel/state_machine.rb +17 -4
- data/lib/hegel/stateful/pool.rb +52 -0
- data/lib/hegel/stateful.rb +2 -2
- data/lib/hegel/syntax/methods.rb +99 -15
- data/lib/hegel/test_case.rb +259 -37
- data/lib/hegel/test_location.rb +49 -0
- data/lib/hegel/version.rb +1 -1
- data/lib/hegel.rb +21 -5
- data/sig/hegel.rbs +300 -42
- data/skills/hegel-ruby/references/ruby/reference.md +463 -41
- metadata +15 -1
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "runner"
|
|
4
|
+
|
|
5
|
+
module Hegel
|
|
6
|
+
# Where a Hegel.test call lives, in the four pieces
|
|
7
|
+
# hegel_settings_set_test_location takes. Under Antithesis the engine
|
|
8
|
+
# reports each run's verdict as the assertion
|
|
9
|
+
# "<class_name>::<function> passes properties" at file:begin_line, so two
|
|
10
|
+
# calls that share a class_name and a function share one assertion.
|
|
11
|
+
TestLocation = Data.define(:file, :begin_line, :class_name, :function)
|
|
12
|
+
|
|
13
|
+
class TestLocation
|
|
14
|
+
# Matches the label prefix Ruby 3.4 puts on a frame inside a block, so
|
|
15
|
+
# that the method underneath names the test.
|
|
16
|
+
BLOCK_PREFIX = /\A(?:block(?: \(\d+ levels\))? in )+/
|
|
17
|
+
|
|
18
|
+
# Matches "Class#method" and "Class.method". Greedy, so a namespaced
|
|
19
|
+
# class keeps its "::" and a "#<Class:0x...>" label splits at its last
|
|
20
|
+
# separator.
|
|
21
|
+
METHOD_LABEL = /\A(.+)[#.]([^#.]+)\z/
|
|
22
|
+
|
|
23
|
+
# The first of +locations+ that is the caller's own, by the same rule
|
|
24
|
+
# Runner.origin_for uses to name a failure (see ADR 0012), so a wrapper in
|
|
25
|
+
# this gem, a gem, or the standard library is not named as the test.
|
|
26
|
+
# +locations+ is never empty, because the frame of Hegel.test's own caller
|
|
27
|
+
# exists even at the top of a script.
|
|
28
|
+
def self.from_locations(locations)
|
|
29
|
+
location = locations.find { |candidate| !Runner.infrastructure?(candidate.path) } || locations.first
|
|
30
|
+
path = location.absolute_path || location.path
|
|
31
|
+
root = File.join(Dir.pwd, "")
|
|
32
|
+
file = path.start_with?(root) ? path.delete_prefix(root) : path
|
|
33
|
+
class_name, function = names(location.label, file, location.lineno)
|
|
34
|
+
new(file: file, begin_line: location.lineno, class_name: class_name, function: function)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# [class_name, function] for a frame +label+. A frame with no enclosing
|
|
38
|
+
# method (a script body, or an RSpec example, whose block sits at the top
|
|
39
|
+
# of its file) has no name of its own, so it takes its file and line:
|
|
40
|
+
# that keeps two examples in one file from sharing one assertion.
|
|
41
|
+
def self.names(label, file, lineno)
|
|
42
|
+
match = METHOD_LABEL.match(label.sub(BLOCK_PREFIX, ""))
|
|
43
|
+
return match.captures if match
|
|
44
|
+
|
|
45
|
+
[file.delete_suffix(".rb"), "line #{lineno}"]
|
|
46
|
+
end
|
|
47
|
+
private_class_method :names
|
|
48
|
+
end
|
|
49
|
+
end
|
data/lib/hegel/version.rb
CHANGED
data/lib/hegel.rb
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require_relative "hegel/version"
|
|
4
4
|
require_relative "hegel/errors"
|
|
5
5
|
require_relative "hegel/runner"
|
|
6
|
+
require_relative "hegel/test_location"
|
|
6
7
|
require_relative "hegel/generator"
|
|
7
8
|
require_relative "hegel/generators"
|
|
8
9
|
require_relative "hegel/syntax/methods"
|
|
@@ -28,6 +29,13 @@ module Hegel
|
|
|
28
29
|
# assertion failure rather than as a Hegel-specific one. Raises
|
|
29
30
|
# Hegel::Error for a run-level failure instead of a property failure.
|
|
30
31
|
#
|
|
32
|
+
# +profile+ names the settings profile the run starts from: one of the
|
|
33
|
+
# engine's shipped profiles, one in hegel.toml, or one registered with
|
|
34
|
+
# Hegel::Settings.register_profile. Left nil, the run starts from the
|
|
35
|
+
# default profile. A keyword passed alongside still beats the profile, and
|
|
36
|
+
# report_multiple_failures is always passed, so a profile's value for it
|
|
37
|
+
# never applies (see docs/adr/0028). An unknown name raises Hegel::Error.
|
|
38
|
+
#
|
|
31
39
|
# +test_cases+, +seed+, +derandomize+, +verbosity+, +phases+, and
|
|
32
40
|
# +suppress_health_check+ all default to nil, which means the same thing
|
|
33
41
|
# for each of them: do not call the matching libhegel setter, and let the
|
|
@@ -54,6 +62,11 @@ module Hegel
|
|
|
54
62
|
# keyword above: see Hegel::Runner.run's own comment for why it is always
|
|
55
63
|
# passed.
|
|
56
64
|
#
|
|
65
|
+
# +stop_on_first_failure+, +print_blob+, +show_statistics+,
|
|
66
|
+
# +nondeterminism_strictness+, and +backend+ default to nil, so the engine's
|
|
67
|
+
# profile decides as it does for +test_cases+; they follow hegel-rust's
|
|
68
|
+
# Settings.
|
|
69
|
+
#
|
|
57
70
|
# +output+ (default $stderr) is where a failure report is written; a
|
|
58
71
|
# caller passes its own IO to capture that report instead (tests do).
|
|
59
72
|
#
|
|
@@ -66,13 +79,16 @@ module Hegel
|
|
|
66
79
|
# real engine. Ordinary callers never pass it. Its default expression
|
|
67
80
|
# (#default_impl) only runs when +impl+ is not given, so a test that does
|
|
68
81
|
# pass one never opens the native library at all.
|
|
69
|
-
def test(test_cases: nil, seed: nil, derandomize: nil, verbosity: nil, database: nil, database_key: nil,
|
|
70
|
-
phases: nil, suppress_health_check: nil, report_multiple_failures: false,
|
|
82
|
+
def test(profile: nil, test_cases: nil, seed: nil, derandomize: nil, verbosity: nil, database: nil, database_key: nil,
|
|
83
|
+
phases: nil, suppress_health_check: nil, report_multiple_failures: false, stop_on_first_failure: nil,
|
|
84
|
+
print_blob: nil, show_statistics: nil, nondeterminism_strictness: nil, backend: nil, output: $stderr,
|
|
71
85
|
reproduce_failure: nil, impl: default_impl, &block)
|
|
72
|
-
Runner.run(impl: impl, test_cases: test_cases, seed: seed, derandomize: derandomize, verbosity: verbosity,
|
|
86
|
+
Runner.run(impl: impl, profile: profile, test_cases: test_cases, seed: seed, derandomize: derandomize, verbosity: verbosity,
|
|
73
87
|
database: database, database_key: database_key, phases: phases, suppress_health_check: suppress_health_check,
|
|
74
|
-
report_multiple_failures: report_multiple_failures,
|
|
75
|
-
|
|
88
|
+
report_multiple_failures: report_multiple_failures, stop_on_first_failure: stop_on_first_failure,
|
|
89
|
+
print_blob: print_blob, show_statistics: show_statistics, nondeterminism_strictness: nondeterminism_strictness,
|
|
90
|
+
backend: backend, output: output, reproduce_failure: reproduce_failure,
|
|
91
|
+
test_location: TestLocation.from_locations(caller_locations(1)), &block)
|
|
76
92
|
end
|
|
77
93
|
|
|
78
94
|
# The Hegel::LibHegel::Real instance #test uses by default, built once and
|