1 #include <catch2/catch.hpp>
2 #include <rapidcheck/catch.h>
3 #include <rapidcheck/state.h>
4 
5 #include "util/GenUtils.h"
6 #include "util/IntVec.h"
7 #include "util/NonCopyableModel.h"
8 #include "util/ShrinkableUtils.h"
9 
10 using namespace rc;
11 using namespace rc::test;
12 
13 namespace {
14 
15 struct ParamsCmd : IntVecCmd {
16   Random random;
17   int size;
18 };
19 
captureParams(const IntVec & vec)20 Gen<IntVecCmdSP> captureParams(const IntVec &vec) {
21   return [](const Random &random, int size) {
22     auto paramsCmd = std::make_shared<ParamsCmd>();
23     paramsCmd->random = random;
24     paramsCmd->size = size;
25     return shrinkable::just(
26         std::static_pointer_cast<const IntVecCmd>(paramsCmd));
27   };
28 }
29 
collectParams(const IntVecCmds & cmds)30 std::vector<GenParams> collectParams(const IntVecCmds &cmds) {
31   std::vector<GenParams> params;
32   std::transform(begin(cmds),
33                  end(cmds),
34                  std::back_inserter(params),
35                  [](const IntVecCmdSP &cmd) {
36                    const auto paramsCmd =
37                        std::static_pointer_cast<const ParamsCmd>(cmd);
38                    GenParams lambdaParams;
39                    lambdaParams.random = paramsCmd->random;
40                    lambdaParams.size = paramsCmd->size;
41                    return lambdaParams;
42                  });
43   return params;
44 }
45 
collectRandoms(const IntVecCmds & cmds)46 std::set<Random> collectRandoms(const IntVecCmds &cmds) {
47   const auto params = collectParams(cmds);
48   std::set<Random> randoms;
49   std::transform(begin(params),
50                  end(params),
51                  std::inserter(randoms, randoms.end()),
52                  [](const GenParams &lambdaParams) { return lambdaParams.random; });
53   return randoms;
54 }
55 
56 struct CountCmd : public IntVecCmd {
CountCmd__anon850b8b480111::CountCmd57   CountCmd(int x)
58       : value(x) {}
59   int value;
60 
checkPreconditions__anon850b8b480111::CountCmd61   void checkPreconditions(const IntVec &s0) const override {
62     RC_PRE(s0.back() == (value - 1));
63   }
64 
apply__anon850b8b480111::CountCmd65   void apply(IntVec &s0) const override { s0.push_back(value); }
66 
run__anon850b8b480111::CountCmd67   void run(const IntVec &s0, IntVec &sut) const override {
68     sut.push_back(value);
69   }
70 
show__anon850b8b480111::CountCmd71   void show(std::ostream &os) const override { os << value; }
72 };
73 
74 struct TaggedCountdownCmd : public state::Command<bool, bool> {
75   int countdown = *genCountdown();
76   int tag = *gen::noShrink(gen::arbitrary<int>());
77 };
78 
79 struct DeprecatedCmd : public IntVecCmd {
apply__anon850b8b480111::DeprecatedCmd80   void apply(IntVec &s0) const override { RC_DISCARD(); }
81 };
82 
83 } // namespace
84 
85 TEST_CASE("state::gen::commands") {
86   prop("command sequences are always valid",
__anon850b8b480502(const GenParams &params, const IntVec &s0) 87        [](const GenParams &params, const IntVec &s0) {
88          const auto gen = state::gen::commands(
89              s0, state::gen::execOneOfWithArgs<PushBack, PopBack>());
90          onAnyPath(gen(params.random, params.size),
91                    [&](const Shrinkable<IntVecCmds> &value,
92                        const Shrinkable<IntVecCmds> &shrink) {
93                      RC_ASSERT(isValidSequence(value.value(), s0));
94                    });
95        });
96 
97   prop("shrinks are shorter or equal length when compared to original",
__anon850b8b480702(const GenParams &params, const IntVec &s0) 98        [](const GenParams &params, const IntVec &s0) {
99          const auto gen = state::gen::commands(
100              s0, state::gen::execOneOfWithArgs<PushBack, PopBack>());
101          onAnyPath(gen(params.random, params.size),
102                    [&](const Shrinkable<IntVecCmds> &value,
103                        const Shrinkable<IntVecCmds> &shrink) {
104                      RC_ASSERT(value.value().size() <= value.value().size());
105                    });
106        });
107 
108   prop("passed random generators are unique",
__anon850b8b480902(const GenParams &params) 109        [](const GenParams &params) {
110          const auto gen = state::gen::commands(IntVec(), &captureParams);
111          const auto cmds = gen(params.random, params.size).value();
112          const auto randoms = collectRandoms(cmds);
113          RC_ASSERT(randoms.size() == cmds.size());
114        });
115 
116   prop("shrinks use a subset of the original random generators",
__anon850b8b480a02(const GenParams &params) 117        [](const GenParams &params) {
118          const auto gen = state::gen::commands(IntVec(), &captureParams);
119          onAnyPath(gen(params.random, params.size),
120                    [&](const Shrinkable<IntVecCmds> &value,
121                        const Shrinkable<IntVecCmds> &shrink) {
122                      const auto valueRandoms = collectRandoms(value.value());
123                      const auto shrinkRandoms = collectRandoms(shrink.value());
124                      std::vector<Random> intersection;
125                      std::set_intersection(begin(valueRandoms),
126                                            end(valueRandoms),
127                                            begin(shrinkRandoms),
128                                            end(shrinkRandoms),
129                                            std::back_inserter(intersection));
130                      RC_ASSERT(intersection.size() == shrinkRandoms.size());
131                    });
132        });
133 
134   prop("passes the correct size",
__anon850b8b480c02(const GenParams &params) 135        [](const GenParams &params) {
136          const auto gen = state::gen::commands(IntVec(), &captureParams);
137          onAnyPath(gen(params.random, params.size),
138                    [&](const Shrinkable<IntVecCmds> &value,
139                        const Shrinkable<IntVecCmds> &shrink) {
140                      const auto allParams = collectParams(value.value());
141                      RC_ASSERT(std::all_of(begin(allParams),
142                                            end(allParams),
143                                            [&](const GenParams &p) {
144                                              return p.size == params.size;
145                                            }));
146                    });
147        });
148 
149   prop("correctly threads the state when generating commands",
__anon850b8b480f02(const GenParams &params) 150        [](const GenParams &params) {
151          IntVec s0({0});
152          const auto gen = state::gen::commands(
153              s0,
154              [](const IntVec &vec) {
155                auto cmd = std::make_shared<const CountCmd>(vec.back() + 1);
156                return gen::just(std::static_pointer_cast<const IntVecCmd>(cmd));
157              });
158 
159          onAnyPath(gen(params.random, params.size),
160                    [&](const Shrinkable<IntVecCmds> &value,
161                        const Shrinkable<IntVecCmds> &shrink) {
162                      auto sut = s0;
163                      runAll(value.value(), s0, sut);
164                      int x = 0;
165                      for (int lambdaValue : sut) {
166                        RC_ASSERT(lambdaValue == x++);
167                      }
168                    });
169        });
170 
171   prop("finds minimum where one commands always fails",
__anon850b8b481202(const GenParams &params, const IntVec &s0) 172        [](const GenParams &params, const IntVec &s0) {
173          const auto gen =
174              state::gen::commands(s0,
175                                   state::gen::execOneOfWithArgs<AlwaysFail,
176                                                                 PushBack,
177                                                                 PopBack,
178                                                                 SomeCommand>());
179          const auto result = searchGen(params.random,
180                                        params.size,
181                                        gen,
182                                        [&](const IntVecCmds &cmds) {
183                                          try {
184                                            IntVec sut = s0;
185                                            runAll(cmds, s0, sut);
186                                          } catch (...) {
187                                            return true;
188                                          }
189                                          return false;
190 
191                                        });
192 
193          RC_ASSERT(result.size() == 1U);
194          std::ostringstream os;
195          result.front()->show(os);
196          RC_ASSERT(os.str().find("AlwaysFail") != std::string::npos);
197        });
198 
199   prop(
200       "for every shrink for every command, there exists a shrink of the "
201       "sequence that includes that shrink",
__anon850b8b481402(const GenParams &params) 202       [](const GenParams &params) {
203         using CommandType = TaggedCountdownCmd::CommandType;
204         using CommandsType = state::Commands<CommandType>;
205 
206         const auto gen = state::gen::commands(
207             false, state::gen::execOneOfWithArgs<TaggedCountdownCmd>());
208         const auto shrinkable = gen(params.random, params.size);
209 
210         // Pick one of the commands
211         const auto commands = shrinkable.value();
212         const auto cmdIndex = *gen::inRange<std::size_t>(0, commands.size());
213         const auto &command =
214             static_cast<const TaggedCountdownCmd &>(*commands[cmdIndex]);
215 
216         // Expect to find a command with the same tag but smaller countdown.
217         const auto expectedCountdown = *gen::inRange(0, command.countdown);
218         RC_ASSERT(seq::any(shrinkable.shrinks(),
219                            [&](const Shrinkable<CommandsType> &shrink) {
220                              for (const auto &cmd : shrink.value()) {
221                                const auto tccmd =
222                                    static_cast<const TaggedCountdownCmd &>(
223                                        *cmd);
224                                if ((tccmd.tag == command.tag) &&
225                                    (tccmd.countdown == expectedCountdown)) {
226                                  return true;
227                                }
228                              }
229 
230                              return false;
231                            }));
232       });
233 
234   prop(
235       "for every command, there exists a shrink of the sequence that does not "
236       "include that command",
__anon850b8b481602(const GenParams &params) 237       [](const GenParams &params) {
238         using CommandType = TaggedCountdownCmd::CommandType;
239         using CommandsType = state::Commands<CommandType>;
240 
241         const auto gen = state::gen::commands(
242             false, state::gen::execOneOfWithArgs<TaggedCountdownCmd>());
243         const auto shrinkable = gen(params.random, params.size);
244 
245         // Pick one of the commands
246         const auto commands = shrinkable.value();
247         const auto cmdIndex = *gen::inRange<std::size_t>(0, commands.size());
248         const auto &command =
249             static_cast<const TaggedCountdownCmd &>(*commands[cmdIndex]);
250 
251         // Expect to find a shrink that has no command with that tag.
252         RC_ASSERT(seq::any(shrinkable.shrinks(),
253                            [&](const Shrinkable<CommandsType> &shrink) {
254                              for (const auto &cmd : shrink.value()) {
255                                const auto tag =
256                                    static_cast<const TaggedCountdownCmd &>(*cmd)
257                                        .tag;
258                                if (tag == command.tag) {
259                                  return false;
260                                }
261                              }
262 
263                              return true;
264                            }));
265       });
266 
267   prop("gives up if unable to generate sequence of enough length",
__anon850b8b481802(const GenParams &params, const IntVec &s0) 268        [](const GenParams &params, const IntVec &s0) {
269          const auto gen = state::gen::commands(
270              s0, state::gen::execOneOfWithArgs<PreNeverHolds>());
271 
272          const auto shrinkable = gen(params.random, params.size);
273          RC_ASSERT_THROWS_AS(shrinkable.value(), GenerationFailure);
274        });
275 
276   prop("discards commands that discard in constructor",
__anon850b8b481902(const GenParams &params, const IntVec &s0) 277        [](const GenParams &params, const IntVec &s0) {
278          const auto gen = state::gen::commands(
279              s0,
280              state::gen::execOneOfWithArgs<DiscardInConstructor, PushBack>());
281          const auto commands = gen(params.random, params.size).value();
282          for (const auto &cmd : commands) {
283            std::ostringstream ss;
284            cmd->show(ss);
285            RC_ASSERT(ss.str() != "DiscardInConstructor");
286          }
287        });
288 
289   prop("works with non-copyable models",
__anon850b8b481a02(const GenParams &params) 290        [](const GenParams &params) {
291          const auto commands = *genNonCopyableCommands();
292          RC_ASSERT(isValidSequence(commands, &initialNonCopyableModel));
293 
294          NonCopyableModel s0;
295          state::applyAll(commands, s0);
296        });
297 
298   prop("throws GenerationFailure if command discards in apply(...)",
__anon850b8b481b02(const GenParams &params, const IntVec &s0) 299        [](const GenParams &params, const IntVec &s0) {
300          RC_PRE(params.size > 0);
301          const auto gen = state::gen::commands(
302              s0, state::gen::execOneOfWithArgs<DeprecatedCmd>());
303          RC_ASSERT_THROWS_AS(gen(params.random, params.size).value(),
304                              GenerationFailure);
305        });
306 }
307