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 &params) 267        [](const GenParams &params) {
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 &params) 282        [](const GenParams &params) {
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 &params) 302        [](const GenParams &params) {
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 &params) 316        [](const GenParams &params) {
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 &params, const std::string &msg) 332       [](const GenParams &params, 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 &params) 364        [](const GenParams &params) {
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 &params) 379        [](const GenParams &params) {
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