Hegel 0.11.4
Property-based testing for C++
Loading...
Searching...
No Matches
internal.h
1#pragma once
2
3#include "test_case.h"
4
5#include <cstdint>
6#include <exception>
7#include <optional>
8#include <string>
9#include <string_view>
10#include <vector>
11
13// opaque libhegel handle types.
14struct hegel_collection_t;
15struct hegel_pool_t;
16struct hegel_state_machine_t;
18
22namespace hegel::internal {
24
25 /* Typed draw primitives over libhegel's C ABI. Declared here (rather
26 * than in a private header) because the generator templates in the
27 * public headers call them from their do_draw implementations. Each
28 * throws HegelStopTest when the engine's choice budget is exhausted,
29 * HegelReject when the draw rejects itself, and std::runtime_error on
30 * any other engine failure.
31 */
32
33 // Span labels understood by the engine's shrinker. Values mirror the
34 // C ABI's hegel_label_t (static_asserted in src/engine.cpp).
35 enum class SpanLabel : uint64_t {
36 List = 1,
37 ListElement = 2,
38 Set = 3,
39 SetElement = 4,
40 Map = 5,
41 MapEntry = 6,
42 Tuple = 7,
43 OneOf = 8,
44 Optional = 9,
45 FlatMap = 11,
46 Filter = 12,
47 Mapped = 13,
48 SampledFrom = 14,
49 EnumVariant = 15,
50 StatefulRule = 31
51 };
52
53 // Open / close a labeled span around a group of draws so the shrinker
54 // can treat them as a unit. stop_span(tc, true) marks the span rejected
55 // (e.g. a filter predicate failed) so the engine retries from before it.
56 void start_span(const TestCase& tc, SpanLabel label);
57 void stop_span(const TestCase& tc, bool discard = false);
58
59 int64_t draw_integer(const TestCase& tc, int64_t min_value,
60 int64_t max_value);
61 // Uses the engine's big-integer draw when the range exceeds int64_t.
62 uint64_t draw_integer_unsigned(const TestCase& tc, uint64_t min_value,
63 uint64_t max_value);
64 bool draw_boolean(const TestCase& tc, double p,
65 std::optional<bool> forced = std::nullopt);
66 double draw_float(const TestCase& tc, uint32_t width, double min_value,
67 double max_value, bool allow_nan, bool allow_infinity,
68 bool exclude_min, bool exclude_max,
69 double smallest_nonzero_magnitude);
70
71 inline constexpr uint64_t no_max_size = ~uint64_t{0};
72 class CollectionHandle {
73 public:
74 CollectionHandle(const TestCase& tc, uint64_t min_size,
75 uint64_t max_size);
76 ~CollectionHandle();
77 CollectionHandle(const CollectionHandle&) = delete;
78 CollectionHandle& operator=(const CollectionHandle&) = delete;
79
80 bool more(const TestCase& tc);
81
82 void reject(const TestCase& tc, const char* why);
83
84 private:
85 hegel_collection_t* handle_ = nullptr;
86 };
87
88 class PoolHandle {
89 public:
90 explicit PoolHandle(const TestCase& tc);
91 ~PoolHandle();
92 PoolHandle(const PoolHandle&) = delete;
93 PoolHandle& operator=(const PoolHandle&) = delete;
94
95 // Returns a fresh variable id.
96 int64_t add(const TestCase& tc);
97 // Returns the variable id the engine picked, removing it from the
98 // pool when consume is true.
99 int64_t draw_variable(const TestCase& tc, bool consume);
100
101 private:
102 hegel_pool_t* handle_ = nullptr;
103 };
104
105 inline constexpr int64_t state_machine_done = -1;
106 class StateMachineHandle {
107 public:
108 StateMachineHandle(const TestCase& tc,
109 const std::vector<std::string>& rule_names,
110 const std::vector<std::string>& invariant_names);
111 ~StateMachineHandle();
112 StateMachineHandle(const StateMachineHandle&) = delete;
113 StateMachineHandle& operator=(const StateMachineHandle&) = delete;
114
115 // Returns the index of the next rule to run, or state_machine_done
116 // once the engine's step budget for this test case is used up.
117 int64_t next_rule(const TestCase& tc);
118 // Report that an assumption failed in the last rule, so it does not
119 // count against the step budget.
120 void rule_rejected(const TestCase& tc);
121
122 private:
123 hegel_state_machine_t* handle_ = nullptr;
124 };
125
126 class NoteIndentScope {
127 public:
128 explicit NoteIndentScope(const TestCase& tc);
129 ~NoteIndentScope();
130 NoteIndentScope(const NoteIndentScope&) = delete;
131 NoteIndentScope& operator=(const NoteIndentScope&) = delete;
132
133 private:
134 const TestCase& tc_;
135 };
136
137 class DrawLogScope {
138 public:
139 DrawLogScope(const TestCase& tc, std::string_view name,
140 bool repeatable);
141 ~DrawLogScope();
142 DrawLogScope(const DrawLogScope&) = delete;
143 DrawLogScope& operator=(const DrawLogScope&) = delete;
144
145 bool should_log() const;
146
147 void log(const std::string& rendered) const;
148
149 private:
150 const TestCase& tc_;
151 bool outermost_;
152 std::string display_;
153 };
154 /* Exception thrown when a test case is rejected and should be
155 * discarded (e.g. by `TestCase::assume(false)`, an exhausted
156 * `filter()`, or an `UnsatisfiedAssumption` from the engine).
157 * The runner records the case as INVALID and continues.
158 */
159 class HegelReject : public std::exception {
160 public:
161 const char* what() const noexcept override {
162 return "test case rejected";
163 }
164 };
165
166 /* Exception thrown when the backend tells us to abandon the current
167 * test iteration entirely (StopTest, Overflow, FlakyStrategyDefinition,
168 * FlakyReplay). The runner unwinds the test body and skips
169 * `mark_complete` — the engine already knows the iteration is over.
170 */
171 class HegelStopTest : public std::exception {
172 public:
173 const char* what() const noexcept override {
174 return "test case stopped by backend";
175 }
176 };
177
178} // namespace hegel::internal
179