181 {
182 std::regex enabled(".*"), disabled;
183 bool list_tests = false;
184 std::string output_xml;
185 for(int n = 1; n < argc; n++)
186 {
187
188 if(argv[n][0] == '-' && argv[n][1] == '-')
189 {
190 if(strstr(argv[n] + 2, "help"))
191 {
192 std::cout << "\nQuickCppLib minimal unit test framework\n\n"
193 << "Usage: " << argv[0]
194 << " [options] [<regex for tests to run, defaults to .*>] [-<regex for tests to not run>]\n\n"
195 << " --help : Prints this help\n"
196 << " --list-tests : List matching tests\n"
197 << " --reporter <format> : Reporter to use, only format possible is junit\n"
198 << " --out <filename> : Write JUnit XML test report to filename\n"
199 << std::endl;
200 return 0;
201 }
202 else if(strstr(argv[n] + 2, "list-tests"))
203 {
204 list_tests = true;
205 }
206 else if(strstr(argv[n] + 2, "reporter"))
207 {
208 if(n + 1 >= argc || strcmp(argv[n + 1], "junit"))
209 {
210 std::cerr << "--reporter must be followed by 'junit'" << std::endl;
211 return 1;
212 }
213 n++;
214 }
215 else if(strstr(argv[n] + 2, "out"))
216 {
217 if(n + 1 >= argc)
218 {
219 std::cerr << "--out must be followed by the output filename" << std::endl;
220 return 1;
221 }
222 output_xml = argv[n + 1];
223 n++;
224 }
225 }
226 else
227 {
228
229 if(argv[n][0] == '-')
230 disabled.assign(argv[n] + 1);
231 else
232 enabled.assign(argv[n]);
233 }
234 }
235 if(list_tests)
236 {
237 size_t maxname = 0;
239 {
240 for(const auto &i : j.test_cases)
241 {
242 if(strlen(i.name) > maxname)
243 maxname = strlen(i.name);
244 }
245 }
247 {
248 std::cout << "\n" << j.name << ":\n";
249 for(const auto &i : j.test_cases)
250 {
251 if(std::regex_match(i.name, enabled) && !std::regex_match(i.name, disabled))
252 {
253 std::string padding(maxname - strlen(i.name), ' ');
254 std::cout << " " << i.name << padding << " (" << i.desc << ")\n";
255 }
256 }
257 }
258 std::cout << std::endl;
259 return 0;
260 }
262 {
263 for(auto &i : j.test_cases)
264 {
265 if(std::regex_match(i.name, enabled) && !std::regex_match(i.name, disabled))
266 {
268 std::cout << std::endl <<
bold <<
blue << i.name <<
white <<
" : " << i.desc <<
normal << std::endl;
269 std::chrono::steady_clock::time_point begin, end;
270#ifdef __cpp_exceptions
271 try
272 {
273#else
275 {
276 end = std::chrono::steady_clock::now();
277 i.requirement_failed = true;
278 }
279 else
280#endif
281 {
282 begin = std::chrono::steady_clock::now();
283 i.func();
284 end = std::chrono::steady_clock::now();
285 }
286#ifdef __cpp_exceptions
287 }
288 catch(const requirement_failed &)
289 {
290 end = std::chrono::steady_clock::now();
291 i.requirement_failed = true;
292 }
293 catch(const std::exception &e)
294 {
295 end = std::chrono::steady_clock::now();
296 ++i.fails;
297 std::cerr <<
red <<
"FAILURE: std::exception '" << e.what() <<
"' thrown out of test case" <<
normal
298 << std::endl;
299 }
300 catch(...)
301 {
302 end = std::chrono::steady_clock::now();
303 ++i.fails;
304 std::cerr <<
red <<
"FAILURE: Exception thrown out of test case" <<
normal << std::endl;
305 }
306#endif
307 i.duration = end - begin;
308 if(i.passes)
309 std::cout <<
green << i.passes <<
" checks passed ";
310 if(i.fails)
311 std::cout <<
red << i.fails <<
" checks failed ";
312 std::cout <<
normal <<
"duration "
313 << std::chrono::duration_cast<std::chrono::milliseconds>(i.duration).count() << " ms" << std::endl;
314 }
315 else
316 i.skipped = true;
317 }
318 }
320 std::ofstream oh;
321 if(!output_xml.empty())
322 {
323 oh.open(output_xml);
324 oh << R"(<?xml version="1.0" encoding="UTF-8"?>
325<testsuites>
326)";
327 }
328 size_t totalpassed = 0, totalfailed = 0, totalskipped = 0;
329 double totaltime = 0;
331 {
332 size_t passed = 0, failed = 0, skipped = 0;
333 double time = 0;
334 for(const auto &i : j.test_cases)
335 {
336 if(i.skipped)
337 ++skipped;
338 else if(i.fails)
339 ++failed;
340 else
341 ++passed;
342 time += std::chrono::duration_cast<std::chrono::duration<double>>(i.duration).count();
343 }
344 if(!output_xml.empty())
345 {
346 oh << " <testsuite name=\"" << j.name << "\" errors=\"" << 0 << "\" failures=\"" << failed << "\" skipped=\""
347 << skipped << "\" tests=\"" << j.test_cases.size() << "\" hostname=\"\" time=\"" << time
348 << "\" timestamp=\"\">\n";
349 for(const auto &i : j.test_cases)
350 {
351 oh << " <testcase classname=\"\" name=\"" << i.name << "\" time=\""
352 << std::chrono::duration_cast<std::chrono::duration<double>>(i.duration).count() << "\">";
353 if(i.skipped)
354 oh << "<skipped/>";
355 else if(i.fails)
356 oh << "<failure/>";
357 oh << "</testcase>\n";
358 }
359 oh << " </testsuite>\n";
360 }
361 totalpassed += passed;
362 totalfailed += failed;
363 totalskipped += skipped;
364 totaltime += time;
365 }
366 if(!output_xml.empty())
367 {
368 oh << R"(</testsuites>
369)";
370 }
371 std::cout << bold << white << "\n\nTest case summary: " <<
green << totalpassed <<
" passed " <<
red << totalfailed
372 <<
" failed " <<
yellow << totalskipped <<
" skipped" <<
normal <<
"\nTotal duration: " << totaltime
constexpr const char bold[]
Definition console_colours.hpp:135
constexpr const char yellow[]
Definition console_colours.hpp:131
constexpr const char normal[]
Definition console_colours.hpp:136
constexpr const char blue[]
Definition console_colours.hpp:130
constexpr const char red[]
Definition console_colours.hpp:128
constexpr const char green[]
Definition console_colours.hpp:129
constexpr const char white[]
Definition console_colours.hpp:134
std::vector< test_suite > & test_suites()
Definition unit_test.hpp:163
jmp_buf & test_case_failed()
Definition unit_test.hpp:112
test_case *& current_test_case()
Definition unit_test.hpp:173