dessert.hpp 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. // Lightweight unit-testing C++11 framework. zlib/libpng licensed.
  2. // - rlyeh ~~ listening to Led Zeppelin / No Quarter
  3. #ifndef DESSERT_VERSION
  4. #define DESSERT_VERSION "1.0.2" // Public API following
  5. #define dessert(...) ( !!(__VA_ARGS__) ? \
  6. ( dessert::suite(#__VA_ARGS__,__FILE__,__LINE__,1) < __VA_ARGS__ ) : \
  7. ( dessert::suite(#__VA_ARGS__,__FILE__,__LINE__,0) < __VA_ARGS__ ) )
  8. #define desserts(...) \
  9. static void dessert$line(dessert)(); \
  10. static const bool dessert$line(dsstSuite_) = dessert::suite::queue( [](){ \
  11. std::string title = "" __VA_ARGS__; if( title.empty() ) title = "Suite"; \
  12. fprintf( stderr, "------ %s\n", title.c_str() ); \
  13. dessert$line(dessert)(); \
  14. }, "" #__VA_ARGS__ ); \
  15. void dessert$line(dessert)()
  16. #define throws(...) ( [&](){ try { __VA_ARGS__; } catch( ... ) { return true; } return false; }() )
  17. /* API details following */
  18. #include <cassert>
  19. #include <cstdio>
  20. #include <cstdlib>
  21. #include <chrono>
  22. #include <deque>
  23. #include <functional>
  24. #include <string>
  25. #include <sstream>
  26. namespace dessert {
  27. using namespace std;
  28. using timer = chrono::high_resolution_clock;
  29. template<typename T> inline string to_str( const T &t ) { stringstream ss; return (ss << t) ? ss.str() : string("??"); }
  30. template< > inline string to_str( const timer::time_point &start ) {
  31. return to_str( double((timer::now() - start).count()) * timer::period::num / timer::period::den );
  32. }
  33. class suite {
  34. timer::time_point start = timer::now();
  35. deque< string > xpr;
  36. int ok = false, has_bp = false;
  37. enum { BREAKPOINT, BREAKPOINTS, PASSED, FAILED, TESTNO };
  38. static unsigned &get(int i) { static unsigned var[TESTNO+1] = {}; return var[i]; }
  39. public:
  40. static bool queue( const function<void()> &fn, const string &text ) {
  41. static auto start = timer::now();
  42. static struct install : public deque<function<void()>> {
  43. install() : deque<function<void()>>() {
  44. get(BREAKPOINT) = stoul( getenv("BREAKON") ? getenv("BREAKON") : "0" );
  45. }
  46. ~install() {
  47. for( auto &fn : *this ) fn();
  48. string ss, run = to_str( get(PASSED)+get(FAILED) ), res = get(FAILED) ? "[FAIL] " : "[ OK ] ";
  49. if( get(FAILED) ) ss += res + "Failure! " + to_str(get(FAILED)) + '/'+ run + " tests failed :(\n";
  50. else ss += res + "Success: " + run + " tests passed :)\n";
  51. ss += " Breakpoints: " + to_str( get(BREAKPOINTS) ) + " (*)\n";
  52. ss += " Total time: " + to_str(start) + " seconds.\n";
  53. fprintf( stderr, "\n%s", ss.c_str() );
  54. if( get(FAILED) ) std::exit( get(FAILED) );
  55. }
  56. } queue;
  57. return text.find("before main()") == string::npos ? ( queue.push_back( fn ), 0 ) : ( fn(), 1 );
  58. }
  59. suite( const char *const text, const char *const file, int line, bool result )
  60. : xpr( {string(file) + ':' + to_str(line), " - ", text, "" } ), ok(result) {
  61. xpr[0] = "Test " + to_str(++get(TESTNO)) + " at " + xpr[0];
  62. if( 0 != ( has_bp = ( get(TESTNO) == get(BREAKPOINT) )) ) {
  63. get(BREAKPOINTS)++;
  64. fprintf( stderr, "<dessert/dessert.hpp> says: breaking on test #%d\n\t", get(TESTNO) );
  65. assert(! "<dessert/dessert.hpp> says: breakpoint requested" );
  66. fprintf( stderr, "%s", "\n<dessert/dessert.hpp> says: breakpoint failed!\n" );
  67. }
  68. }
  69. ~suite() {
  70. if( xpr.empty() ) return;
  71. operator bool(), queue( [&](){ get(ok ? PASSED : FAILED)++; }, "before main()" );
  72. string res[] = { "[FAIL]", "[ OK ]", " ", " *", " ", "" }, *bp = &res[2], *tab = &res[4];
  73. xpr[0] = res[ok] + bp[has_bp] + xpr[0] + " (" + to_str(start) + " s)" + (xpr[1].size() > 3 && !ok ? xpr[1] : tab[1]);
  74. xpr.erase( xpr.begin() + 1 );
  75. if( ok ) xpr = { xpr[0] }; else {
  76. xpr[1].resize( (xpr[1] != (xpr[2] = xpr[2].substr( xpr[2][2] == ' ' ? 3 : 4 ))) * xpr[1].size() );
  77. xpr.push_back( "(unexpected)" );
  78. }
  79. for( auto end = xpr.size(), it = end - end; it < end; ++it ) {
  80. fprintf( stderr, &"\0%s%s\n"[ !xpr[it].empty() ], tab[ !it ].c_str(), xpr[ it ].c_str() );
  81. } }
  82. # define dessert$join(str, num) str##num
  83. # define dessert$glue(str, num) dessert$join(str, num)
  84. # define dessert$line(str) dessert$glue(str, __LINE__)
  85. # define dessert$(OP) \
  86. template<typename T> suite &operator OP( const T &rhs ) { return xpr[3] += " "#OP" " + to_str(rhs), *this; } \
  87. template<unsigned N> suite &operator OP( const char (&rhs)[N] ) { return xpr[3] += " "#OP" " + to_str(rhs), *this; }
  88. template<typename T> suite &operator <<( const T &t ) { return xpr[1] += to_str(t), *this; }
  89. template<unsigned N> suite &operator <<( const char (&str)[N] ) { return xpr[1] += to_str(str), *this; }
  90. operator bool() {
  91. return xpr.size() >= 3 && xpr[3].size() >= 6 && [&]() -> bool {
  92. char signR = xpr[3].at(2);
  93. bool equal = xpr[3].substr( 4 + xpr[3].size()/2 ) == xpr[3].substr( 3, xpr[3].size()/2 - 3 );
  94. return (ok = ( signR == '=' ? equal : ( signR == '!' ? !equal : ok ) ));
  95. }(), ok;
  96. }
  97. dessert$(<) dessert$(<=) dessert$(>) dessert$(>=) dessert$(!=) dessert$(==) dessert$(&&) dessert$(||)
  98. };
  99. }
  100. #endif