1 #include <catch2/catch.hpp>
2 #include <rapidcheck/catch.h>
3 
4 #include <numeric>
5 
6 #include "rapidcheck/gen/Arbitrary.h"
7 #include "rapidcheck/gen/Numeric.h"
8 #include "rapidcheck/shrinkable/Operations.h"
9 
10 #include "util/Util.h"
11 #include "util/Meta.h"
12 #include "util/TypeListMacros.h"
13 #include "util/ArbitraryRandom.h"
14 #include "util/GenUtils.h"
15 #include "util/ShrinkableUtils.h"
16 
17 using namespace rc;
18 using namespace rc::test;
19 
20 namespace {
21 
22 template <typename T>
absoluteInt(T x,std::true_type)23 typename std::make_unsigned<T>::type absoluteInt(T x, std::true_type) {
24   return (x < 0) ? -x : x;
25 }
26 
27 template <typename T>
absoluteInt(T x,std::false_type)28 T absoluteInt(T x, std::false_type) {
29   return x;
30 }
31 
32 template <typename T,
33           typename = typename std::enable_if<std::is_integral<T>::value>::type>
absolute(T x)34 typename std::make_unsigned<T>::type absolute(T x) {
35   return absoluteInt(x, std::is_signed<T>());
36 }
37 
38 template <typename T>
isAllOnes(T x)39 bool isAllOnes(T x) {
40   using UInt = typename std::make_unsigned<T>::type;
41   return static_cast<UInt>(x) == std::numeric_limits<UInt>::max();
42 }
43 
44 struct IntegralProperties {
45   template <typename T>
exec__anon638225990111::IntegralProperties46   static void exec() {
47     TEMPLATED_SECTION(T, "when size >= gen::kNominalSize") {
48       templatedProp<T>("all bits can be either 1 or 0",
49                        [](Random random) {
50                          T ones = 0;
51                          T zeroes = 0;
52                          while (!isAllOnes(ones) || !isAllOnes(zeroes)) {
53                            T value =
54                                gen::arbitrary<T>()(random.split()).value();
55                            ones |= value;
56                            zeroes |= ~value;
57                          }
58                        });
59 
60       templatedProp<T>(
61           "values are uniformly distributed over entire range",
62           [](Random random) {
63             using UInt = typename std::make_unsigned<T>::type;
64 
65             std::array<uint64_t, 8> bins;
66             constexpr auto kBinSize =
67                 (std::numeric_limits<UInt>::max() / bins.size()) + 1;
68             bins.fill(0);
69 
70             static constexpr std::size_t nSamples = 10000;
71             for (std::size_t i = 0; i < nSamples; i++) {
72               const auto value = static_cast<UInt>(
73                   gen::arbitrary<T>()(random.split()).value());
74               bins[static_cast<std::size_t>(value / kBinSize)]++;
75             }
76 
77             const auto ideal = nSamples / static_cast<double>(bins.size());
78             const auto error = std::accumulate(begin(bins),
79                                            end(bins),
80                                            0.0,
81                                            [=](double lambdaError, uint64_t x) {
82                                              double diff = 1.0 - (x / ideal);
83                                              return lambdaError + (diff * diff);
84                                            });
85 
86             RC_ASSERT(error < 0.1);
87           });
88     }
89 
90     templatedProp<T>(
91         "monotonically increasing size yields monotonically inscreasing"
92         " abs(value)",
93         [](const Random &random) {
94           using AbsT = decltype(absolute(std::declval<T>()));
95           AbsT prev = 0;
96           for (int i = 0; i <= kNominalSize; i++) {
97             AbsT value = absolute(gen::arbitrary<T>()(random, i).value());
98             RC_ASSERT(value >= prev);
99             prev = value;
100           }
101         });
102 
103     templatedProp<T>(
104         "finds minimum where value must be larger/smaller than some value",
105         [](const Random &random) {
106           int size = *gen::inRange<int>(0, 200);
107           const auto shrinkable = gen::arbitrary<T>()(random, size);
108           T start = shrinkable.value();
109           T target;
110           std::pair<T, std::size_t> result;
111           if (start < 0) {
112             target = *gen::inRange<T>(start, 1);
113             result = shrinkable::findLocalMin(shrinkable,
114                                               [=](T x) { return x <= target; });
115           } else {
116             target = *gen::inRange<T>(0, start + 1);
117             result = shrinkable::findLocalMin(shrinkable,
118                                               [=](T x) { return x >= target; });
119           }
120 
121           RC_ASSERT(result.first == target);
122         });
123   }
124 };
125 
126 struct NumericProperties {
127   template <typename T>
exec__anon638225990111::NumericProperties128   static void exec() {
129     templatedProp<T>("zero size always yields zero",
130                      [](const Random &random) {
131                        auto shrinkable = gen::arbitrary<T>()(random, 0);
132                        RC_ASSERT(shrinkable ==
133                                  shrinkable::just(static_cast<T>(0)));
134                      });
135   }
136 };
137 
138 struct SignedProperties {
139   template <typename T>
exec__anon638225990111::SignedProperties140   static void exec() {
141     templatedProp<T>("P(value > 0) ~ P(value < 0)",
142                      [](Random random) {
143                        static constexpr int kEnough = 5000;
144                        int size = *gen::inRange<int>(50, 200);
145                        int n = 0;
146                        for (int i = 0; i < kEnough; i++) {
147                          T value =
148                              gen::arbitrary<T>()(random.split(), size).value();
149                          if (value < 0) {
150                            n--;
151                          } else if (value > 0) {
152                            n++;
153                          }
154                        }
155 
156                        double avg = static_cast<double>(n) / kEnough;
157                        RC_ASSERT(avg < 0.08);
158                      });
159   }
160 };
161 
162 } // namespace
163 
164 TEST_CASE("arbitrary integers") {
165   forEachType<IntegralProperties, RC_INTEGRAL_TYPES>();
166   forEachType<NumericProperties, RC_INTEGRAL_TYPES>();
167   forEachType<SignedProperties, RC_SIGNED_INTEGRAL_TYPES>();
168 }
169 
170 TEST_CASE("arbitrary reals") {
171   forEachType<NumericProperties, RC_REAL_TYPES>();
172   forEachType<SignedProperties, RC_REAL_TYPES>();
173 }
174 
175 namespace {
176 
177 struct InRangeProperties {
178   template <typename T>
exec__anon638225990b11::InRangeProperties179   static void exec() {
180 
181     // TODO proper range generator
182     static const auto genRange = gen::exec([] {
183       const auto a = *gen::arbitrary<T>();
184       const auto b = *gen::distinctFrom(a);
185       return std::make_pair(std::min(a, b), std::max(a, b));
186     });
187 
188     templatedProp<T>(
189         "never generates values outside of range",
190         [](const GenParams &params) {
191           // TODO range generator
192           const auto range = *genRange;
193           const auto shrinkable = gen::inRange<T>(range.first, range.second)(
194               params.random, params.size);
195           onAnyPath(
196               shrinkable,
197               [&](const Shrinkable<T> &value, const Shrinkable<T> &shrink) {
198                 const auto x = value.value();
199                 RC_ASSERT(x >= range.first && x < range.second);
200               });
201         });
202 
203     templatedProp<T>("throws if min <= max",
204                      [](const GenParams &params) {
205                        const auto a = *gen::arbitrary<T>();
206                        const auto b = *gen::distinctFrom(a);
207                        const auto gen =
208                            gen::inRange<T>(std::max(a, b), std::min(a, b));
209                        const auto shrinkable = gen(params.random, params.size);
210                        RC_ASSERT_THROWS_AS(shrinkable.value(),
211                                            GenerationFailure);
212                      });
213 
214     templatedProp<T>("first shrink is min",
215                      [](const GenParams &params) {
216                        // TODO range generator
217                        const auto range = *genRange;
218                        const auto shrinkable =
219                            gen::inRange<T>(range.first, range.second)(
220                                params.random, params.size);
221                        if (shrinkable.value() != range.first) {
222                          const auto firstShrink = shrinkable.shrinks().next();
223                          RC_ASSERT(firstShrink);
224                          RC_ASSERT(firstShrink->value() == range.first);
225                        }
226                      });
227 
228     templatedProp<T>(
229         "when size == kNominalSize, generates all values in range",
230         [](const Random &random) {
231           const auto size = *gen::inRange<T>(1, 20);
232           const auto min =
233               *gen::inRange<T>(std::numeric_limits<T>::min(),
234                                std::numeric_limits<T>::max() - size);
235 
236           const auto gen = gen::inRange<T>(min, min + size);
237           auto r = random;
238           std::vector<int> counts(static_cast<std::size_t>(size), 0);
239           for (std::size_t i = 0; i < 2000000; i++) {
240             const auto x = gen(r.split(), kNominalSize).value();
241             counts[static_cast<std::size_t>(x - min)]++;
242             const auto done =
243                 std::find(begin(counts), end(counts), 0) == end(counts);
244             if (done) {
245               RC_SUCCEED("All generated");
246             }
247           }
248 
249           RC_FAIL("Gave up");
250         });
251 
252     templatedProp<T>("when size == 0, generates only min",
253                      [](const Random &random) {
254                        const auto range = *genRange;
255                        RC_ASSERT(
256                            gen::inRange(range.first, range.second)(random, 0) ==
257                            shrinkable::just(range.first));
258                      });
259 
260     templatedProp<T>("finds shrink where value must be larger than some value",
261                      [](const Random &random) {
262                        const auto range = *genRange;
263                        const auto target =
264                            *gen::inRange(range.first, range.second);
265 
266                        const auto result =
267                            searchGen(random,
268                                      kNominalSize,
269                                      gen::inRange(range.first, range.second),
270                                      [=](T x) { return x >= target; });
271                        RC_ASSERT(result == target);
272                      });
273   }
274 };
275 
276 } // namespace
277 
278 TEST_CASE("gen::inRange") {
279   forEachType<InRangeProperties, RC_INTEGRAL_TYPES>();
280 }
281