Hegel 0.11.4
Property-based testing for C++
Loading...
Searching...
No Matches
core.h
1#pragma once
2
3#include <functional>
4#include <memory>
5#include <optional>
6#include <stdexcept>
7#include <type_traits>
8
9#include "config.h"
10#include "internal.h"
11#include "repr.h"
12#include "test_case.h"
13
18
19 template <typename T> class CompositeGenerator;
20 template <typename T, typename U> class MappedGenerator;
21
29 template <typename T> struct IGenerator {
30 IGenerator() {}
31 virtual ~IGenerator() = default;
32
34 // Every generator produces its value by driving the engine's typed
35 // draw primitives (hegel::internal::draw_*) against the test case.
36 virtual T do_draw(const TestCase& tc) const = 0;
38 };
39
67 template <typename T> class Generator {
68 public:
70 using value_type = T;
71
73 Generator(IGenerator<T>* p) : inner_(p) {}
74 Generator(std::shared_ptr<IGenerator<T>> p) : inner_(std::move(p)) {}
75
76 T do_draw(const TestCase& tc) const { return inner_->do_draw(tc); }
78
102 template <typename F>
104 using ResultType = std::invoke_result_t<F, T>;
106 new MappedGenerator<T, ResultType>(inner_, std::forward<F>(f)));
107 }
108
130 template <typename F> std::invoke_result_t<F, T> flat_map(F&& f) const {
131 // F: T -> Generator<ResultType>; flat_map returns the same
132 // Generator<ResultType>.
133 using ResultType = typename std::invoke_result_t<F, T>::value_type;
134 auto inner = inner_;
135 return compose([inner, f = std::forward<F>(f)](
136 const TestCase& tc) -> ResultType {
137 internal::start_span(tc, internal::SpanLabel::FlatMap);
138 ResultType result = f(inner->do_draw(tc)).do_draw(tc);
139 internal::stop_span(tc);
140 return result;
141 });
142 }
143
169 Generator<T> filter(std::function<bool(const T&)> pred) const {
170 auto inner = inner_;
171 return compose([inner, pred](const TestCase& tc) -> T {
172 for (int i = 0; i < 3; ++i) {
173 internal::start_span(tc, internal::SpanLabel::Filter);
174 T value = inner->do_draw(tc);
175 if (pred(value)) {
176 internal::stop_span(tc);
177 return value;
178 }
179 // Discard the rejected span so the engine retries from
180 // before it opened.
181 internal::stop_span(tc, true);
182 }
183 tc.assume(false);
184 // unreachable: assume(false) throws
185 throw internal::HegelReject();
186 });
187 }
188
189 private:
190 std::shared_ptr<IGenerator<T>> inner_;
191 };
192
194 // Generator that produces values by invoking a user-provided function.
195 // Users never construct or reference this type directly; it's produced
196 // internally by compose() and by map()/flat_map()/filter().
197 template <typename T> class CompositeGenerator : public IGenerator<T> {
198 public:
199 explicit CompositeGenerator(std::function<T(const TestCase&)> fn)
200 : gen_fn_(std::move(fn)) {}
201
202 T do_draw(const TestCase& tc) const override { return gen_fn_(tc); }
203
204 private:
205 std::function<T(const TestCase&)> gen_fn_;
206 };
208
210 // Generator that applies a client-side transformation to values drawn
211 // from a source generator. Produced internally by Generator<T>::map().
212 template <typename T, typename U>
213 class MappedGenerator : public IGenerator<U> {
214 public:
215 MappedGenerator(std::shared_ptr<IGenerator<T>> source,
216 std::function<U(T)> f)
217 : source_(std::move(source)), f_(std::move(f)) {}
218
219 U do_draw(const TestCase& tc) const override {
220 internal::start_span(tc, internal::SpanLabel::Mapped);
221 U result = f_(source_->do_draw(tc));
222 internal::stop_span(tc);
223 return result;
224 }
225
226 private:
227 std::shared_ptr<IGenerator<T>> source_;
228 std::function<U(T)> f_;
229 };
231
253 template <typename F> auto compose(F&& fn) {
254 using T = std::invoke_result_t<F, const TestCase&>;
256 std::function<T(const TestCase&)>(std::forward<F>(fn))));
257 }
258
259} // namespace hegel::generators
260
261namespace hegel {
262
263 template <typename T>
265 return draw(std::string_view(), gen);
266 }
267
268 template <typename T>
269 T TestCase::draw(std::string_view name, const generators::Generator<T>& gen,
270 bool repeatable) const {
271 internal::DrawLogScope scope(*this, name, repeatable);
272 T value = gen.do_draw(*this);
273 if (scope.should_log()) {
274 scope.log(internal::repr(value));
275 }
276 return value;
277 }
278
279} // namespace hegel
Handle to the currently-executing test case.
Definition test_case.h:40
void assume(bool condition) const
Reject the current test case if condition is false.
T draw(const generators::Generator< T > &gen) const
Draw a random value from a generator.
Definition core.h:264
The base class of all generators.
Definition core.h:67
Generator< std::invoke_result_t< F, T > > map(F &&f) const
*‍/
Definition core.h:103
Generator< T > filter(std::function< bool(const T &)> pred) const
Filter generated values by a predicate.
Definition core.h:169
std::invoke_result_t< F, T > flat_map(F &&f) const
Chain generators for dependent generation.
Definition core.h:130
T value_type
The type of value this generator produces.
Definition core.h:70
Hegel generators.
Definition core.h:17
auto compose(F &&fn)
*‍/
Definition core.h:253
Main namespace.
Definition config.h:29
Base interface for generators.
Definition core.h:29