123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291 |
- // [Broken]
- // Lightweight Unit Testing for C++.
- //
- // [License]
- // Public Domain (Unlicense)
- #include "./broken.h"
- #include <stdarg.h>
- // ============================================================================
- // [Broken - Global]
- // ============================================================================
- // Zero initialized globals.
- struct BrokenGlobal {
- // Application arguments.
- int _argc;
- const char** _argv;
- // Output file.
- FILE* _file;
- // Unit tests.
- BrokenAPI::Unit* _unitList;
- BrokenAPI::Unit* _unitRunning;
- bool hasArg(const char* a) const noexcept {
- for (int i = 1; i < _argc; i++)
- if (strcmp(_argv[i], a) == 0)
- return true;
- return false;
- }
- inline FILE* file() const noexcept { return _file ? _file : stdout; }
- };
- static BrokenGlobal _brokenGlobal;
- // ============================================================================
- // [Broken - API]
- // ============================================================================
- // Get whether the string `a` starts with string `b`.
- static bool BrokenAPI_startsWith(const char* a, const char* b) noexcept {
- for (size_t i = 0; ; i++) {
- if (b[i] == '\0') return true;
- if (a[i] != b[i]) return false;
- }
- }
- //! Compares names and priority of two unit tests.
- static int BrokenAPI_compareUnits(const BrokenAPI::Unit* a, const BrokenAPI::Unit* b) noexcept {
- if (a->priority == b->priority)
- return strcmp(a->name, b->name);
- else
- return a->priority > b->priority ? 1 : -1;
- }
- // Get whether the strings `a` and `b` are equal, ignoring case and treating
- // `-` as `_`.
- static bool BrokenAPI_matchesFilter(const char* a, const char* b) noexcept {
- for (size_t i = 0; ; i++) {
- int ca = (unsigned char)a[i];
- int cb = (unsigned char)b[i];
- // If filter is defined as wildcard the rest automatically matches.
- if (cb == '*')
- return true;
- if (ca == '-') ca = '_';
- if (cb == '-') cb = '_';
- if (ca >= 'A' && ca <= 'Z') ca += 'a' - 'A';
- if (cb >= 'A' && cb <= 'Z') cb += 'a' - 'A';
- if (ca != cb)
- return false;
- if (ca == '\0')
- return true;
- }
- }
- static bool BrokenAPI_canRun(BrokenAPI::Unit* unit) noexcept {
- BrokenGlobal& global = _brokenGlobal;
- int i, argc = global._argc;
- const char** argv = global._argv;
- const char* unitName = unit->name;
- bool hasFilter = false;
- for (i = 1; i < argc; i++) {
- const char* arg = argv[i];
- if (BrokenAPI_startsWith(arg, "--run-") && strcmp(arg, "--run-all") != 0) {
- hasFilter = true;
- if (BrokenAPI_matchesFilter(unitName, arg + 6))
- return true;
- }
- }
- // If no filter has been specified the default is to run.
- return !hasFilter;
- }
- static void BrokenAPI_runUnit(BrokenAPI::Unit* unit) noexcept {
- BrokenAPI::info("Running %s", unit->name);
- _brokenGlobal._unitRunning = unit;
- unit->entry();
- _brokenGlobal._unitRunning = NULL;
- }
- static void BrokenAPI_runAll() noexcept {
- BrokenAPI::Unit* unit = _brokenGlobal._unitList;
- bool hasUnits = unit != NULL;
- size_t count = 0;
- int currentPriority = 0;
- while (unit != NULL) {
- if (BrokenAPI_canRun(unit)) {
- if (currentPriority != unit->priority) {
- if (count)
- INFO("");
- INFO("[[Priority=%d]]", unit->priority);
- }
- currentPriority = unit->priority;
- BrokenAPI_runUnit(unit);
- count++;
- }
- unit = unit->next;
- }
- if (count) {
- INFO("\nSuccess:");
- INFO(" All tests passed!");
- }
- else {
- INFO("\nWarning:");
- INFO(" No units %s!", hasUnits ? "matched the filter" : "defined");
- }
- }
- static void BrokenAPI_listAll() noexcept {
- BrokenAPI::Unit* unit = _brokenGlobal._unitList;
- if (unit != NULL) {
- INFO("Units:");
- do {
- INFO(" %s [priority=%d]", unit->name, unit->priority);
- unit = unit->next;
- } while (unit != NULL);
- }
- else {
- INFO("Warning:");
- INFO(" No units defined!");
- }
- }
- bool BrokenAPI::hasArg(const char* name) noexcept {
- return _brokenGlobal.hasArg(name);
- }
- void BrokenAPI::add(Unit* unit) noexcept {
- Unit** pPrev = &_brokenGlobal._unitList;
- Unit* current = *pPrev;
- // C++ static initialization doesn't guarantee anything. We sort all units by
- // name so the execution will always happen in deterministic order.
- while (current != NULL) {
- if (BrokenAPI_compareUnits(current, unit) >= 0)
- break;
- pPrev = ¤t->next;
- current = *pPrev;
- }
- *pPrev = unit;
- unit->next = current;
- }
- void BrokenAPI::setOutputFile(FILE* file) noexcept {
- BrokenGlobal& global = _brokenGlobal;
- global._file = file;
- }
- int BrokenAPI::run(int argc, const char* argv[], Entry onBeforeRun, Entry onAfterRun) {
- BrokenGlobal& global = _brokenGlobal;
- global._argc = argc;
- global._argv = argv;
- if (global.hasArg("--help")) {
- INFO("Options:");
- INFO(" --help - print this usage");
- INFO(" --list - list all tests");
- INFO(" --run-... - run a test(s), trailing wildcards supported");
- INFO(" --run-all - run all tests (default)");
- return 0;
- }
- if (global.hasArg("--list")) {
- BrokenAPI_listAll();
- return 0;
- }
- if (onBeforeRun)
- onBeforeRun();
- // We don't care about filters here, it's implemented by `runAll`.
- BrokenAPI_runAll();
- if (onAfterRun)
- onAfterRun();
- return 0;
- }
- static void BrokenAPI_printMessage(const char* prefix, const char* fmt, va_list ap) noexcept {
- BrokenGlobal& global = _brokenGlobal;
- FILE* dst = global.file();
- if (!fmt || fmt[0] == '\0') {
- fprintf(dst, "\n");
- }
- else {
- // This looks scary, but we really want to use only a single call to vfprintf()
- // in multithreaded code. So we change the format a bit if necessary.
- enum : unsigned { kBufferSize = 512 };
- char staticBuffer[512];
- size_t fmtSize = strlen(fmt);
- size_t prefixSize = strlen(prefix);
- char* fmtBuf = staticBuffer;
- if (fmtSize > kBufferSize - 2 - prefixSize)
- fmtBuf = static_cast<char*>(malloc(fmtSize + prefixSize + 2));
- if (!fmtBuf) {
- fprintf(dst, "%sCannot allocate buffer for vfprintf()\n", prefix);
- }
- else {
- memcpy(fmtBuf, prefix, prefixSize);
- memcpy(fmtBuf + prefixSize, fmt, fmtSize);
- fmtSize += prefixSize;
- if (fmtBuf[fmtSize - 1] != '\n')
- fmtBuf[fmtSize++] = '\n';
- fmtBuf[fmtSize] = '\0';
- vfprintf(dst, fmtBuf, ap);
- if (fmtBuf != staticBuffer)
- free(fmtBuf);
- }
- }
- fflush(dst);
- }
- void BrokenAPI::info(const char* fmt, ...) noexcept {
- BrokenGlobal& global = _brokenGlobal;
- va_list ap;
- va_start(ap, fmt);
- BrokenAPI_printMessage(global._unitRunning ? " " : "", fmt, ap);
- va_end(ap);
- }
- void BrokenAPI::fail(const char* file, int line, const char* expression, const char* fmt, ...) noexcept {
- BrokenGlobal& global = _brokenGlobal;
- FILE* dst = global.file();
- fprintf(dst, " FAILED: %s\n", expression);
- if (fmt) {
- va_list ap;
- va_start(ap, fmt);
- BrokenAPI_printMessage(" REASON: ", fmt, ap);
- va_end(ap);
- }
- fprintf(dst, " SOURCE: %s (Line: %d)\n", file, line);
- fflush(dst);
- exit(1);
- }
|