1 #include "catch.hpp"
2
3 #include <cstring>
4
5
6 // Generators and sections can be nested freely
7 TEST_CASE("Generators -- simple", "[generators]") {
8 auto i = GENERATE(1, 2, 3);
9 SECTION("one") {
10 auto j = GENERATE(values({ -3, -2, -1 }));
11 REQUIRE(j < i);
12 }
13
14 SECTION("two") {
15 // You can also explicitly set type for generators via Catch::Generators::as
16 auto str = GENERATE(as<std::string>{}, "a", "bb", "ccc");
17 REQUIRE(4u * i > str.size());
18 }
19 }
20
21 // You can create a cartesian-product of generators by creating multiple ones
22 TEST_CASE("3x3x3 ints", "[generators]") {
23 auto x = GENERATE(1, 2, 3);
24 auto y = GENERATE(4, 5, 6);
25 auto z = GENERATE(7, 8, 9);
26 // These assertions will be run 27 times (3x3x3)
27 CHECK(x < y);
28 CHECK(y < z);
29 REQUIRE(x < z);
30 }
31
32 // You can also create data tuples
33 TEST_CASE("tables", "[generators]") {
34 // Note that this will not compile with libstdc++ older than libstdc++6
35 // See https://stackoverflow.com/questions/12436586/tuple-vector-and-initializer-list
36 // for possible workarounds
37 // auto data = GENERATE(table<char const*, int>({
38 // {"first", 5},
39 // {"second", 6},
40 // {"third", 5},
41 // {"etc...", 6}
42 // }));
43
44 // Workaround for the libstdc++ bug mentioned above
45 using tuple_type = std::tuple<char const*, int>;
46 auto data = GENERATE(table<char const*, int>({
47 tuple_type{"first", 5},
48 tuple_type{"second", 6},
49 tuple_type{"third", 5},
50 tuple_type{"etc...", 6}
51 }));
52
53 REQUIRE(strlen(std::get<0>(data)) == static_cast<size_t>(std::get<1>(data)));
54 }
55
56
57 #ifdef __cpp_structured_bindings
58
59 // Structured bindings make the table utility much nicer to use
60 TEST_CASE( "strlen2", "[approvals][generators]" ) {
61 using tuple_type = std::tuple<std::string, int>; // see above workaround
62 auto [test_input, expected] =
63 GENERATE( table<std::string, size_t>( { tuple_type{ "one", 3 },
64 tuple_type{ "two", 3 },
65 tuple_type{ "three", 5 },
66 tuple_type{ "four", 4 } } ) );
67
68 REQUIRE( test_input.size() == expected );
69 }
70 #endif
71
72
73 // An alternate way of doing data tables without structured bindings
74 struct Data { std::string str; size_t len; };
75
76 TEST_CASE( "strlen3", "[generators]" ) {
77 auto data = GENERATE( values<Data>({
78 {"one", 3},
79 {"two", 3},
80 {"three", 5},
81 {"four", 4}
82 }));
83
84 REQUIRE( data.str.size() == data.len );
85 }
86
87
88
89 #ifdef __cpp_structured_bindings
90
91 // Based on example from https://docs.cucumber.io/gherkin/reference/#scenario-outline
92 // (thanks to https://github.com/catchorg/Catch2/issues/850#issuecomment-399504851)
93
94 // Note that GIVEN, WHEN, and THEN now forward onto DYNAMIC_SECTION instead of SECTION.
95 // DYNAMIC_SECTION takes its name as a stringstream-style expression, so can be formatted using
96 // variables in scope - such as the generated variables here. This reads quite nicely in the
97 // test name output (the full scenario description).
98
eatCucumbers(int start,int eat)99 static auto eatCucumbers( int start, int eat ) -> int { return start-eat; }
100
101 SCENARIO("Eating cucumbers", "[generators][approvals]") {
102 using tuple_type = std::tuple<int, int, int>;
103 auto [start, eat, left] = GENERATE( table<int, int, int>(
104 { tuple_type{ 12, 5, 7 }, tuple_type{ 20, 5, 15 } } ) );
105
106 GIVEN( "there are " << start << " cucumbers" )
107 WHEN( "I eat " << eat << " cucumbers" )
108 THEN( "I should have " << left << " cucumbers" ) {
109 REQUIRE( eatCucumbers( start, eat ) == left );
110 }
111 }
112 #endif
113
114 // There are also some generic generator manipulators
115 TEST_CASE("Generators -- adapters", "[generators][generic]") {
116 // TODO: This won't work yet, introduce GENERATE_VAR?
117 //auto numbers = Catch::Generators::values({ 1, 2, 3, 4, 5, 6 });
118 SECTION("Filtering by predicate") {
119 SECTION("Basic usage") {
120 // This filters out all odd (false) numbers, giving [2, 4, 6]
__anon9f036ea40102(int val) 121 auto i = GENERATE(filter([] (int val) { return val % 2 == 0; }, values({ 1, 2, 3, 4, 5, 6 })));
122 REQUIRE(i % 2 == 0);
123 }
124 SECTION("Throws if there are no matching values") {
125 using namespace Catch::Generators;
__anon9f036ea40202(int) 126 REQUIRE_THROWS_AS(filter([] (int) {return false; }, value(1)), Catch::GeneratorException);
127 }
128 }
129 SECTION("Shortening a range") {
130 // This takes the first 3 elements from the values, giving back [1, 2, 3]
131 auto i = GENERATE(take(3, values({ 1, 2, 3, 4, 5, 6 })));
132 REQUIRE(i < 4);
133 }
134 SECTION("Transforming elements") {
135 SECTION("Same type") {
136 // This doubles values [1, 2, 3] into [2, 4, 6]
__anon9f036ea40302(int val) 137 auto i = GENERATE(map([] (int val) { return val * 2; }, values({ 1, 2, 3 })));
138 REQUIRE(i % 2 == 0);
139 }
140 SECTION("Different type") {
141 // This takes a generator that returns ints and maps them into strings
__anon9f036ea40402(int val) 142 auto i = GENERATE(map<std::string>([] (int val) { return std::to_string(val); }, values({ 1, 2, 3 })));
143 REQUIRE(i.size() == 1);
144 }
145 SECTION("Different deduced type") {
146 // This takes a generator that returns ints and maps them into strings
__anon9f036ea40502(int val) 147 auto i = GENERATE(map([] (int val) { return std::to_string(val); }, values({ 1, 2, 3 })));
148 REQUIRE(i.size() == 1);
149 }
150 }
151 SECTION("Repeating a generator") {
152 // This will return values [1, 2, 3, 1, 2, 3]
153 auto j = GENERATE(repeat(2, values({ 1, 2, 3 })));
154 REQUIRE(j > 0);
155 }
156 SECTION("Chunking a generator into sized pieces") {
157 SECTION("Number of elements in source is divisible by chunk size") {
158 auto chunk2 = GENERATE(chunk(2, values({ 1, 1, 2, 2, 3, 3 })));
159 REQUIRE(chunk2.size() == 2);
160 REQUIRE(chunk2.front() == chunk2.back());
161 }
162 SECTION("Number of elements in source is not divisible by chunk size") {
163 auto chunk2 = GENERATE(chunk(2, values({ 1, 1, 2, 2, 3 })));
164 REQUIRE(chunk2.size() == 2);
165 REQUIRE(chunk2.front() == chunk2.back());
166 REQUIRE(chunk2.front() < 3);
167 }
168 SECTION("Chunk size of zero") {
169 auto chunk2 = GENERATE(take(3, chunk(0, value(1))));
170 REQUIRE(chunk2.size() == 0);
171 }
172 SECTION("Throws on too small generators") {
173 using namespace Catch::Generators;
174 REQUIRE_THROWS_AS(chunk(2, value(1)), Catch::GeneratorException);
175 }
176 }
177 }
178
179 // Note that because of the non-reproducibility of distributions,
180 // anything involving the random generators cannot be part of approvals
181 TEST_CASE("Random generator", "[generators][approvals]") {
182 SECTION("Infer int from integral arguments") {
183 auto val = GENERATE(take(4, random(0, 1)));
184 STATIC_REQUIRE(std::is_same<decltype(val), int>::value);
185 REQUIRE(0 <= val);
186 REQUIRE(val <= 1);
187 }
188 SECTION("Infer double from double arguments") {
189 auto val = GENERATE(take(4, random(0., 1.)));
190 STATIC_REQUIRE(std::is_same<decltype(val), double>::value);
191 REQUIRE(0. <= val);
192 REQUIRE(val < 1);
193 }
194 }
195
196
197 TEST_CASE("Nested generators and captured variables", "[generators]") {
198 // Workaround for old libstdc++
199 using record = std::tuple<int, int>;
200 // Set up 3 ranges to generate numbers from
201 auto extent = GENERATE(table<int, int>({
202 record{3, 7},
203 record{-5, -3},
204 record{90, 100}
205 }));
206
207 auto from = std::get<0>(extent);
208 auto to = std::get<1>(extent);
209
210 auto values = GENERATE_COPY(range(from, to));
211 REQUIRE(values > -6);
212 }
213
214 namespace {
215 size_t call_count = 0;
216 size_t test_count = 0;
make_data()217 std::vector<int> make_data() {
218 return { 1, 3, 5, 7, 9, 11 };
219 }
make_data_counted()220 std::vector<int> make_data_counted() {
221 ++call_count;
222 return make_data();
223 }
224 }
225
226 #if defined(__clang__)
227 #pragma clang diagnostic push
228 #pragma clang diagnostic ignored "-Wexit-time-destructors"
229 #endif
230
231 TEST_CASE("Copy and then generate a range", "[generators]") {
232 SECTION("from var and iterators") {
233 static auto data = make_data();
234
235 // It is important to notice that a generator is only initialized
236 // **once** per run. What this means is that modifying data will not
237 // modify the underlying generator.
238 auto elem = GENERATE_REF(from_range(data.begin(), data.end()));
239 REQUIRE(elem % 2 == 1);
240 }
241 SECTION("From a temporary container") {
242 auto elem = GENERATE(from_range(make_data_counted()));
243 ++test_count;
244 REQUIRE(elem % 2 == 1);
245 }
246 SECTION("Final validation") {
247 REQUIRE(call_count == 1);
248 REQUIRE(make_data().size() == test_count);
249 }
250 }
251
252 TEST_CASE("#1913 - GENERATE inside a for loop should not keep recreating the generator", "[regression][generators]") {
253 static int counter = 0;
254 for (int i = 0; i < 3; ++i) {
255 int _ = GENERATE(1, 2);
256 (void)_;
257 ++counter;
258 }
259 // There should be at most 6 (3 * 2) counter increments
260 REQUIRE(counter < 7);
261 }
262
263 TEST_CASE("#1913 - GENERATEs can share a line", "[regression][generators]") {
264 int i = GENERATE(1, 2); int j = GENERATE(3, 4);
265 REQUIRE(i != j);
266 }
267
268
269 #if defined(__clang__)
270 #pragma clang diagnostic pop
271 #endif
272