• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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     auto [test_input, expected] = GENERATE( table<std::string, size_t>({
62             {"one", 3},
63             {"two", 3},
64             {"three", 5},
65             {"four", 4}
66         }));
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 
103     auto [start, eat, left] = GENERATE( table<int,int,int> ({
104             { 12, 5, 7 },
105             { 20, 5, 15 }
106         }));
107 
108     GIVEN( "there are " << start << " cucumbers" )
109     WHEN( "I eat " << eat << " cucumbers" )
110     THEN( "I should have " << left << " cucumbers" ) {
111         REQUIRE( eatCucumbers( start, eat ) == left );
112     }
113 }
114 #endif
115 
116 // There are also some generic generator manipulators
117 TEST_CASE("Generators -- adapters", "[generators][generic]") {
118     // TODO: This won't work yet, introduce GENERATE_VAR?
119     //auto numbers = Catch::Generators::values({ 1, 2, 3, 4, 5, 6 });
120     SECTION("Filtering by predicate") {
121         SECTION("Basic usage") {
122             // This filters out all odd (false) numbers, giving [2, 4, 6]
__anoncb67c6960102(int val) 123             auto i = GENERATE(filter([] (int val) { return val % 2 == 0; }, values({ 1, 2, 3, 4, 5, 6 })));
124             REQUIRE(i % 2 == 0);
125         }
126         SECTION("Throws if there are no matching values") {
127             using namespace Catch::Generators;
__anoncb67c6960202(int) 128             REQUIRE_THROWS_AS(filter([] (int) {return false; }, value(1)), Catch::GeneratorException);
129         }
130     }
131     SECTION("Shortening a range") {
132         // This takes the first 3 elements from the values, giving back [1, 2, 3]
133         auto i = GENERATE(take(3, values({ 1, 2, 3, 4, 5, 6 })));
134         REQUIRE(i < 4);
135     }
136     SECTION("Transforming elements") {
137         SECTION("Same type") {
138             // This doubles values [1, 2, 3] into [2, 4, 6]
__anoncb67c6960302(int val) 139             auto i = GENERATE(map([] (int val) { return val * 2; }, values({ 1, 2, 3 })));
140             REQUIRE(i % 2 == 0);
141         }
142         SECTION("Different type") {
143             // This takes a generator that returns ints and maps them into strings
__anoncb67c6960402(int val) 144             auto i = GENERATE(map<std::string>([] (int val) { return std::to_string(val); }, values({ 1, 2, 3 })));
145             REQUIRE(i.size() == 1);
146         }
147         SECTION("Different deduced type") {
148             // This takes a generator that returns ints and maps them into strings
__anoncb67c6960502(int val) 149             auto i = GENERATE(map([] (int val) { return std::to_string(val); }, values({ 1, 2, 3 })));
150             REQUIRE(i.size() == 1);
151         }
152     }
153     SECTION("Repeating a generator") {
154         // This will return values [1, 2, 3, 1, 2, 3]
155         auto j = GENERATE(repeat(2, values({ 1, 2, 3 })));
156         REQUIRE(j > 0);
157     }
158     SECTION("Chunking a generator into sized pieces") {
159         SECTION("Number of elements in source is divisible by chunk size") {
160             auto chunk2 = GENERATE(chunk(2, values({ 1, 1, 2, 2, 3, 3 })));
161             REQUIRE(chunk2.size() == 2);
162             REQUIRE(chunk2.front() == chunk2.back());
163         }
164         SECTION("Number of elements in source is not divisible by chunk size") {
165             auto chunk2 = GENERATE(chunk(2, values({ 1, 1, 2, 2, 3 })));
166             REQUIRE(chunk2.size() == 2);
167             REQUIRE(chunk2.front() == chunk2.back());
168             REQUIRE(chunk2.front() < 3);
169         }
170         SECTION("Chunk size of zero") {
171             auto chunk2 = GENERATE(take(3, chunk(0, value(1))));
172             REQUIRE(chunk2.size() == 0);
173         }
174         SECTION("Throws on too small generators") {
175             using namespace Catch::Generators;
176             REQUIRE_THROWS_AS(chunk(2, value(1)), Catch::GeneratorException);
177         }
178     }
179 }
180 
181 // Note that because of the non-reproducibility of distributions,
182 // anything involving the random generators cannot be part of approvals
183 TEST_CASE("Random generator", "[generators][approvals]") {
184     SECTION("Infer int from integral arguments") {
185         auto val = GENERATE(take(4, random(0, 1)));
186         STATIC_REQUIRE(std::is_same<decltype(val), int>::value);
187         REQUIRE(0 <= val);
188         REQUIRE(val <= 1);
189     }
190     SECTION("Infer double from double arguments") {
191         auto val = GENERATE(take(4, random(0., 1.)));
192         STATIC_REQUIRE(std::is_same<decltype(val), double>::value);
193         REQUIRE(0. <= val);
194         REQUIRE(val < 1);
195     }
196 }
197 
198 
199 TEST_CASE("Nested generators and captured variables", "[generators]") {
200     // Workaround for old libstdc++
201     using record = std::tuple<int, int>;
202     // Set up 3 ranges to generate numbers from
203     auto extent = GENERATE(table<int, int>({
204         record{3, 7},
205         record{-5, -3},
206         record{90, 100}
207     }));
208 
209     auto from = std::get<0>(extent);
210     auto to = std::get<1>(extent);
211 
212     auto values = GENERATE_COPY(range(from, to));
213     REQUIRE(values > -6);
214 }
215 
216 namespace {
217     size_t call_count = 0;
218     size_t test_count = 0;
make_data()219     std::vector<int> make_data() {
220         return { 1, 3, 5, 7, 9, 11 };
221     }
make_data_counted()222     std::vector<int> make_data_counted() {
223         ++call_count;
224         return make_data();
225     }
226 }
227 
228 #if defined(__clang__)
229 #pragma clang diagnostic push
230 #pragma clang diagnostic ignored "-Wexit-time-destructors"
231 #endif
232 
233 TEST_CASE("Copy and then generate a range", "[generators]") {
234     SECTION("from var and iterators") {
235         static auto data = make_data();
236 
237         // It is important to notice that a generator is only initialized
238         // **once** per run. What this means is that modifying data will not
239         // modify the underlying generator.
240         auto elem = GENERATE_REF(from_range(data.begin(), data.end()));
241         REQUIRE(elem % 2 == 1);
242     }
243     SECTION("From a temporary container") {
244         auto elem = GENERATE(from_range(make_data_counted()));
245         ++test_count;
246         REQUIRE(elem % 2 == 1);
247     }
248     SECTION("Final validation") {
249         REQUIRE(call_count == 1);
250         REQUIRE(make_data().size() == test_count);
251     }
252 }
253 
254 #if defined(__clang__)
255 #pragma clang diagnostic pop
256 #endif
257