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 ¶ms) {
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 ¶ms) {
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 ¶ms) {
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