Hegel 0.3.5
Property-based testing for C++
Loading...
Searching...
No Matches
collections.h
1#pragma once
2
3#include <map>
4#include <set>
5#include <tuple>
6#include <vector>
7
8#include "hegel/core.h"
9#include "hegel/generators/numeric.h"
10
11namespace hegel::generators {
12
13 // =============================================================================
14 // Parameter structs
15 // =============================================================================
16
21 size_t min_size = 0;
22 std::optional<size_t> max_size;
23 bool unique = false;
24 };
25
29 struct SetsParams {
30 size_t min_size = 0;
31 std::optional<size_t> max_size;
32 };
33
37 struct MapsParams {
38 size_t min_size = 0;
39 std::optional<size_t>
41 };
42
44 // Concrete IGenerator for vectors(). Schema path when the element
45 // generator is basic; otherwise a client-side length draw + element
46 // draws loop.
47 template <typename T>
48 class VectorsGenerator : public IGenerator<std::vector<T>> {
49 public:
50 VectorsGenerator(Generator<T> elements, VectorsParams params = {})
51 : elements_(std::move(elements)), params_(params) {
52 if (params_.max_size && params_.min_size > *params_.max_size) {
53 throw std::invalid_argument("Cannot have max_size < min_size");
54 }
55 }
56
57 std::optional<BasicGenerator<std::vector<T>>>
58 as_basic() const override {
59 auto basic = elements_.as_basic();
60 if (!basic)
61 return std::nullopt;
62
63 hegel::internal::json::json schema = {
64 {"type", "list"},
65 {"elements", basic->schema},
66 {"min_size", params_.min_size},
67 {"unique", params_.unique}};
68 if (params_.max_size)
69 schema["max_size"] = *params_.max_size;
70
71 auto parse = basic->parse;
72 return BasicGenerator<std::vector<T>>{
73 std::move(schema),
74 [parse = std::move(parse)](
75 const hegel::internal::json::json_raw_ref& raw)
76 -> std::vector<T> {
77 std::vector<T> result;
78 auto items = raw.iterate();
79 result.reserve(items.size());
80 for (const auto& item : items) {
81 result.push_back(parse(item));
82 }
83 return result;
84 }};
85 }
86
87 std::vector<T> do_draw(const TestCase& tc) const override {
88 if (auto basic = as_basic()) {
89 return basic->do_draw(tc);
90 }
91 size_t max_size = params_.max_size.value_or(100);
92 auto length_gen = integers<size_t>(
93 {.min_value = params_.min_size, .max_value = max_size});
94 size_t len = length_gen.do_draw(tc);
95 std::vector<T> result;
96 result.reserve(len);
97 for (size_t i = 0; i < len; ++i) {
98 result.push_back(elements_.do_draw(tc));
99 }
100 return result;
101 }
102
103 private:
104 Generator<T> elements_;
105 VectorsParams params_;
106 };
107
108 // Concrete IGenerator for sets(). Schema path uses the list schema with
109 // unique=true; fallback uniques are enforced by std::set semantics.
110 template <typename T> class SetsGenerator : public IGenerator<std::set<T>> {
111 public:
112 SetsGenerator(Generator<T> elements, SetsParams params = {})
113 : elements_(std::move(elements)), params_(params) {
114 if (params_.max_size && params_.min_size > *params_.max_size) {
115 throw std::invalid_argument("Cannot have max_size < min_size");
116 }
117 }
118
119 std::optional<BasicGenerator<std::set<T>>> as_basic() const override {
120 auto basic = elements_.as_basic();
121 if (!basic)
122 return std::nullopt;
123
124 hegel::internal::json::json schema = {
125 {"type", "list"},
126 {"elements", basic->schema},
127 {"min_size", params_.min_size},
128 {"unique", true}};
129 if (params_.max_size)
130 schema["max_size"] = *params_.max_size;
131
132 auto parse = basic->parse;
133 return BasicGenerator<std::set<T>>{
134 std::move(schema),
135 [parse = std::move(parse)](
136 const hegel::internal::json::json_raw_ref& raw)
137 -> std::set<T> {
138 std::set<T> result;
139 for (const auto& item : raw.iterate()) {
140 result.insert(parse(item));
141 }
142 return result;
143 }};
144 }
145
146 std::set<T> do_draw(const TestCase& tc) const override {
147 if (auto basic = as_basic()) {
148 return basic->do_draw(tc);
149 }
150 size_t max_size = params_.max_size.value_or(20);
151 auto length_gen = integers<size_t>(
152 {.min_value = params_.min_size, .max_value = max_size});
153 size_t target_len = length_gen.do_draw(tc);
154 std::set<T> result;
155 size_t attempts = 0;
156 while (result.size() < target_len && attempts < target_len * 10) {
157 result.insert(elements_.do_draw(tc));
158 ++attempts;
159 }
160 return result;
161 }
162
163 private:
164 Generator<T> elements_;
165 SetsParams params_;
166 };
167
168 // Concrete IGenerator for maps(). Schema path requires both keys and
169 // values to be basic; fallback draws K/V and inserts until `len`
170 // unique keys are produced.
171 template <typename K, typename V>
172 class MapsGenerator : public IGenerator<std::map<K, V>> {
173 public:
174 MapsGenerator(Generator<K> keys, Generator<V> values,
175 MapsParams params = {})
176 : keys_(std::move(keys)), values_(std::move(values)),
177 params_(params) {
178 if (params_.max_size && params_.min_size > *params_.max_size) {
179 throw std::invalid_argument("Cannot have max_size < min_size");
180 }
181 }
182
183 std::optional<BasicGenerator<std::map<K, V>>>
184 as_basic() const override {
185 auto k_basic = keys_.as_basic();
186 auto v_basic = values_.as_basic();
187 if (!k_basic || !v_basic)
188 return std::nullopt;
189
190 hegel::internal::json::json schema = {
191 {"type", "dict"},
192 {"keys", k_basic->schema},
193 {"values", v_basic->schema},
194 {"min_size", params_.min_size}};
195 if (params_.max_size)
196 schema["max_size"] = *params_.max_size;
197
198 auto kp = k_basic->parse;
199 auto vp = v_basic->parse;
200 // Wire format is [[key, value], ...]
201 return BasicGenerator<std::map<K, V>>{
202 std::move(schema),
203 [kp = std::move(kp), vp = std::move(vp)](
204 const hegel::internal::json::json_raw_ref& raw)
205 -> std::map<K, V> {
206 std::map<K, V> result;
207 for (const auto& pair : raw.iterate()) {
208 result.emplace(kp(pair[0]), vp(pair[1]));
209 }
210 return result;
211 }};
212 }
213
214 std::map<K, V> do_draw(const TestCase& tc) const override {
215 if (auto basic = as_basic()) {
216 return basic->do_draw(tc);
217 }
218 size_t max_size = params_.max_size.value_or(20);
219 auto length_gen = integers<size_t>(
220 {.min_value = params_.min_size, .max_value = max_size});
221 size_t len = length_gen.do_draw(tc);
222 std::map<K, V> result;
223 while (result.size() < len) {
224 K key = keys_.do_draw(tc);
225 V value = values_.do_draw(tc);
226 result[std::move(key)] = std::move(value);
227 }
228 return result;
229 }
230
231 private:
232 Generator<K> keys_;
233 Generator<V> values_;
234 MapsParams params_;
235 };
237
240
255 template <typename T>
257 VectorsParams params = {}) {
258 return Generator<std::vector<T>>(
259 new VectorsGenerator<T>(std::move(elements), params));
260 }
261
275 template <typename T>
277 return Generator<std::set<T>>(
278 new SetsGenerator<T>(std::move(elements), params));
279 }
280
303 template <typename K, typename V>
305 MapsParams params = {}) {
306 return Generator<std::map<K, V>>(new MapsGenerator<K, V>(
307 std::move(keys), std::move(values), params));
308 }
309
311 namespace detail {
312
313 template <typename Tuple, typename GenTuple, size_t... Is>
314 Tuple draw_tuple_impl(const GenTuple& gens, std::index_sequence<Is...>,
315 const TestCase& tc) {
316 return Tuple{std::get<Is>(gens).do_draw(tc)...};
317 }
318
319 } // namespace detail
320
321 // Concrete IGenerator for tuples(). Schema path requires every element
322 // generator to be basic.
323 template <typename... Ts>
324 class TuplesGenerator : public IGenerator<std::tuple<Ts...>> {
325 public:
326 using ResultTuple = std::tuple<Ts...>;
327
328 explicit TuplesGenerator(Generator<Ts>... gens)
329 : gens_(std::move(gens)...) {}
330
331 std::optional<BasicGenerator<ResultTuple>> as_basic() const override {
332 auto basics = std::apply(
333 [](const auto&... g) {
334 return std::make_tuple(g.as_basic()...);
335 },
336 gens_);
337 bool all_basic = std::apply(
338 [](const auto&... b) { return (b.has_value() && ...); },
339 basics);
340 if (!all_basic)
341 return std::nullopt;
342
343 hegel::internal::json::json elements =
344 hegel::internal::json::json::array();
345 std::apply(
346 [&elements](const auto&... b) {
347 (elements.push_back(b->schema), ...);
348 },
349 basics);
350
351 hegel::internal::json::json schema = {{"type", "tuple"},
352 {"elements", elements}};
353
354 auto parsers = std::apply(
355 [](const auto&... b) { return std::make_tuple(b->parse...); },
356 basics);
357
358 return BasicGenerator<ResultTuple>{
359 std::move(schema),
360 [parsers = std::move(parsers)](
361 const hegel::internal::json::json_raw_ref& raw)
362 -> ResultTuple {
363 return [&]<size_t... Is>(std::index_sequence<Is...>) {
364 return ResultTuple{std::get<Is>(parsers)(raw[Is])...};
365 }(std::index_sequence_for<Ts...>{});
366 }};
367 }
368
369 ResultTuple do_draw(const TestCase& tc) const override {
370 if (auto basic = as_basic()) {
371 return basic->do_draw(tc);
372 }
373 return detail::draw_tuple_impl<ResultTuple>(
374 gens_, std::index_sequence_for<Ts...>{}, tc);
375 }
376
377 private:
378 std::tuple<Generator<Ts>...> gens_;
379 };
381
396 template <typename... Ts>
397 Generator<std::tuple<Ts...>> tuples(Generator<Ts>... gens) {
398 return Generator<std::tuple<Ts...>>(
399 new TuplesGenerator<Ts...>(std::move(gens)...));
400 }
401
403
404} // namespace hegel::generators
The base class of all generators.
Definition core.h:157
Hegel generators.
Definition core.h:16
Generator< std::tuple< Ts... > > tuples(Generator< Ts >... gens)
Generate tuples from multiple generators.
Definition collections.h:397
Generator< std::vector< T > > vectors(Generator< T > elements, VectorsParams params={})
Generate vectors with elements from another generator.
Definition collections.h:256
Generator< std::set< T > > sets(Generator< T > elements, SetsParams params={})
Generate sets with elements from another generator.
Definition collections.h:276
Generator< std::map< K, V > > maps(Generator< K > keys, Generator< V > values, MapsParams params={})
Generate maps with configurable key and value types.
Definition collections.h:304
*‍/
Definition core.h:94
Parameters for maps() generator.
Definition collections.h:37
std::optional< size_t > max_size
Maximum number of entries. Default: 20.
Definition collections.h:40
size_t min_size
Minimum number of entries.
Definition collections.h:38
Parameters for sets() generator.
Definition collections.h:29
std::optional< size_t > max_size
Maximum set size. Default: 20.
Definition collections.h:31
size_t min_size
Minimum set size.
Definition collections.h:30
Parameters for vectors() generator.
Definition collections.h:20
size_t min_size
Minimum vector size.
Definition collections.h:21
bool unique
If true, all elements must be unique.
Definition collections.h:23
std::optional< size_t > max_size
Maximum vector size. Default: 100.
Definition collections.h:22