broken.cpp 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. // [Broken]
  2. // Lightweight Unit Testing for C++.
  3. //
  4. // [License]
  5. // Public Domain (Unlicense)
  6. #include "./broken.h"
  7. #include <stdarg.h>
  8. // ============================================================================
  9. // [Broken - Global]
  10. // ============================================================================
  11. // Zero initialized globals.
  12. struct BrokenGlobal {
  13. // Application arguments.
  14. int _argc;
  15. const char** _argv;
  16. // Output file.
  17. FILE* _file;
  18. // Unit tests.
  19. BrokenAPI::Unit* _unitList;
  20. BrokenAPI::Unit* _unitRunning;
  21. bool hasArg(const char* a) const noexcept {
  22. for (int i = 1; i < _argc; i++)
  23. if (strcmp(_argv[i], a) == 0)
  24. return true;
  25. return false;
  26. }
  27. inline FILE* file() const noexcept { return _file ? _file : stdout; }
  28. };
  29. static BrokenGlobal _brokenGlobal;
  30. // ============================================================================
  31. // [Broken - API]
  32. // ============================================================================
  33. // Get whether the string `a` starts with string `b`.
  34. static bool BrokenAPI_startsWith(const char* a, const char* b) noexcept {
  35. for (size_t i = 0; ; i++) {
  36. if (b[i] == '\0') return true;
  37. if (a[i] != b[i]) return false;
  38. }
  39. }
  40. //! Compares names and priority of two unit tests.
  41. static int BrokenAPI_compareUnits(const BrokenAPI::Unit* a, const BrokenAPI::Unit* b) noexcept {
  42. if (a->priority == b->priority)
  43. return strcmp(a->name, b->name);
  44. else
  45. return a->priority > b->priority ? 1 : -1;
  46. }
  47. // Get whether the strings `a` and `b` are equal, ignoring case and treating
  48. // `-` as `_`.
  49. static bool BrokenAPI_matchesFilter(const char* a, const char* b) noexcept {
  50. for (size_t i = 0; ; i++) {
  51. int ca = (unsigned char)a[i];
  52. int cb = (unsigned char)b[i];
  53. // If filter is defined as wildcard the rest automatically matches.
  54. if (cb == '*')
  55. return true;
  56. if (ca == '-') ca = '_';
  57. if (cb == '-') cb = '_';
  58. if (ca >= 'A' && ca <= 'Z') ca += 'a' - 'A';
  59. if (cb >= 'A' && cb <= 'Z') cb += 'a' - 'A';
  60. if (ca != cb)
  61. return false;
  62. if (ca == '\0')
  63. return true;
  64. }
  65. }
  66. static bool BrokenAPI_canRun(BrokenAPI::Unit* unit) noexcept {
  67. BrokenGlobal& global = _brokenGlobal;
  68. int i, argc = global._argc;
  69. const char** argv = global._argv;
  70. const char* unitName = unit->name;
  71. bool hasFilter = false;
  72. for (i = 1; i < argc; i++) {
  73. const char* arg = argv[i];
  74. if (BrokenAPI_startsWith(arg, "--run-") && strcmp(arg, "--run-all") != 0) {
  75. hasFilter = true;
  76. if (BrokenAPI_matchesFilter(unitName, arg + 6))
  77. return true;
  78. }
  79. }
  80. // If no filter has been specified the default is to run.
  81. return !hasFilter;
  82. }
  83. static void BrokenAPI_runUnit(BrokenAPI::Unit* unit) noexcept {
  84. BrokenAPI::info("Running %s", unit->name);
  85. _brokenGlobal._unitRunning = unit;
  86. unit->entry();
  87. _brokenGlobal._unitRunning = NULL;
  88. }
  89. static void BrokenAPI_runAll() noexcept {
  90. BrokenAPI::Unit* unit = _brokenGlobal._unitList;
  91. bool hasUnits = unit != NULL;
  92. size_t count = 0;
  93. int currentPriority = 0;
  94. while (unit != NULL) {
  95. if (BrokenAPI_canRun(unit)) {
  96. if (currentPriority != unit->priority) {
  97. if (count)
  98. INFO("");
  99. INFO("[[Priority=%d]]", unit->priority);
  100. }
  101. currentPriority = unit->priority;
  102. BrokenAPI_runUnit(unit);
  103. count++;
  104. }
  105. unit = unit->next;
  106. }
  107. if (count) {
  108. INFO("\nSuccess:");
  109. INFO(" All tests passed!");
  110. }
  111. else {
  112. INFO("\nWarning:");
  113. INFO(" No units %s!", hasUnits ? "matched the filter" : "defined");
  114. }
  115. }
  116. static void BrokenAPI_listAll() noexcept {
  117. BrokenAPI::Unit* unit = _brokenGlobal._unitList;
  118. if (unit != NULL) {
  119. INFO("Units:");
  120. do {
  121. INFO(" %s [priority=%d]", unit->name, unit->priority);
  122. unit = unit->next;
  123. } while (unit != NULL);
  124. }
  125. else {
  126. INFO("Warning:");
  127. INFO(" No units defined!");
  128. }
  129. }
  130. bool BrokenAPI::hasArg(const char* name) noexcept {
  131. return _brokenGlobal.hasArg(name);
  132. }
  133. void BrokenAPI::add(Unit* unit) noexcept {
  134. Unit** pPrev = &_brokenGlobal._unitList;
  135. Unit* current = *pPrev;
  136. // C++ static initialization doesn't guarantee anything. We sort all units by
  137. // name so the execution will always happen in deterministic order.
  138. while (current != NULL) {
  139. if (BrokenAPI_compareUnits(current, unit) >= 0)
  140. break;
  141. pPrev = &current->next;
  142. current = *pPrev;
  143. }
  144. *pPrev = unit;
  145. unit->next = current;
  146. }
  147. void BrokenAPI::setOutputFile(FILE* file) noexcept {
  148. BrokenGlobal& global = _brokenGlobal;
  149. global._file = file;
  150. }
  151. int BrokenAPI::run(int argc, const char* argv[], Entry onBeforeRun, Entry onAfterRun) {
  152. BrokenGlobal& global = _brokenGlobal;
  153. global._argc = argc;
  154. global._argv = argv;
  155. if (global.hasArg("--help")) {
  156. INFO("Options:");
  157. INFO(" --help - print this usage");
  158. INFO(" --list - list all tests");
  159. INFO(" --run-... - run a test(s), trailing wildcards supported");
  160. INFO(" --run-all - run all tests (default)");
  161. return 0;
  162. }
  163. if (global.hasArg("--list")) {
  164. BrokenAPI_listAll();
  165. return 0;
  166. }
  167. if (onBeforeRun)
  168. onBeforeRun();
  169. // We don't care about filters here, it's implemented by `runAll`.
  170. BrokenAPI_runAll();
  171. if (onAfterRun)
  172. onAfterRun();
  173. return 0;
  174. }
  175. static void BrokenAPI_printMessage(const char* prefix, const char* fmt, va_list ap) noexcept {
  176. BrokenGlobal& global = _brokenGlobal;
  177. FILE* dst = global.file();
  178. if (!fmt || fmt[0] == '\0') {
  179. fprintf(dst, "\n");
  180. }
  181. else {
  182. // This looks scary, but we really want to use only a single call to vfprintf()
  183. // in multithreaded code. So we change the format a bit if necessary.
  184. enum : unsigned { kBufferSize = 512 };
  185. char staticBuffer[512];
  186. size_t fmtSize = strlen(fmt);
  187. size_t prefixSize = strlen(prefix);
  188. char* fmtBuf = staticBuffer;
  189. if (fmtSize > kBufferSize - 2 - prefixSize)
  190. fmtBuf = static_cast<char*>(malloc(fmtSize + prefixSize + 2));
  191. if (!fmtBuf) {
  192. fprintf(dst, "%sCannot allocate buffer for vfprintf()\n", prefix);
  193. }
  194. else {
  195. memcpy(fmtBuf, prefix, prefixSize);
  196. memcpy(fmtBuf + prefixSize, fmt, fmtSize);
  197. fmtSize += prefixSize;
  198. if (fmtBuf[fmtSize - 1] != '\n')
  199. fmtBuf[fmtSize++] = '\n';
  200. fmtBuf[fmtSize] = '\0';
  201. vfprintf(dst, fmtBuf, ap);
  202. if (fmtBuf != staticBuffer)
  203. free(fmtBuf);
  204. }
  205. }
  206. fflush(dst);
  207. }
  208. void BrokenAPI::info(const char* fmt, ...) noexcept {
  209. BrokenGlobal& global = _brokenGlobal;
  210. va_list ap;
  211. va_start(ap, fmt);
  212. BrokenAPI_printMessage(global._unitRunning ? " " : "", fmt, ap);
  213. va_end(ap);
  214. }
  215. void BrokenAPI::fail(const char* file, int line, const char* expression, const char* fmt, ...) noexcept {
  216. BrokenGlobal& global = _brokenGlobal;
  217. FILE* dst = global.file();
  218. fprintf(dst, " FAILED: %s\n", expression);
  219. if (fmt) {
  220. va_list ap;
  221. va_start(ap, fmt);
  222. BrokenAPI_printMessage(" REASON: ", fmt, ap);
  223. va_end(ap);
  224. }
  225. fprintf(dst, " SOURCE: %s (Line: %d)\n", file, line);
  226. fflush(dst);
  227. exit(1);
  228. }