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 ¶ms, const IntVec &s0) 87 [](const GenParams ¶ms, 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 ¶ms, const IntVec &s0) 98 [](const GenParams ¶ms, 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 ¶ms) 109 [](const GenParams ¶ms) {
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 ¶ms) 117 [](const GenParams ¶ms) {
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 ¶ms) 135 [](const GenParams ¶ms) {
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 ¶ms) 150 [](const GenParams ¶ms) {
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 ¶ms, const IntVec &s0) 172 [](const GenParams ¶ms, 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 ¶ms) 202 [](const GenParams ¶ms) {
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 ¶ms) 237 [](const GenParams ¶ms) {
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 ¶ms, const IntVec &s0) 268 [](const GenParams ¶ms, 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 ¶ms, const IntVec &s0) 277 [](const GenParams ¶ms, 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 ¶ms) 290 [](const GenParams ¶ms) {
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 ¶ms, const IntVec &s0) 299 [](const GenParams ¶ms, 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