Logo AND Algorithmique Numérique Distribuée

Public GIT Repository
cunit: use string and iostream.
[simgrid.git] / src / xbt / cunit.cpp
1 /* cunit - A little C Unit facility                                         */
2
3 /* Copyright (c) 2005-2017. The SimGrid Team. All rights reserved.          */
4
5 /* This program is free software; you can redistribute it and/or modify it
6  * under the terms of the license (GNU LGPL) which comes with this package. */
7
8 /* This is partially inspirated from the OSSP ts (Test Suite Library)       */
9 /* At some point we should use https://github.com/google/googletest instead */
10
11 #include "src/internal_config.h"
12 #include <algorithm>
13 #include <iostream>
14 #include <string>
15 #include <vector>
16
17 #include <xbt/cunit.h>
18 #include <xbt/ex.hpp>
19 #include <xbt/string.hpp>
20
21 /* collection of all suites */
22 static std::vector<xbt_test_suite_t> _xbt_test_suites;
23 /* global statistics */
24 static int _xbt_test_nb_tests = 0;
25 static int _xbt_test_test_failed = 0;
26 static int _xbt_test_test_ignore = 0;
27 static int _xbt_test_test_expect = 0;
28
29 static int _xbt_test_nb_units = 0;
30 static int _xbt_test_unit_failed = 0;
31 static int _xbt_test_unit_ignore = 0;
32 static int _xbt_test_unit_disabled = 0;
33
34 static int _xbt_test_nb_suites = 0;
35 static int _xbt_test_suite_failed = 0;
36 static int _xbt_test_suite_ignore = 0;
37 static int _xbt_test_suite_disabled = 0;
38
39 /* Context */
40 xbt_test_unit_t _xbt_test_current_unit = nullptr;
41
42 /* test suite test log */
43 class s_xbt_test_log {
44 public:
45   s_xbt_test_log(std::string text, std::string file, int line)
46       : text_(std::move(text)), file_(std::move(file)), line_(line)
47   {
48   }
49   void dump() const;
50
51   std::string text_;
52   std::string file_;
53   int line_;
54 };
55
56 void s_xbt_test_log::dump() const
57 {
58   std::cerr << "      log " << this << "(" << file_ << ":" << line_ << ")=" << text_ << "\n";
59 }
60
61 /* test suite test check */
62 class s_xbt_test_test {
63 public:
64   s_xbt_test_test(std::string title, std::string file, int line)
65       : title_(std::move(title)), file_(std::move(file)), line_(line)
66   {
67   }
68   void dump() const;
69
70   std::string title_;
71   bool failed_           = false;
72   bool expected_failure_ = false;
73   bool ignored_          = false;
74   std::string file_;
75   int line_;
76   std::vector<s_xbt_test_log> logs_;
77 };
78
79 void s_xbt_test_test::dump() const
80 {
81   std::cerr << "    test " << this << "(" << file_ << ":" << line_ << ")=" << title_ << " ("
82             << (failed_ ? "failed" : "not failed") << ")\n";
83   for (s_xbt_test_log const& log : this->logs_)
84     log.dump();
85 }
86
87 /* test suite test unit */
88 class s_xbt_test_unit {
89 public:
90   s_xbt_test_unit(std::string name, std::string title, ts_test_cb_t func)
91       : name_(std::move(name)), title_(std::move(title)), func_(func)
92   {
93   }
94   void dump() const;
95
96   std::string name_;
97   std::string title_;
98   ts_test_cb_t func_;
99   std::vector<s_xbt_test_test> tests_;
100
101   bool enabled_    = true;
102   int nb_tests_    = 0;
103   int test_failed_ = 0;
104   int test_ignore_ = 0;
105   int test_expect_ = 0;
106 };
107
108 void s_xbt_test_unit::dump() const
109 {
110   std::cerr << "  UNIT " << name_ << ": " << title_ << " (" << (this->enabled_ ? "enabled" : "disabled") << ")\n";
111   if (this->enabled_) {
112     for (s_xbt_test_test const& test : this->tests_)
113       test.dump();
114   }
115 }
116
117 /* test suite */
118 class s_xbt_test_suite {
119 public:
120   s_xbt_test_suite(std::string name, std::string title) : name_(std::move(name)), title_(std::move(title)) {}
121   void dump() const;
122   void push(s_xbt_test_unit unit) { units_.emplace_back(std::move(unit)); }
123   int run(int verbosity);
124
125   std::string name_;
126   std::string title_;
127   std::vector<s_xbt_test_unit> units_;
128
129   bool enabled_      = true;
130   int nb_tests_      = 0;
131   int nb_units_      = 0;
132   int test_failed_   = 0;
133   int test_ignore_   = 0;
134   int test_expect_   = 0;
135   int unit_failed_   = 0;
136   int unit_ignore_   = 0;
137   int unit_disabled_ = 0;
138 };
139
140 /** @brief retrieve a testsuite from name, or create a new one */
141 xbt_test_suite_t xbt_test_suite_by_name(const char *name, const char *fmt, ...)
142 {
143   auto res = std::find_if(begin(_xbt_test_suites), end(_xbt_test_suites),
144                           [&name](xbt_test_suite_t const& suite) { return suite->name_ == name; });
145   if (res != end(_xbt_test_suites))
146     return *res;
147
148   va_list ap;
149   va_start(ap, fmt);
150   xbt_test_suite_t suite = new s_xbt_test_suite(name, simgrid::xbt::string_vprintf(fmt, ap));
151   va_end(ap);
152
153   _xbt_test_suites.push_back(suite);
154
155   return suite;
156 }
157
158 void s_xbt_test_suite::dump() const
159 {
160   std::cerr << "TESTSUITE " << name_ << ": " << title_ << " (" << (this->enabled_ ? "enabled" : "disabled") << ")\n";
161   if (this->enabled_) {
162     for (s_xbt_test_unit const& unit : this->units_)
163       unit.dump();
164   }
165 }
166
167 void xbt_test_suite_dump(xbt_test_suite_t suite)
168 {
169   suite->dump();
170 }
171
172 /* add test case to test suite */
173 void xbt_test_suite_push(xbt_test_suite_t suite, const char *name, ts_test_cb_t func, const char *fmt, ...)
174 {
175   xbt_assert(suite);
176   xbt_assert(func);
177   xbt_assert(fmt);
178
179   va_list ap;
180   va_start(ap, fmt);
181   s_xbt_test_unit unit(name, simgrid::xbt::string_vprintf(fmt, ap), func);
182   va_end(ap);
183   suite->push(unit);
184 }
185
186 /* run test one suite */
187 int s_xbt_test_suite::run(int verbosity)
188 {
189   /* suite title pretty-printing */
190   int suite_len = this->title_.length();
191   xbt_assert(suite_len < 68, "suite title \"%s\" too long (%d should be less than 68", this->title_.c_str(), suite_len);
192
193   std::string suite_title = " ";
194   suite_title.resize(40 - (suite_len + 4) / 2, '=');
195   suite_title += std::string("[ ") + this->title_ + " ]";
196   suite_title.resize(79, '=');
197   if (not this->enabled_)
198     suite_title.replace(70, std::string::npos, " DISABLED");
199   std::cerr << "\n" << suite_title << "\n";
200
201   if (this->enabled_) {
202     /* iterate through all tests */
203     for (s_xbt_test_unit& unit : this->units_) {
204       /* init unit case counters */
205       unit.nb_tests_    = 0;
206       unit.test_ignore_ = 0;
207       unit.test_failed_ = 0;
208       unit.test_expect_ = 0;
209
210       /* display unit title */
211       std::string cp = std::string(" Unit: ") + unit.title_ + " ";
212       cp.resize(70, '.');
213       std::cerr << cp;
214
215       /* run the test case function */
216       _xbt_test_current_unit = &unit;
217       if (unit.enabled_)
218         unit.func_();
219
220       /* iterate through all performed tests to determine status */
221       for (s_xbt_test_test const& test : unit.tests_) {
222         if (test.ignored_) {
223           unit.test_ignore_++;
224         } else {
225           unit.nb_tests_++;
226
227           if ((test.failed_ && not test.expected_failure_) || (not test.failed_ && test.expected_failure_))
228             unit.test_failed_++;
229           if (test.expected_failure_)
230             unit.test_expect_++;
231         }
232       }
233       /* Display whether this unit went well */
234       if (unit.test_failed_ > 0 || unit.test_expect_ || (verbosity && unit.nb_tests_ > 0)) {
235         /* some tests failed (or were supposed to), so do detailed reporting of test case */
236         if (unit.test_failed_ > 0) {
237           std::cerr << ".. failed\n";
238         } else if (unit.nb_tests_) {
239           std::cerr << "...... ok\n"; /* successful, but show about expected */
240         } else {
241           std::cerr << ".... skip\n"; /* shouldn't happen, but I'm a bit lost with this logic */
242         }
243         for (s_xbt_test_test const& test : unit.tests_) {
244           std::string file = test.file_;
245           int line         = test.line_;
246           std::string resname;
247           if (test.ignored_)
248             resname = " SKIP";
249           else if (test.expected_failure_) {
250             if (test.failed_)
251               resname = "EFAIL";
252             else
253               resname = "EPASS";
254           } else {
255             if (test.failed_)
256               resname = " FAIL";
257             else
258               resname = " PASS";
259           }
260           std::cerr << "      " << resname << ": " << test.title_ << " [" << file << ":" << line << "]\n";
261
262           if ((test.expected_failure_ && not test.failed_) || (not test.expected_failure_ && test.failed_)) {
263             for (s_xbt_test_log const& log : test.logs_) {
264               file = (log.file_.empty() ? file : log.file_);
265               line = (log.line_ == 0 ? line : log.line_);
266               std::cerr << "             " << file << ":" << line << ": " << log.text_ << "\n";
267             }
268           }
269         }
270         std::cerr << "    Summary: " << unit.test_failed_ << " of " << unit.nb_tests_ << " tests failed";
271         if (unit.test_ignore_) {
272           std::cerr << " (" << unit.test_ignore_ << " tests ignored)\n";
273         } else {
274           std::cerr << "\n";
275         }
276       } else if (not unit.enabled_) {
277         std::cerr << " disabled\n"; /* no test were run */
278       } else if (unit.nb_tests_) {
279         std::cerr << "...... ok\n"; /* successful */
280       } else {
281         std::cerr << ".... skip\n"; /* no test were run */
282       }
283
284       /* Accumulate test counts into the suite */
285       this->nb_tests_ += unit.nb_tests_;
286       this->test_failed_ += unit.test_failed_;
287       this->test_ignore_ += unit.test_ignore_;
288       this->test_expect_ += unit.test_expect_;
289
290       _xbt_test_nb_tests += unit.nb_tests_;
291       _xbt_test_test_failed += unit.test_failed_;
292       _xbt_test_test_ignore += unit.test_ignore_;
293       _xbt_test_test_expect += unit.test_expect_;
294
295       /* What's the conclusion of this test anyway? */
296       if (unit.nb_tests_) {
297         this->nb_units_++;
298         if (unit.test_failed_)
299           this->unit_failed_++;
300       } else if (not unit.enabled_) {
301         this->unit_disabled_++;
302       } else {
303         this->unit_ignore_++;
304       }
305     }
306   }
307   _xbt_test_nb_units += this->nb_units_;
308   _xbt_test_unit_failed += this->unit_failed_;
309   _xbt_test_unit_ignore += this->unit_ignore_;
310   _xbt_test_unit_disabled += this->unit_disabled_;
311
312   if (this->nb_units_) {
313     _xbt_test_nb_suites++;
314     if (this->test_failed_)
315       _xbt_test_suite_failed++;
316   } else if (not this->enabled_) {
317     _xbt_test_suite_disabled++;
318   } else {
319     _xbt_test_suite_ignore++;
320   }
321
322   /* print test suite summary */
323   if (this->enabled_) {
324     bool first = true; /* for result pretty printing */
325
326     std::cerr << " ====================================================================="
327               << (this->nb_units_ ? (this->unit_failed_ ? "== FAILED" : "====== OK")
328                                   : (this->unit_disabled_ ? " DISABLED" : "==== SKIP"))
329               << "\n";
330     std::cerr.setf(std::ios::fixed);
331     std::cerr.precision(0);
332     std::cerr << " Summary: Units: "
333               << (this->nb_units_ ? ((1 - (double)this->unit_failed_ / (double)this->nb_units_) * 100.0) : 100.0)
334               << "% ok (" << this->nb_units_ << " units: ";
335     if (this->nb_units_ != this->unit_failed_) {
336       std::cerr << (first ? "" : ", ") << (this->nb_units_ - this->unit_failed_) << " ok";
337       first = false;
338     }
339     if (this->unit_failed_) {
340       std::cerr << (first ? "" : ", ") << this->unit_failed_ << " failed";
341       first = false;
342     }
343     if (this->unit_ignore_) {
344       std::cerr << (first ? "" : ", ") << this->unit_ignore_ << " ignored";
345       first = false;
346     }
347     if (this->unit_disabled_) {
348       std::cerr << (first ? "" : ", ") << this->unit_disabled_ << " disabled";
349     }
350     std::cerr << ")\n          Tests: "
351               << (this->nb_tests_ ? ((1 - (double)this->test_failed_ / (double)this->nb_tests_) * 100.0) : 100.0)
352               << "% ok (" << this->nb_tests_ << " tests: ";
353     first = true;
354     if (this->nb_tests_ != this->test_failed_) {
355       std::cerr << (first ? "" : ", ") << (this->nb_tests_ - this->test_failed_) << " ok";
356       first = false;
357     }
358     if (this->test_failed_) {
359       std::cerr << (first ? "" : ", ") << this->test_failed_ << " failed";
360       first = false;
361     }
362     if (this->test_ignore_) {
363       std::cerr << (first ? "" : "; ") << this->test_ignore_ << " ignored";
364       first = false;
365     }
366     if (this->test_expect_) {
367       std::cerr << (first ? "" : "; ") << this->test_expect_ << " expected to fail";
368     }
369     std::cerr << ")\n";
370   }
371   return this->unit_failed_;
372 }
373
374 static void apply_selection(char *selection)
375 {
376   if (not selection || selection[0] == '\0')
377     return;
378
379   /* for the parsing */
380   std::string sel = selection;
381   bool done       = false;
382   std::string dir; /* the directive */
383   std::string suitename;
384   std::string unitname;
385
386   /* First apply the selection */
387   size_t p0 = 0;
388   while (not done) {
389     bool enabling = true;
390
391     size_t p = sel.find(',', p0);
392     if (p != std::string::npos) {
393       dir = sel.substr(p0, p - p0);
394       p0  = p + 1;
395     } else {
396       dir  = sel.substr(p0);
397       done = true;
398     }
399
400     if (dir[0] == '-') {
401       enabling = false;
402       dir.erase(0, 1);
403     }
404     if (dir[0] == '+') {
405       enabling = true;
406       dir.erase(0, 1);
407     }
408
409     p = dir.find(':');
410     if (p != std::string::npos) {
411       suitename = dir.substr(0, p);
412       unitname  = dir.substr(p + 1);
413     } else {
414       suitename = dir;
415       unitname  = "";
416     }
417
418     /* Deal with the specific case of 'all' pseudo serie */
419     if (suitename == "all") {
420       xbt_assert(unitname.empty(), "The 'all' pseudo-suite does not accept any unit specification\n");
421
422       for (xbt_test_suite_t& suite : _xbt_test_suites) {
423         for (s_xbt_test_unit& unit : suite->units_) {
424           unit.enabled_ = enabling;
425         }
426         suite->enabled_ = enabling;
427       }
428     } else {
429       bool suitefound = false;
430       for (xbt_test_suite_t& thissuite : _xbt_test_suites) {
431         if (suitename == thissuite->name_) {
432           /* Do not disable the whole suite when we just want to disable a child */
433           if (enabling || unitname.empty())
434             thissuite->enabled_ = enabling;
435
436           if (unitname.empty()) {
437             for (s_xbt_test_unit& unit : thissuite->units_) {
438               unit.enabled_ = enabling;
439             }
440           } else {              /* act on one child only */
441             /* search relevant unit */
442             auto unit = std::find_if(begin(thissuite->units_), end(thissuite->units_),
443                                      [&unitname](s_xbt_test_unit const& unit) { return unit.name_ == unitname; });
444             if (unit == end(thissuite->units_))
445               xbt_die("Suite '%s' has no unit of name '%s'. Cannot apply the selection\n", suitename.c_str(),
446                       unitname.c_str());
447             unit->enabled_ = enabling;
448           }                     /* act on childs (either all or one) */
449           suitefound = true;
450           break;                /* found the relevant serie. We are happy */
451         }
452       }                         /* search relevant series */
453       xbt_assert(suitefound, "No suite of name '%s' found. Cannot apply the selection\n", suitename.c_str());
454     }
455   }
456 }
457
458 void xbt_test_dump(char *selection)
459 {
460   apply_selection(selection);
461
462   if (not _xbt_test_suites.empty()) {
463     for (xbt_test_suite_t suite : _xbt_test_suites)
464       suite->dump();
465   } else {
466     std::cerr << " No suite defined.";
467   }
468 }
469
470 int xbt_test_run(char *selection, int verbosity)
471 {
472   apply_selection(selection);
473
474   if (not _xbt_test_suites.empty()) {
475     bool first = true;
476
477     /* Run all the suites */
478     for (xbt_test_suite_t& suite : _xbt_test_suites)
479       if (suite)
480         suite->run(verbosity);
481
482     /* Display some more statistics */
483     std::cerr.setf(std::ios::fixed);
484     std::cerr.precision(0);
485     std::cerr << "\n\n TOTAL: Suites: "
486               << (_xbt_test_nb_suites ? ((1 - (double)_xbt_test_suite_failed / (double)_xbt_test_nb_suites) * 100.0)
487                                       : 100.0)
488               << "% ok (" << _xbt_test_nb_suites << " suites: ";
489     if (_xbt_test_nb_suites != _xbt_test_suite_failed) {
490       std::cerr << (_xbt_test_nb_suites - _xbt_test_suite_failed) << " ok";
491       first = false;
492     }
493     if (_xbt_test_suite_failed) {
494       std::cerr << (first ? "" : ", ") << _xbt_test_suite_failed << " failed";
495       first = false;
496     }
497
498     if (_xbt_test_suite_ignore) {
499       std::cerr << (first ? "" : ", ") << _xbt_test_suite_ignore << " ignored";
500     }
501     std::cerr << ")\n        Units:  "
502               << (_xbt_test_nb_units ? ((1 - (double)_xbt_test_unit_failed / (double)_xbt_test_nb_units) * 100.0)
503                                      : 100.0)
504               << "% ok (" << _xbt_test_nb_units << " units: ";
505     first = true;
506     if (_xbt_test_nb_units != _xbt_test_unit_failed) {
507       std::cerr << (_xbt_test_nb_units - _xbt_test_unit_failed) << " ok";
508       first = false;
509     }
510     if (_xbt_test_unit_failed) {
511       std::cerr << (first ? "" : ", ") << _xbt_test_unit_failed << " failed";
512       first = false;
513     }
514     if (_xbt_test_unit_ignore) {
515       std::cerr << (first ? "" : ", ") << _xbt_test_unit_ignore << " ignored";
516     }
517     std::cerr << ")\n        Tests:  "
518               << (_xbt_test_nb_tests ? ((1 - (double)_xbt_test_test_failed / (double)_xbt_test_nb_tests) * 100.0)
519                                      : 100.0)
520               << "% ok (" << _xbt_test_nb_tests << " tests: ";
521     first = true;
522     if (_xbt_test_nb_tests != _xbt_test_test_failed) {
523       std::cerr << (_xbt_test_nb_tests - _xbt_test_test_failed) << " ok";
524       first = false;
525     }
526     if (_xbt_test_test_failed) {
527       std::cerr << (first ? "" : ", ") << _xbt_test_test_failed << " failed";
528       first = false;
529     }
530     if (_xbt_test_test_ignore) {
531       std::cerr << (first ? "" : ", ") << _xbt_test_test_ignore << " ignored";
532       first = false;
533     }
534     if (_xbt_test_test_expect) {
535       std::cerr << (first ? "" : ", ") << _xbt_test_test_expect << " expected to fail";
536     }
537
538     std::cerr << ")\n";
539   } else {
540     std::cerr << "No unit to run!\n";
541     _xbt_test_unit_failed++;
542   }
543   return _xbt_test_unit_failed;
544 }
545
546 void xbt_test_exit()
547 {
548   for (xbt_test_suite_t suite : _xbt_test_suites)
549     delete suite;
550   _xbt_test_suites.clear();
551 }
552
553 /* annotate test case with test */
554 void _xbt_test_add(const char *file, int line, const char *fmt, ...)
555 {
556   xbt_test_unit_t unit = _xbt_test_current_unit;
557   xbt_assert(unit);
558
559   va_list ap;
560   va_start(ap, fmt);
561   unit->tests_.emplace_back(simgrid::xbt::string_vprintf(fmt, ap), file, line);
562   va_end(ap);
563 }
564
565 /* annotate test case with log message and failure */
566 void _xbt_test_fail(const char *file, int line, const char *fmt, ...)
567 {
568   xbt_test_unit_t unit = _xbt_test_current_unit;
569   xbt_assert(unit);
570   xbt_assert(not _xbt_test_current_unit->tests_.empty(), "Test failed even before being declared (broken unit: %s)",
571              unit->title_.c_str());
572
573   s_xbt_test_test& test = unit->tests_.back();
574   va_list ap;
575   va_start(ap, fmt);
576   test.logs_.emplace_back(simgrid::xbt::string_vprintf(fmt, ap), file, line);
577   va_end(ap);
578
579   test.failed_ = true;
580 }
581
582 void xbt_test_exception(xbt_ex_t e)
583 {
584   _xbt_test_fail(e.throwPoint().file, e.throwPoint().line, "Exception %s raised: %s", xbt_ex_catname(e.category), e.what());
585 }
586
587 void xbt_test_expect_failure()
588 {
589   xbt_assert(not _xbt_test_current_unit->tests_.empty(),
590              "Cannot expect the failure of a test before declaring it (broken unit: %s)",
591              _xbt_test_current_unit->title_.c_str());
592   _xbt_test_current_unit->tests_.back().expected_failure_ = true;
593 }
594
595 void xbt_test_skip()
596 {
597   xbt_assert(not _xbt_test_current_unit->tests_.empty(), "Test skipped even before being declared (broken unit: %s)",
598              _xbt_test_current_unit->title_.c_str());
599   _xbt_test_current_unit->tests_.back().ignored_ = true;
600 }
601
602 /* annotate test case with log message only */
603 void _xbt_test_log(const char *file, int line, const char *fmt, ...)
604 {
605   xbt_test_unit_t unit = _xbt_test_current_unit;
606   xbt_assert(unit);
607   xbt_assert(not _xbt_test_current_unit->tests_.empty(),
608              "Test logged into even before being declared (broken test unit: %s)", unit->title_.c_str());
609
610   va_list ap;
611   va_start(ap, fmt);
612   unit->tests_.back().logs_.emplace_back(simgrid::xbt::string_vprintf(fmt, ap), file, line);
613   va_end(ap);
614 }
615
616 #ifdef SIMGRID_TEST
617 XBT_TEST_SUITE("cunit", "Testsuite mechanism autotest");
618
619 XBT_TEST_UNIT("expect", test_expected_failure, "expected failures")
620 {
621   xbt_test_add("Skipped test");
622   xbt_test_skip();
623
624   xbt_test_add("%s %s", "EXPECTED", "FAILURE");
625   xbt_test_expect_failure();
626   xbt_test_log("%s %s", "Test", "log");
627   xbt_test_fail("EXPECTED FAILURE");
628 }
629 #endif                          /* SIMGRID_TEST */