Hegel 0.11.4
Property-based testing for C++
Loading...
Searching...
No Matches
combinators.h
1#pragma once
2
3#include <cstdint>
4#include <initializer_list>
5#include <memory>
6#include <optional>
7#include <stdexcept>
8#include <string>
9#include <tuple>
10#include <variant>
11#include <vector>
12
13#include "hegel/core.h"
14
15namespace hegel::generators {
16
18 // Concrete IGenerator for sampled_from(). Draws an index into the
19 // captured `elements_` vector and returns that element.
20 template <typename T> class SampledFromGenerator : public IGenerator<T> {
21 public:
22 explicit SampledFromGenerator(std::vector<T> elements)
23 : elements_(std::move(elements)) {
24 if (elements_.empty()) {
25 throw std::invalid_argument(
26 "sampled_from requires a non-empty vector");
27 }
28 }
29
30 T do_draw(const TestCase& tc) const override {
31 int64_t index = hegel::internal::draw_integer(
32 tc, 0, static_cast<int64_t>(elements_.size() - 1));
33 return elements_[static_cast<size_t>(index)];
34 }
35
36 private:
37 std::vector<T> elements_;
38 };
39
40 // Concrete IGenerator for one_of(). Draws a branch index inside a
41 // one_of span, then delegates to that branch.
42 template <typename T> class OneOfGenerator : public IGenerator<T> {
43 public:
44 explicit OneOfGenerator(std::vector<Generator<T>> gens)
45 : gens_(std::move(gens)) {
46 if (gens_.empty()) {
47 throw std::invalid_argument(
48 "one_of requires a non-empty vector of generators");
49 }
50 }
51
52 T do_draw(const TestCase& tc) const override {
53 namespace hi = hegel::internal;
54 hi::start_span(tc, hi::SpanLabel::OneOf);
55 int64_t index =
56 hi::draw_integer(tc, 0, static_cast<int64_t>(gens_.size() - 1));
57 T result = gens_[static_cast<size_t>(index)].do_draw(tc);
58 hi::stop_span(tc);
59 return result;
60 }
61
62 private:
63 std::vector<Generator<T>> gens_;
64 };
66
69
82 template <typename T>
83 Generator<T> sampled_from(const std::vector<T>& elements) {
84 return Generator<T>(new SampledFromGenerator<T>(elements));
85 }
86
93 template <typename T>
94 Generator<T> sampled_from(std::initializer_list<T> elements) {
95 return sampled_from(std::vector<T>(elements));
96 }
97
103 inline Generator<std::string>
104 sampled_from(std::initializer_list<const char*> elements) {
105 std::vector<std::string> strings;
106 strings.reserve(elements.size());
107 for (const char* s : elements) {
108 strings.push_back(s);
109 }
110 return sampled_from(strings);
111 }
112
114
117
134 template <typename T> Generator<T> one_of(std::vector<Generator<T>> gens) {
135 return Generator<T>(new OneOfGenerator<T>(std::move(gens)));
136 }
137
144 template <typename T>
145 Generator<T> one_of(std::initializer_list<Generator<T>> gens) {
146 return one_of(std::vector<Generator<T>>(gens));
147 }
148
150 namespace detail {
151
152 template <typename Variant, typename GenTuple, size_t I = 0>
153 Variant draw_variant_impl(const GenTuple& gens, size_t idx,
154 const TestCase& tc) {
155 if constexpr (I < std::tuple_size_v<GenTuple>) {
156 if (idx == I) {
157 return Variant{std::in_place_index<I>,
158 std::get<I>(gens).do_draw(tc)};
159 }
160 return draw_variant_impl<Variant, GenTuple, I + 1>(gens, idx,
161 tc);
162 } else {
163 // Unreachable: idx is always in [0, N), so an earlier branch
164 // matches before the recursion bottoms out.
165 return Variant{}; // GCOVR_EXCL_LINE
166 }
167 }
168
169 } // namespace detail
170
171 // Concrete IGenerator for variant(). Draws a branch index inside a
172 // one_of span; branches can have heterogeneous types.
173 template <typename... Ts>
174 class VariantGenerator : public IGenerator<std::variant<Ts...>> {
175 public:
176 using Result = std::variant<Ts...>;
177
178 explicit VariantGenerator(Generator<Ts>... gens)
179 : gens_(std::move(gens)...) {}
180
181 Result do_draw(const TestCase& tc) const override {
182 namespace hi = hegel::internal;
183 constexpr size_t N = sizeof...(Ts);
184 hi::start_span(tc, hi::SpanLabel::OneOf);
185 int64_t index =
186 hi::draw_integer(tc, 0, static_cast<int64_t>(N - 1));
187 Result result = detail::draw_variant_impl<Result, decltype(gens_)>(
188 gens_, static_cast<size_t>(index), tc);
189 hi::stop_span(tc);
190 return result;
191 }
192
193 private:
194 std::tuple<Generator<Ts>...> gens_;
195 };
196
197 // Concrete IGenerator for optional(). A boolean draw inside an
198 // optional span gates presence; false shrinks toward nullopt.
199 template <typename T>
200 class OptionalGenerator : public IGenerator<std::optional<T>> {
201 public:
202 explicit OptionalGenerator(Generator<T> gen) : gen_(std::move(gen)) {}
203
204 std::optional<T> do_draw(const TestCase& tc) const override {
205 namespace hi = hegel::internal;
206 hi::start_span(tc, hi::SpanLabel::Optional);
207 std::optional<T> result;
208 if (hi::draw_boolean(tc, 0.5)) {
209 result = gen_.do_draw(tc);
210 }
211 hi::stop_span(tc);
212 return result;
213 }
214
215 private:
216 Generator<T> gen_;
217 };
219
234 template <typename... Ts>
235 Generator<std::variant<Ts...>> variant(Generator<Ts>... gens) {
236 return Generator<std::variant<Ts...>>(
237 new VariantGenerator<Ts...>(std::move(gens)...));
238 }
239
255 template <typename T>
258 new OptionalGenerator<T>(std::move(gen)));
259 }
260
262
264 // Generator that delegates to an implementation supplied later through a
265 // shared slot. Handed out by DeferredGeneratorDefinition::generator().
266 template <typename T> class DeferredGenerator : public IGenerator<T> {
267 public:
268 explicit DeferredGenerator(
269 std::shared_ptr<std::optional<Generator<T>>> slot)
270 : slot_(std::move(slot)) {}
271
272 T do_draw(const TestCase& tc) const override {
273 if (!*slot_) {
274 throw std::runtime_error(
275 "deferred generator drawn before set() was called");
276 }
277 return (*slot_)->do_draw(tc);
278 }
279
280 private:
281 std::shared_ptr<std::optional<Generator<T>>> slot_;
282 };
284
296 template <typename T> class DeferredGeneratorDefinition {
297 public:
298 DeferredGeneratorDefinition()
299 : slot_(std::make_shared<std::optional<Generator<T>>>()) {}
300
310 return Generator<T>(new DeferredGenerator<T>(slot_));
311 }
312
322 void set(Generator<T> gen) {
323 if (*slot_) {
324 throw std::runtime_error(
325 "deferred generator set() called more than once");
326 }
327 *slot_ = std::move(gen);
328 }
329
330 private:
331 std::shared_ptr<std::optional<Generator<T>>> slot_;
332 };
333
352 template <typename T> DeferredGeneratorDefinition<T> deferred() {
354 }
355
356} // namespace hegel::generators
Handle to the currently-executing test case.
Definition test_case.h:40
A forward reference to a generator whose definition is provided later.
Definition combinators.h:296
Generator< T > generator() const
Return a handle that delegates to whatever is later passed to set(). May be called multiple times....
Definition combinators.h:309
void set(Generator< T > gen)
Set the implementation for this deferred generator.
Definition combinators.h:322
The base class of all generators.
Definition core.h:67
Hegel generators.
Definition core.h:17
DeferredGeneratorDefinition< T > deferred()
Create a deferred generator definition for forward references.
Definition combinators.h:352
Generator< T > sampled_from(const std::vector< T > &elements)
Sample from a fixed set of values.
Definition combinators.h:83
Generator< std::variant< Ts... > > variant(Generator< Ts >... gens)
Generate std::variant from heterogeneous generators.
Definition combinators.h:235
Generator< std::optional< T > > optional(Generator< T > gen)
Generate optional values (present or absent).
Definition combinators.h:256
Generator< T > one_of(std::vector< Generator< T > > gens)
Choose from multiple generators of the same type.
Definition combinators.h:134
Base interface for generators.
Definition core.h:29