1 #include <catch2/catch.hpp>
2 #include <rapidcheck/catch.h>
3
4 #include "rapidcheck/detail/Property.h"
5
6 #include "util/Generators.h"
7 #include "util/Predictable.h"
8 #include "util/GenUtils.h"
9 #include "util/TemplateProps.h"
10 #include "util/ShrinkableUtils.h"
11
12 using namespace rc;
13 using namespace rc::test;
14 using namespace rc::detail;
15
16 TEST_CASE("CaseDescription") {
17 SECTION("operator==/operator!=") {
18 propConformsToEquals<CaseDescription>();
19 PROP_REPLACE_MEMBER_INEQUAL(CaseDescription, result);
20 PROP_REPLACE_MEMBER_INEQUAL(CaseDescription, tags);
21
22 prop("not equal if example not equal",
__anon61752d240102(const CaseDescription &original) 23 [](const CaseDescription &original) {
24 auto other(original);
25 const auto otherExample = *gen::distinctFrom(other.example());
26 other.example = [=] { return otherExample; };
27 RC_ASSERT(original != other);
28 RC_ASSERT(other != original);
29 RC_ASSERT(!(original == other));
30 RC_ASSERT(!(other == original));
31 });
32
33 SECTION("equal if neither has example") {
34 CaseDescription a;
35 CaseDescription b;
36 REQUIRE(a == b);
37 }
38
39 SECTION("inequal if one has example but not the other") {
40 CaseDescription a;
41 CaseDescription b;
__anon61752d240302null42 b.example = [] { return Example(); };
43 REQUIRE_FALSE(a == b);
44 }
45 }
46
47 SECTION("operator<<") {
48 propConformsToOutputOperator<CaseDescription>();
49
50 SECTION("prints description without example") {
51 std::ostringstream os;
52 CaseDescription desc;
53 os << desc;
54 REQUIRE(os.str().find("example=") == std::string::npos);
55 }
56 }
57 }
58
59 namespace {
60
61 template <typename Callable>
makeAdapter(Callable && callable)62 PropertyAdapter<Decay<Callable>> makeAdapter(Callable &&callable) {
63 return PropertyAdapter<Decay<Callable>>(std::forward<Callable>(callable));
64 }
65
descriptionContains(const TaggedResult & result,const std::string & str)66 bool descriptionContains(const TaggedResult &result, const std::string &str) {
67 return result.result.description.find(str) != std::string::npos;
68 }
69
reportAll(const std::vector<CaseResult> & results)70 void reportAll(const std::vector<CaseResult> &results) {
71 for (const auto &result : results) {
72 ImplicitParam<param::CurrentPropertyContext>::value()->reportResult(result);
73 }
74 }
75
logAll(const std::vector<std::string> & log)76 void logAll(const std::vector<std::string> &log) {
77 auto &logStream =
78 ImplicitParam<param::CurrentPropertyContext>::value()->logStream();
79 for (const auto &message : log) {
80 logStream << message;
81 }
82 }
83
genResultOfType(std::initializer_list<CaseResult::Type> types)84 Gen<CaseResult> genResultOfType(std::initializer_list<CaseResult::Type> types) {
85 return gen::build<CaseResult>(
86 gen::set(&CaseResult::type,
87 gen::elementOf<std::vector<CaseResult::Type>>(types)),
88 gen::set(&CaseResult::description));
89 }
90
91 } // namespace
92
93 TEST_CASE("PropertyAdapter") {
94 prop("Discard overrides any other reported result",
__anon61752d240502null95 [] {
96 auto results =
97 *gen::container<std::vector<CaseResult>>(genResultOfType(
98 {CaseResult::Type::Success, CaseResult::Type::Failure}));
99 const auto discardResult =
100 *genResultOfType({CaseResult::Type::Discard});
101 const auto position = *gen::inRange<std::size_t>(0, results.size());
102 results.insert(begin(results) + position, discardResult);
103
104 const auto result = makeAdapter([=] { reportAll(results); })().result;
105 RC_ASSERT(result == discardResult);
106 });
107
108 prop("Failure overrides any reported successes",
__anon61752d240702null109 [] {
110 auto results = *gen::container<std::vector<CaseResult>>(
111 genResultOfType({CaseResult::Type::Success}));
112 const auto failureResult =
113 *genResultOfType({CaseResult::Type::Failure});
114 const auto position = *gen::inRange<std::size_t>(0, results.size());
115 results.insert(begin(results) + position, failureResult);
116
117 const auto result = makeAdapter([=] { reportAll(results); })().result;
118 RC_ASSERT(result == failureResult);
119 });
120
121 prop("result description contains descriptions of all reported failures",
__anon61752d240902null122 [] {
123 auto failureResults =
124 *gen::container<std::vector<CaseResult>>(
125 genResultOfType({CaseResult::Type::Failure}));
126
127 const auto result = makeAdapter([=] { reportAll(failureResults); })();
128 for (const auto &failureResult : failureResults) {
129 RC_ASSERT(descriptionContains(result, failureResult.description));
130 }
131 });
132
133 prop("result description contains all logged messages",
__anon61752d240b02(const std::vector<std::string> &messages) 134 [](const std::vector<std::string> &messages) {
135 const auto result = makeAdapter([=] { logAll(messages); })();
136 for (const auto &message : messages) {
137 RC_ASSERT(descriptionContains(result, message));
138 }
139 });
140
141 SECTION("does not include log when nothing was logged") {
__anon61752d240d02null142 const auto result = makeAdapter([=] {})();
143 REQUIRE(!descriptionContains(result, "Log:"));
144 }
145
146 prop("returns CaseResult as is",
__anon61752d240e02(const CaseResult &result) 147 [](const CaseResult &result) {
148 RC_ASSERT(makeAdapter([=] { return result; })().result == result);
149 });
150
151 SECTION("returns success result for void callables") {
__anon61752d241002null152 REQUIRE(makeAdapter([] {})().result.type == CaseResult::Type::Success);
153 }
154
155 SECTION("if callable returns a bool") {
156 SECTION("returns success result for true") {
__anon61752d241102null157 REQUIRE(makeAdapter([] { return true; })().result.type ==
158 CaseResult::Type::Success);
159 }
160
161 SECTION("returns success result for false") {
__anon61752d241202null162 REQUIRE(makeAdapter([] { return false; })().result.type ==
163 CaseResult::Type::Failure);
164 }
165 }
166
167 SECTION("returns success for empty strings") {
__anon61752d241302null168 REQUIRE(makeAdapter([] { return std::string(); })().result.type ==
169 CaseResult::Type::Success);
170 }
171
172 prop("returns failure with the string as message for non-empty strings",
__anon61752d241402null173 [] {
174 // TODO non-empty generator
175 const auto msg = *gen::nonEmpty<std::string>();
176 const auto result = makeAdapter([&] { return msg; })();
177 RC_ASSERT(result.result.type == CaseResult::Type::Failure);
178 RC_ASSERT(descriptionContains(result, msg));
179 });
180
181 prop("if a CaseResult is thrown, returns that case result",
__anon61752d241602(const CaseResult &result) 182 [](const CaseResult &result) {
183 RC_ASSERT(makeAdapter([&] { throw result; })().result == result);
184 });
185
186 prop("returns a discard result if a GenerationFailure is thrown",
__anon61752d241802(const std::string &msg) 187 [](const std::string &msg) {
188 const auto result =
189 makeAdapter([&] { throw GenerationFailure(msg); })();
190 RC_ASSERT(result.result.type == CaseResult::Type::Discard);
191 RC_ASSERT(descriptionContains(result, msg));
192 });
193
194 prop(
195 "returns a failure result with what message if an std::exception is"
196 " thrown",
__anon61752d241a02(const std::string &msg) 197 [](const std::string &msg) {
198 const auto result =
199 makeAdapter([&] { throw std::runtime_error(msg); })();
200 RC_ASSERT(result.result.type == CaseResult::Type::Failure);
201 RC_ASSERT(descriptionContains(result, msg));
202 });
203
204 prop(
205 "returns a failure result with the string as the message if a string"
206 " is thrown",
__anon61752d241c02(const std::string &msg) 207 [](const std::string &msg) {
208 const auto result = makeAdapter([&] { throw msg; })();
209 RC_ASSERT(result.result.type == CaseResult::Type::Failure);
210 RC_ASSERT(descriptionContains(result, msg));
211 });
212
213 SECTION("returns a failure result if other values are thrown") {
__anon61752d241e02null214 const auto result = makeAdapter([&] { throw 1337; })();
215 RC_ASSERT(result.result.type == CaseResult::Type::Failure);
216 }
217
218 prop("forwards arguments to callable",
__anon61752d241f02(int a, const std::string &b, NonCopyable c) 219 [](int a, const std::string &b, NonCopyable c) {
220 const auto expected = std::to_string(a) + b + std::to_string(c.extra);
221 const auto adapter =
222 makeAdapter([](int d, const std::string &e, NonCopyable f) {
223 return std::to_string(d) + e + std::to_string(f.extra);
224 });
225 const auto result = adapter(std::move(a), std::move(b), std::move(c));
226 RC_ASSERT(result.result.description == expected);
227 });
228
229 prop("returns any tags that were added",
__anon61752d242102(const std::vector<std::string> &tags) 230 [](const std::vector<std::string> &tags) {
231 const auto result = makeAdapter([&] {
232 for (const auto &tag : tags) {
233 ImplicitParam<param::CurrentPropertyContext>::value()->addTag(tag);
234 }
235 })();
236
237 RC_ASSERT(result.tags == tags);
238 });
239 }
240
241 namespace {
242
243 template <int N>
244 struct Fixed {};
245
246 } // namespace
247
248 namespace rc {
249
250 template <int N>
251 struct Arbitrary<Fixed<N>> {
arbitraryrc::Arbitrary252 static Gen<Fixed<N>> arbitrary() {
253 return [](const Random &random, int) {
254 const int n = Random(random).next() % 10;
255 return shrinkable::just(
256 Fixed<N>(), seq::take(n, seq::repeat(shrinkable::just(Fixed<N>()))));
257 };
258 }
259 };
260
261 } // namespace rc
262
263 TEST_CASE("toProperty") {
264 using ShrinkableResult = Shrinkable<CaseDescription>;
265
266 prop("counterexample contains arguments as tuple",
__anon61752d242502(const GenParams ¶ms) 267 [](const GenParams ¶ms) {
268 const auto gen = toProperty([=](Fixed<1>, Fixed<2>, Fixed<3>) {});
269 const auto shrinkable = gen(params.random, params.size);
270 const auto expected =
271 toString(std::make_tuple(Fixed<1>(), Fixed<2>(), Fixed<3>()));
272
273 onAnyPath(shrinkable,
274 [&](const ShrinkableResult &value,
275 const ShrinkableResult &shrink) {
276 RC_ASSERT(value.value().example().front().second ==
277 expected);
278 });
279 });
280
281 prop("counterexample contains string versions of picked values",
__anon61752d242802(const GenParams ¶ms) 282 [](const GenParams ¶ms) {
283 const auto n = *gen::inRange<std::size_t>(0, 10);
284 const auto gen = toProperty([=] {
285 for (std::size_t i = 0; i < n; i++) {
286 *gen::arbitrary<Fixed<1337>>();
287 }
288 });
289 const auto shrinkable = gen(params.random, params.size);
290 const auto expected = toString(Fixed<1337>());
291
292 onAnyPath(shrinkable,
293 [&](const ShrinkableResult &value,
294 const ShrinkableResult &shrink) {
295 for (const auto &desc : value.value().example()) {
296 RC_ASSERT(desc.second == expected);
297 }
298 });
299 });
300
301 prop("counterexample contains type of value generator has no name",
__anon61752d242b02(const GenParams ¶ms) 302 [](const GenParams ¶ms) {
303 const auto gen = toProperty([] { *gen::arbitrary<int>(); });
304 const auto shrinkable = gen(params.random, params.size);
305 const auto expected = typeToString<int>();
306
307 onAnyPath(shrinkable,
308 [&](const ShrinkableResult &value,
309 const ShrinkableResult &shrink) {
310 RC_ASSERT(value.value().example().front().first ==
311 expected);
312 });
313 });
314
315 prop("counterexample contains name of generator if it has one",
__anon61752d242e02(const GenParams ¶ms) 316 [](const GenParams ¶ms) {
317 const auto name = *gen::nonEmpty<std::string>();
318 const auto gen = gen::arbitrary<int>().as(name);
319 const auto property = toProperty([=] { *gen; });
320 const auto shrinkable = property(params.random, params.size);
321
322 onAnyPath(shrinkable,
323 [&](const ShrinkableResult &value,
324 const ShrinkableResult &shrink) {
325 RC_ASSERT(value.value().example().front().first == name);
326 });
327 });
328
329 prop(
330 "throws in counterexample is replaced with placeholders dscribing the"
331 " error",
__anon61752d243102(const GenParams ¶ms, const std::string &msg) 332 [](const GenParams ¶ms, const std::string &msg) {
333 const auto n = *gen::inRange<std::size_t>(1, 10);
334 const auto throwIndex = *gen::inRange<std::size_t>(0, n);
335 const auto gen = toProperty([=] {
336 for (std::size_t i = 0; i < n; i++) {
337 if (i == throwIndex) {
338 // TODO maybe a "throws" generator?
339 try {
340 // Introduce a dummy variable to prevent double-free error on LLVM 8.0.0.
341 auto dummy = Gen<int>([=](const Random &, int) -> Shrinkable<int> {
342 throw GenerationFailure(msg);
343 });
344 *dummy;
345 } catch (...) {
346 }
347 } else {
348 *gen::arbitrary<Fixed<1337>>();
349 }
350 }
351 });
352 const auto shrinkable = gen(params.random, params.size);
353 const std::pair<std::string, std::string> expected("Generation failed",
354 msg);
355
356 onAnyPath(
357 shrinkable,
358 [&](const ShrinkableResult &value, const ShrinkableResult &shrink) {
359 RC_ASSERT(value.value().example()[throwIndex] == expected);
360 });
361 });
362
363 prop("case result corresponds to counterexample",
__anon61752d243502(const GenParams ¶ms) 364 [](const GenParams ¶ms) {
365 const auto gen =
366 toProperty([=] { return (*gen::arbitrary<int>() % 2) == 0; });
367 const auto shrinkable = gen(params.random, params.size);
368
369 onAnyPath(
370 shrinkable,
371 [](const ShrinkableResult &value, const ShrinkableResult &shrink) {
372 const auto desc = value.value();
373 RC_ASSERT((desc.result.type == CaseResult::Type::Success) ==
374 ((std::stoi(desc.example().back().second) % 2) == 0));
375 });
376 });
377
378 prop("tags correspond to counterexample",
__anon61752d243802(const GenParams ¶ms) 379 [](const GenParams ¶ms) {
380 const auto gen = toProperty([=] {
381 const auto tags =
382 *gen::scale(0.25, gen::arbitrary<std::vector<std::string>>());
383 for (const auto &tag : tags) {
384 ImplicitParam<param::CurrentPropertyContext>::value()->addTag(tag);
385 }
386 });
387 const auto shrinkable = gen(params.random, params.size);
388
389 onAnyPath(
390 shrinkable,
391 [](const ShrinkableResult &value, const ShrinkableResult &shrink) {
392 const auto desc = value.value();
393 RC_ASSERT(toString(desc.tags) == desc.example().back().second);
394 });
395 });
396 }
397