GCC Code Coverage Report


Directory: include/tts/
File: include/tts/engine/generator.hpp
Date: 2026-10-14 16:28:14
Exec Total Coverage
Lines: 60 62 96.8%
Functions: 599 599 100.0%
Branches: 10 13 76.9%

Line Branch Exec Source
1 //======================================================================================================================
2 /*
3 TTS - Tiny Test System
4 Copyright : TTS Contributors & Maintainers
5 SPDX-License-Identifier: BSL-1.0
6 */
7 //======================================================================================================================
8 #pragma once
9
10 #include <tts/tools/concepts.hpp>
11 #include <tts/tools/random.hpp>
12 #include <tts/tools/types.hpp>
13
14 namespace tts
15 {
16 namespace _
17 {
18 template<std::size_t N, bool Signed> struct sized_integer;
19
20 template<> struct sized_integer<1, true>
21 {
22 using type = std::int8_t;
23 };
24
25 template<> struct sized_integer<2, true>
26 {
27 using type = std::int16_t;
28 };
29
30 template<> struct sized_integer<4, true>
31 {
32 using type = std::int32_t;
33 };
34
35 template<> struct sized_integer<8, true>
36 {
37 using type = std::int64_t;
38 };
39
40 template<> struct sized_integer<1, false>
41 {
42 using type = std::uint8_t;
43 };
44
45 template<> struct sized_integer<2, false>
46 {
47 using type = std::uint16_t;
48 };
49
50 template<> struct sized_integer<4, false>
51 {
52 using type = std::uint32_t;
53 };
54
55 template<> struct sized_integer<8, false>
56 {
57 using type = std::uint64_t;
58 };
59
60 template<typename T, bool Signed = false>
61 using sized_integer_t = typename sized_integer<sizeof(T), Signed>::type;
62
63 template<typename T, typename V> struct builtin_conversion
64 {
65 117928 static auto from(V const& v)
66 {
67 117928 return static_cast<T>(v);
68 }
69 };
70 }
71
72 //====================================================================================================================
73 /**
74 @defgroup tools-generators Data Generators related tools
75 @brief Tools for data generation.
76 @{
77 **/
78 //====================================================================================================================
79
80 //====================================================================================================================
81 /**
82 @defgroup tools-generators-custom Data Generators Customization Points
83 @brief Customization points to customize data generation for user-defined types.
84 @{
85 **/
86 //====================================================================================================================
87
88 //====================================================================================================================
89 /**
90 @brief How a value is read as another type
91
92 Generators funnel their bounds through this, so a bound written once has to answer for every
93 type the case is run on. Specialize it for your own types rather than overloading convert_as. A
94 type without a specialization keeps the built-in cast.
95
96 The `convert_as` free function is the dispatcher rather than a customization point, and the
97 call sites reach it qualified, so an overload of that name in another namespace is never found.
98 It carries a generic default, which is why a leftover cannot be reported the way tts::precision
99 and tts::comparison report theirs.
100
101 @tparam T Target data type
102 @tparam V Type of the value being read
103 **/
104 //====================================================================================================================
105 template<typename T, typename V> struct conversion
106 {
107 };
108
109 //====================================================================================================================
110 /**
111 @brief Single value evaluation customization point
112 **/
113 //====================================================================================================================
114 117934 template<typename T, typename V> auto convert_as(V const& v, type<T> const&)
115 {
116 6 if constexpr(requires { conversion<T, V>::from(v); }) return conversion<T, V>::from(v);
117 else
118 {
119 static_assert(
120 !requires { conversion<T, V>::from; },
121 "[TTS] tts::conversion<T, V>::from does not accept a value of type V.");
122
123 117928 return _::builtin_conversion<T, V>::from(v);
124 }
125 }
126
127 //====================================================================================================================
128 /**
129 @brief Type conversion customization point
130
131 Some generators need to rebuild a template sequence type changing only the element type. This
132 structure provides a way to do so in an extensible way. The default implementation handles
133 standard sequence types like `std::array` or `std::vector`.
134
135 @tparam Seq Source sequence type.
136 @tparam U New element type.
137 **/
138 //====================================================================================================================
139 template<tts::_::sequence Seq, typename U> struct rebuild;
140
141 template<template<typename, typename...> typename Seq, typename T, typename... S, typename U>
142 struct rebuild<Seq<T, S...>, U>
143 {
144 using type = Seq<U, S...>;
145 };
146
147 template<template<typename, std::size_t> typename Seq, typename T, std::size_t N, typename U>
148 struct rebuild<Seq<T, N>, U>
149 {
150 using type = Seq<U, N>;
151 };
152
153 //====================================================================================================================
154 /**
155 @brief Data production customization point
156
157 This function is in charge of producing data of type `T` using generator `g` and additional
158 parameters `others...`. The default implementation simply calls the generator `g` with the
159 provided parameters.
160
161 By default, this function also handles sequence types by producing each of their elements using
162 the provided generator.
163
164 @tparam T Target data type
165 @param t Type wrapper for the target data type
166 @param g Data generator to use
167 @param others Additional parameters forwarded to the generator
168 @return A value of type `T` produced by the generator `g`.
169 **/
170 //====================================================================================================================
171 template<typename T> auto produce(type<T> const& t, auto g, auto... others);
172 }
173
174 namespace tts::_
175 {
176 template<typename T> struct builtin_generation
177 {
178 30366 static auto make(auto g, auto... others)
179 {
180 30366 return g(tts::type<T> {}, others...);
181 }
182 };
183
184 template<sequence T> struct builtin_generation<T>
185 {
186 160 static auto make(auto g, auto... args)
187 {
188 using elmt_type = std::remove_cvref_t<decltype(*begin(tts::_::declval<T>()))>;
189 using value_type = decltype(produce(tts::type<elmt_type> {}, g, 0, 0ULL, args...));
190
191 1 typename rebuild<T, value_type>::type that;
192 160 auto b = begin(that);
193 160 auto e = end(that);
194 160 std::ptrdiff_t sz = e - b;
195
196
2/2
✓ Branch 0 taken 743 times.
✓ Branch 1 taken 84 times.
1582 for(std::ptrdiff_t i = 0; i < sz; ++i)
197 {
198
1/1
✓ Branch 1 taken 711 times.
1422 *b++ = produce(tts::type<value_type> {}, g, i, sz, args...);
199 }
200 160 return that;
201 ✗ }
202 };
203 }
204
205 namespace tts
206 {
207 //====================================================================================================================
208 /*!
209 @ingroup tools-generators-custom
210 @brief How a value of a given type is built from a generator
211
212 Specialize this for your own types rather than overloading produce. A type without a
213 specialization keeps the default way of building, which hands a scalar straight to the generator
214 and fills a sequence element by element.
215
216 The `produce` free function is the dispatcher rather than a customization point, and the call
217 sites reach it qualified, so an overload of that name in another namespace is never found. It
218 carries a generic default, which is why a leftover cannot be reported the way tts::precision and
219 tts::comparison report theirs.
220
221 @tparam T Target data type
222 **/
223 //====================================================================================================================
224 template<typename T> struct generation
225 {
226 };
227
228 30558 template<typename T> auto produce(type<T> const&, auto g, auto... others)
229 {
230 if constexpr(requires { generation<T>::make(g, others...); })
231 {
232 6 return generation<T>::make(g, others...);
233 }
234 else
235 {
236 static_assert(
237 !requires { generation<T>::make; },
238 "[TTS] tts::generation<T>::make does not accept the generator.");
239
240 30552 return _::builtin_generation<T>::make(g, others...);
241 }
242 }
243
244 //====================================================================================================================
245 /// @}
246 //====================================================================================================================
247
248 //====================================================================================================================
249 /**
250 @brief Base type extraction
251
252 This structure is used to extract the base type of a type `T`. By default, the base type is `T`
253 itself, but this structure can be specialized for user-defined types to provide a different base
254 type. The base type is used to determine the limits and special values for the type `T` when
255 generating test data.
256
257 @par Helper types
258 - `base_type_t<T>`: Alias for `typename base_type<T>::type`
259
260 @tparam T Type for which to extract the base type.
261 **/
262 //====================================================================================================================
263 template<typename T> struct base_type
264 {
265 using type = T;
266 };
267
268 template<typename T> using base_type_t = typename base_type<T>::type;
269
270 //====================================================================================================================
271 /**
272 @brief Boolean type computation
273
274 This structure is used to compute a boolean type for a type `T`. By default, the boolean type is
275 `bool`, but this structure can be specialized for user-defined types to provide a different
276 boolean type.
277
278 @par Helper types
279 - `boolean_type_t<T>`: Alias for `typename boolean_type<T>::type`
280
281 @tparam T Type for which to compute the boolean type.
282 **/
283 //====================================================================================================================
284 template<typename T> struct boolean_type
285 {
286 using type = bool;
287 };
288
289 template<typename T> using boolean_type_t = typename boolean_type<T>::type;
290
291 namespace _
292 {
293 struct empty_limits
294 {
295 };
296
297 template<typename T> struct common_limits
298 {
299 using b_t = base_type_t<T>;
300 static_assert(std::numeric_limits<b_t>::is_specialized,
301 "Base type must have std::numeric_limits specialized");
302
303 T valmax = static_cast<T>(std::numeric_limits<b_t>::max());
304 T valmin = static_cast<T>(std::numeric_limits<b_t>::lowest());
305 T zero = static_cast<T>(0);
306 T one = static_cast<T>(1);
307 };
308
309 template<typename T> struct float_limits
310 {
311 using b_t = base_type_t<T>;
312
313 static constexpr b_t get_maxflint()
314 {
315 if constexpr(sizeof(b_t) == 4) return static_cast<b_t>(0x1p+24);
316 else return static_cast<b_t>(0x1p+53);
317 }
318
319 T nan = static_cast<T>(std::numeric_limits<b_t>::quiet_NaN());
320 T inf = static_cast<T>(std::numeric_limits<b_t>::infinity());
321 T minf = static_cast<T>(-std::numeric_limits<b_t>::infinity());
322 T mzero = static_cast<T>(-0.0);
323 T maxflint = static_cast<T>(get_maxflint());
324 T mindenormal = static_cast<T>(std::numeric_limits<b_t>::denorm_min());
325 T smallestposval = static_cast<T>(std::numeric_limits<b_t>::min());
326 T mone = static_cast<T>(-1.0);
327 };
328 }
329
330 //====================================================================================================================
331 /**
332 @defgroup tools-generators-class Data Generators
333 @brief Generators to produce data samples for testing.
334 @{
335 **/
336 //====================================================================================================================
337
338 //====================================================================================================================
339 /**
340 @brief Defines a set of limits for a type `T`.
341
342 This structure provides a set of limits and special values for the type `T` that can be used for
343 generating test data that covers edge cases and special values. The actual limits provided
344 depend on the type `T` and its base type.
345
346 The provided limits include:
347 - `valmax`: The maximum representable value of the base type.
348 - `valmin`: The minimum representable value of the base type.
349 - `zero`: The value representing zero.
350 - `one`: The value representing one.
351
352 For floating-point types, additional limits include:
353 - `nan`: Not-a-Number value.
354 - `inf`: Positive infinity.
355 - `minf`: Negative infinity.
356 - `maxflint`: The largest integer value that can be exactly represented in the type.
357 - `mindenormal`: The smallest positive subnormal value.
358 - `smallestposval`: The smallest positive normal value.
359 - `mzero`: Negative zero.
360 - `mone`: The value representing negative one.
361
362 This class can be specialized for user-defined types to provide custom limits.
363
364 @groupheader{Examples}
365 @snippet doc/limits.cpp snippet
366
367 @tparam T Type for which to provide limits for.
368 **/
369 //====================================================================================================================
370 template<typename T>
371 struct limits_set
372 : _::common_limits<T>
373 , std::conditional_t<std::floating_point<base_type_t<T>>, _::float_limits<T>, _::empty_limits>
374 {
375 using type = T;
376 };
377
378 //====================================================================================================================
379 /**
380 @brief Provides a set of limits for the type `T`.
381
382 This function returns a structure containing various limits and special values for the type `T`
383 that can be used for generating test data that covers edge cases and special values.
384
385 @tparam T Type for which to provide limits
386 @return A instance of `limits_set<T>` containing limits and special values for type `T`.
387
388 @relates limits_set
389 **/
390 //====================================================================================================================
391 95 template<typename T> inline auto limits(tts::type<T>)
392 {
393 95 return limits_set<T> {};
394 }
395
396 //====================================================================================================================
397 /**
398 @brief Defines a data generator that always return the same value.
399
400 This generator produces always the same value provided at construction converted to the target
401 type.
402
403 @tparam T Type of the value to be produced
404
405 @groupheader{Example}
406 @snippet doc/generator_value.cpp snippet
407 **/
408 //====================================================================================================================
409 template<typename T> struct value
410 {
411 5 constexpr explicit value(T v)
412 5 : seed(v)
413 {
414 5 }
415
416 238 template<typename D> D operator()(tts::type<D>, auto...) const
417 {
418 238 return ::tts::convert_as(seed, type<D> {});
419 }
420
421 T seed;
422 };
423
424 //====================================================================================================================
425 /**
426 @brief Defines a data generator that produce logical values in a regular pattern.
427
428 This generator produces logical values (true/false) in a regular pattern defined by two
429 parameters: a starting index and a range.
430
431 For a size `N`, the produced values are:
432 - `true` for indices `i` where `(start + i) % range == 0`
433 - `false` for all other indices.
434
435 @tparam T Type of the starting index
436 @tparam U Type of the range
437 **/
438 //====================================================================================================================
439 template<typename T, typename U = T> struct logicals
440 {
441 constexpr logicals(T v, U k)
442 : start(v)
443 , range(k)
444 {
445 }
446
447 template<typename D> auto operator()(tts::type<D>) const
448 {
449 return ::tts::convert_as(false, type<tts::boolean_type_t<D>> {});
450 }
451
452 18 template<typename D> auto operator()(tts::type<D>, auto idx, auto...) const
453 {
454 18 return ::tts::convert_as(((start + idx) % range) == 0, type<tts::boolean_type_t<D>> {});
455 }
456
457 T start;
458 U range;
459 };
460
461 //====================================================================================================================
462 /**
463 @brief Defines a data generator that produce a ramp of data.
464
465 This generator produces a ramp starting from an initial value and increasing by a fixed step at
466 each call. I.e., for a size `N`, the produced values are: `start, start+step, start+2*step, ...,
467 start+(N-1)*step`.
468
469 @tparam T Type of the initial value
470 @tparam U Type of the step value
471
472 @groupheader{Example}
473 @snippet doc/generator_ramp.cpp snippet
474 **/
475 //====================================================================================================================
476 template<typename T, typename U = T> struct ramp
477 {
478 constexpr explicit ramp(T s)
479 : start(s)
480 , step(1)
481 {
482 }
483 1 constexpr ramp(T s, U st)
484 1 : start(s)
485 1 , step(st)
486 {
487 1 }
488
489 366 template<typename D> D operator()(tts::type<D>, auto idx, auto...) const
490 {
491 366 return ::tts::convert_as(start + idx * step, type<D> {});
492 }
493
494 48 template<typename D> D operator()(tts::type<D>) const
495 {
496 48 return ::tts::convert_as(start, type<D> {});
497 }
498
499 T start;
500 U step;
501 };
502
503 //====================================================================================================================
504 /**
505 @brief Defines a data generator that produce a reverse ramp of data.
506
507 This generator produces a ramp read backward: the last value is `start` and every earlier one
508 stands one step above it. I.e., for a size `N`, the produced values are:
509 `start+(N-1)*step, ..., start+2*step, start+step, start`.
510
511 @tparam T Type of the initial value
512 @tparam U Type of the step value
513
514 @groupheader{Example}
515 @snippet doc/generator_reverse_ramp.cpp snippet
516 **/
517 //====================================================================================================================
518 template<typename T, typename U = T> struct reverse_ramp
519 {
520 constexpr explicit reverse_ramp(T s)
521 : start(s)
522 , step(1)
523 {
524 }
525 constexpr reverse_ramp(T s, U st)
526 : start(s)
527 , step(st)
528 {
529 }
530
531 380 template<typename D, typename I> D operator()(tts::type<D>, I idx, auto sz, auto...) const
532 {
533 380 auto const rev = static_cast<I>(sz) - 1 - idx;
534 380 return ::tts::convert_as(start + rev * step, type<D> {});
535 }
536
537 48 template<typename D> D operator()(tts::type<D>) const
538 {
539 48 return ::tts::convert_as(start, type<D> {});
540 }
541
542 T start;
543 U step;
544 };
545
546 //====================================================================================================================
547 /**
548 @brief Defines a data generator that produce values between two bounds.
549
550 This generator produces values linearly spaced between two bounds (inclusive). For a size `N`,
551 the produced values are: `first, first+step, first+2*step, ..., last`, where `step =
552 (last-first)/(N-1)`.
553
554 When generated values exceed the last bound due to rounding, the last bound is returned instead.
555
556 @tparam T Type of the first bound
557 @tparam U Type of the last bound
558
559 @groupheader{Example}
560 @snippet doc/generator_between.cpp snippet
561 **/
562 //====================================================================================================================
563 template<typename T, typename U = T> struct between
564 {
565 constexpr between(T first, U last)
566 : first_(first)
567 , last_(last)
568 {
569 }
570
571 8402 template<typename D> D operator()(tts::type<D>, auto idx, auto sz, auto...) const
572 {
573 8402 auto w1 = ::tts::convert_as(first_, type<D> {});
574 8402 auto w2 = ::tts::convert_as(last_, type<D> {});
575
1/2
✓ Branch 0 taken 8302 times.
✗ Branch 1 not taken.
16768 D step = (sz - 1) ? static_cast<D>(::tts::convert_as(last_ - first_, type<D> {}) /
576 8402 ::tts::convert_as(sz - 1, type<D> {}))
577 ✗ : ::tts::convert_as(0, type<D> {});
578 16804 auto value = ::tts::convert_as(
579 8402 w1 + ::tts::convert_as(idx, type<D> {}) * ::tts::convert_as(step, type<D> {}), type<D> {});
580
581 // Clamp rounding overshoot back to the last bound, whichever direction the range runs in.
582
2/2
✓ Branch 0 taken 8297 times.
✓ Branch 1 taken 5 times.
8402 return (w1 <= w2) ? _::min(value, w2) : _::max(value, w2);
583 }
584
585 30 template<typename D> D operator()(tts::type<D>) const
586 {
587 30 return ::tts::convert_as(first_, type<D> {});
588 }
589
590 T first_;
591 U last_;
592 };
593
594 //====================================================================================================================
595 /**
596 @brief Random generator between two bounds using realistic_distribution
597
598 This generator produces random values between two bounds (inclusive) using a realistic
599 distribution. The realistic distribution aims to provide a more uniform coverage of the range by
600 avoiding too many extreme values.
601
602 @tparam Mx Type of the upper bound
603 @tparam Mn Type of the lower bound
604
605 @groupheader{Example}
606 @snippet doc/generator_randoms.cpp snippet
607 **/
608 //====================================================================================================================
609 template<typename Mx, typename Mn> struct randoms
610 {
611 constexpr randoms(Mn mn, Mx mx)
612 : mini(mn)
613 , maxi(mx)
614 {
615 }
616
617 250 template<typename D> D operator()(tts::type<D>, auto...)
618 {
619 // A bound is not always a number: it can be a per-type recipe evaluated by convert_as,
620 // which has no ordering against 0. Only check the ones that can actually be compared.
621 if constexpr(std::is_unsigned_v<D> && requires { mini >= 0; })
622 {
623
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 42 times.
84 assert(mini >= 0 &&
624 "Minimum value for unsigned type random generator must be non-negative");
625 }
626 if constexpr(std::is_unsigned_v<D> && requires { maxi >= 0; })
627 {
628
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 42 times.
84 assert(maxi >= 0 &&
629 "Maximum value for unsigned type random generator must be non-negative");
630 }
631
1/1
✓ Branch 3 taken 125 times.
250 return random_value(::tts::convert_as(mini, type<D> {}), ::tts::convert_as(maxi, type<D> {}));
632 }
633
634 Mn mini;
635 Mx maxi;
636 };
637
638 //====================================================================================================================
639 /*!
640 @ingroup tools-generators-custom
641 @brief Whether a generator is a tts::randoms, by its type
642
643 tts::generation keys on the type being built, so a specialization that has to treat a range
644 apart cannot see it in the overload set the way a produce overload could. Recognizing the
645 generator is left here rather than at the point of use, where it would come down to guessing from
646 the members a generator happens to carry.
647
648 @tparam G Generator type being examined
649 **/
650 //====================================================================================================================
651 template<typename G> struct is_randoms : std::false_type
652 {
653 };
654 template<typename Mx, typename Mn> struct is_randoms<randoms<Mx, Mn>> : std::true_type
655 {
656 };
657
658 //! @brief Value alias for tts::is_randoms, indifferent to references and cv-qualifiers
659 template<typename G>
660 inline constexpr bool is_randoms_v = is_randoms<std::remove_cvref_t<G>>::value;
661
662 //====================================================================================================================
663 /**
664 @brief Produces random bits patterns
665
666 This generator produces random bit patterns by generating a random integer value and
667 interpreting its bits as the bits of the target type. The number of bits generated is equal to the
668 number of bits in the target type.
669
670 @groupheader{Example}
671 @snippet doc/generator_random_bits.cpp snippet
672 **/
673 //====================================================================================================================
674 struct random_bits
675 {
676 800020 template<typename D> auto operator()(tts::type<D>, auto...)
677 {
678 using i_t = tts::_::sized_integer_t<tts::base_type_t<D>>;
679 800020 return tts::random_value<i_t>(0, std::numeric_limits<i_t>::max());
680 }
681 };
682
683 //====================================================================================================================
684 /**
685 @brief Produces valid shift amounts for the target type
686
687 Unlike @ref tts::random_bits, which draws a full-width bit pattern, this generator draws an
688 index into the bits of the target type - a value in `[0, 8*sizeof(T)[`. That is what a shift
689 operand has to be: shifting by the width of the type or more is undefined behaviour.
690 **/
691 //====================================================================================================================
692 struct random_shift
693 {
694 template<typename D> auto operator()(tts::type<D>, auto...) const
695 {
696 using i_t = tts::_::sized_integer_t<tts::base_type_t<D>>;
697 return tts::random_value<i_t>(0, static_cast<i_t>(8 * sizeof(i_t) - 1));
698 }
699 };
700
701 //====================================================================================================================
702 /**
703 @brief Converts a generator to produce integer values
704
705 This generator adapter takes a generator `g` and produces integer values by generating values
706 using `g` and converting them to integers. The conversion is done by interpreting the generated
707 values as integers of the same size as the base type of the target type.
708
709 @groupheader{Example}
710 @snippet doc/generator_as_integer.cpp snippet
711
712 @tparam G Type of the generator to adapt
713 **/
714 //====================================================================================================================
715 template<typename G> struct as_integer
716 {
717 constexpr explicit as_integer(G g)
718 : generator_(g)
719 {
720 }
721 4 template<typename D> auto operator()(tts::type<D>, auto... args)
722 {
723 using i_t = tts::_::sized_integer_t<tts::base_type_t<D>>;
724 4 return generator_(tts::type<i_t> {}, args...);
725 }
726 G generator_;
727 };
728
729 //====================================================================================================================
730 /**
731 @brief Converts a generator to produce integer values
732
733 This generator adapter takes a generator `g` and produces signed integer values by generating
734 values using `g` and converting them to integers. The conversion is done by interpreting the
735 generated values as integers of the same size as the base type of the target type.
736
737 @groupheader{Example}
738 @snippet doc/generator_as_signed_integer.cpp snippet
739
740 @tparam G Type of the generator to adapt
741 **/
742 //====================================================================================================================
743 template<typename G> struct as_signed_integer
744 {
745 constexpr explicit as_signed_integer(G g)
746 : generator_(g)
747 {
748 }
749 4 template<typename D> auto operator()(tts::type<D>, auto... args)
750 {
751 using i_t = tts::_::sized_integer_t<tts::base_type_t<D>, true>;
752 tts::type<i_t> tgt {};
753
1/1
✓ Branch 1 taken 2 times.
8 return generator_(tgt, args...);
754 }
755 G generator_;
756 };
757
758 //====================================================================================================================
759 /// @}
760 //====================================================================================================================
761 }
762