odin-blend2d

Odin bindings to Blend2D
Log | Files | Refs | README | LICENSE

broken.cpp (8140B)


      1 // Broken - Lightweight unit testing for C++
      2 //
      3 // This is free and unencumbered software released into the public domain.
      4 //
      5 // Anyone is free to copy, modify, publish, use, compile, sell, or
      6 // distribute this software, either in source code form or as a compiled
      7 // binary, for any purpose, commercial or non-commercial, and by any
      8 // means.
      9 //
     10 // In jurisdictions that recognize copyright laws, the author or authors
     11 // of this software dedicate any and all copyright interest in the
     12 // software to the public domain. We make this dedication for the benefit
     13 // of the public at large and to the detriment of our heirs and
     14 // successors. We intend this dedication to be an overt act of
     15 // relinquishment in perpetuity of all present and future rights to this
     16 // software under copyright law.
     17 //
     18 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
     19 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
     20 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
     21 // IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
     22 // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
     23 // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
     24 // OTHER DEALINGS IN THE SOFTWARE.
     25 //
     26 // For more information, please refer to <http://unlicense.org>
     27 
     28 #include "broken.h"
     29 #include <stdarg.h>
     30 
     31 // Broken - Globals
     32 // ================
     33 
     34 // Zero initialized globals.
     35 struct BrokenGlobal {
     36   // Application arguments.
     37   int _argc;
     38   const char** _argv;
     39 
     40   // Output file.
     41   FILE* _file;
     42 
     43   // Unit tests.
     44   BrokenAPI::Unit* _unit_list;
     45   BrokenAPI::Unit* _unit_running;
     46 
     47   bool has_arg(const char* a) const noexcept {
     48     for (int i = 1; i < _argc; i++)
     49       if (strcmp(_argv[i], a) == 0)
     50         return true;
     51     return false;
     52   }
     53 
     54   inline FILE* file() const noexcept { return _file ? _file : stdout; }
     55 };
     56 static BrokenGlobal _broken_global;
     57 
     58 // Broken - API
     59 // ============
     60 
     61 // Get whether the string `a` starts with string `b`.
     62 static bool BrokenAPI_startsWith(const char* a, const char* b) noexcept {
     63   for (size_t i = 0; ; i++) {
     64     if (b[i] == '\0') return true;
     65     if (a[i] != b[i]) return false;
     66   }
     67 }
     68 
     69 //! Compares names and priority of two unit tests.
     70 static int BrokenAPI_compareUnits(const BrokenAPI::Unit* a, const BrokenAPI::Unit* b) noexcept {
     71   if (a->priority == b->priority)
     72     return strcmp(a->name, b->name);
     73   else
     74     return a->priority > b->priority ? 1 : -1;
     75 }
     76 
     77 // Get whether the strings `a` and `b` are equal, ignoring case and treating
     78 // `-` as `_`.
     79 static bool BrokenAPI_matchesFilter(const char* a, const char* b) noexcept {
     80   for (size_t i = 0; ; i++) {
     81     int ca = (unsigned char)a[i];
     82     int cb = (unsigned char)b[i];
     83 
     84     // If filter is defined as wildcard the rest automatically matches.
     85     if (cb == '*')
     86       return true;
     87 
     88     if (ca == '-') ca = '_';
     89     if (cb == '-') cb = '_';
     90 
     91     if (ca >= 'A' && ca <= 'Z') ca += 'a' - 'A';
     92     if (cb >= 'A' && cb <= 'Z') cb += 'a' - 'A';
     93 
     94     if (ca != cb)
     95       return false;
     96 
     97     if (ca == '\0')
     98       return true;
     99   }
    100 }
    101 
    102 static bool BrokenAPI_canRun(BrokenAPI::Unit* unit) noexcept {
    103   BrokenGlobal& global = _broken_global;
    104 
    105   int i, argc = global._argc;
    106   const char** argv = global._argv;
    107 
    108   const char* unit_name = unit->name;
    109   bool has_filter = false;
    110 
    111   for (i = 1; i < argc; i++) {
    112     const char* arg = argv[i];
    113 
    114     if (BrokenAPI_startsWith(arg, "--run-") && strcmp(arg, "--run-all") != 0) {
    115       has_filter = true;
    116 
    117       if (BrokenAPI_matchesFilter(unit_name, arg + 6))
    118         return true;
    119     }
    120   }
    121 
    122   // If no filter has been specified the default is to run.
    123   return !has_filter;
    124 }
    125 
    126 static void BrokenAPI_runUnit(BrokenAPI::Unit* unit) noexcept {
    127   BrokenAPI::info("Running %s", unit->name);
    128 
    129   _broken_global._unit_running = unit;
    130   unit->entry();
    131   _broken_global._unit_running = NULL;
    132 }
    133 
    134 static void BrokenAPI_runAll() noexcept {
    135   BrokenAPI::Unit* unit = _broken_global._unit_list;
    136 
    137   bool has_units = unit != NULL;
    138   size_t count = 0;
    139   int current_priority = 0;
    140 
    141   while (unit != NULL) {
    142     if (BrokenAPI_canRun(unit)) {
    143       if (current_priority != unit->priority) {
    144         if (count)
    145           INFO("");
    146         INFO("[[Priority=%d]]", unit->priority);
    147       }
    148 
    149       current_priority = unit->priority;
    150       BrokenAPI_runUnit(unit);
    151       count++;
    152     }
    153     unit = unit->next;
    154   }
    155 
    156   if (count) {
    157     INFO("\nSuccess:");
    158     INFO("  All tests passed!");
    159   }
    160   else {
    161     INFO("\nWarning:");
    162     INFO("  No units %s!", has_units ? "matched the filter" : "defined");
    163   }
    164 }
    165 
    166 static void BrokenAPI_listAll() noexcept {
    167   BrokenAPI::Unit* unit = _broken_global._unit_list;
    168 
    169   if (unit != NULL) {
    170     INFO("Units:");
    171     do {
    172       INFO("  %s [priority=%d]", unit->name, unit->priority);
    173       unit = unit->next;
    174     } while (unit != NULL);
    175   }
    176   else {
    177     INFO("Warning:");
    178     INFO("  No units defined!");
    179   }
    180 }
    181 
    182 bool BrokenAPI::has_arg(const char* name) noexcept {
    183   return _broken_global.has_arg(name);
    184 }
    185 
    186 void BrokenAPI::add_unit(Unit* unit) noexcept {
    187   Unit** pPrev = &_broken_global._unit_list;
    188   Unit* current = *pPrev;
    189 
    190   // C++ static initialization doesn't guarantee anything. We sort all units by
    191   // name so the execution will always happen in deterministic order.
    192   while (current != NULL) {
    193     if (BrokenAPI_compareUnits(current, unit) >= 0)
    194       break;
    195 
    196     pPrev = &current->next;
    197     current = *pPrev;
    198   }
    199 
    200   *pPrev = unit;
    201   unit->next = current;
    202 }
    203 
    204 void BrokenAPI::set_output_file(FILE* file) noexcept {
    205   BrokenGlobal& global = _broken_global;
    206 
    207   global._file = file;
    208 }
    209 
    210 int BrokenAPI::run(int argc, const char* argv[], Entry on_before_run, Entry on_after_run) {
    211   BrokenGlobal& global = _broken_global;
    212 
    213   global._argc = argc;
    214   global._argv = argv;
    215 
    216   if (global.has_arg("--help")) {
    217     INFO("Options:");
    218     INFO("  --help    - print this usage");
    219     INFO("  --list    - list all tests");
    220     INFO("  --run-... - run a test(s), trailing wildcards supported");
    221     INFO("  --run-all - run all tests (default)");
    222     return 0;
    223   }
    224 
    225   if (global.has_arg("--list")) {
    226     BrokenAPI_listAll();
    227     return 0;
    228   }
    229 
    230   if (on_before_run)
    231     on_before_run();
    232 
    233   // We don't care about filters here, it's implemented by `run_all`.
    234   BrokenAPI_runAll();
    235 
    236   if (on_after_run)
    237     on_after_run();
    238 
    239   return 0;
    240 }
    241 
    242 static void BrokenAPI_printMessage(const char* prefix, const char* fmt, va_list ap) noexcept {
    243   BrokenGlobal& global = _broken_global;
    244   FILE* dst = global.file();
    245 
    246   if (!fmt || fmt[0] == '\0') {
    247     fprintf(dst, "\n");
    248   }
    249   else {
    250     // This looks scary, but we really want to use only a single call to vfprintf()
    251     // in multithreaded code. So we change the format a bit if necessary.
    252     enum : unsigned { kBufferSize = 512 };
    253     char static_buffer[512];
    254 
    255     size_t fmt_size = strlen(fmt);
    256     size_t prefix_size = strlen(prefix);
    257 
    258     char* fmt_buf = static_buffer;
    259     if (fmt_size > kBufferSize - 2 - prefix_size)
    260       fmt_buf = static_cast<char*>(malloc(fmt_size + prefix_size + 2));
    261 
    262     if (!fmt_buf) {
    263       fprintf(dst, "%s_cannot allocate buffer for vfprintf()\n", prefix);
    264     }
    265     else {
    266       memcpy(fmt_buf, prefix, prefix_size);
    267       memcpy(fmt_buf + prefix_size, fmt, fmt_size);
    268 
    269       fmt_size += prefix_size;
    270       if (fmt_buf[fmt_size - 1] != '\n')
    271         fmt_buf[fmt_size++] = '\n';
    272       fmt_buf[fmt_size] = '\0';
    273 
    274       vfprintf(dst, fmt_buf, ap);
    275 
    276       if (fmt_buf != static_buffer)
    277         free(fmt_buf);
    278     }
    279   }
    280 
    281   fflush(dst);
    282 }
    283 
    284 void BrokenAPI::info(const char* fmt, ...) noexcept {
    285   BrokenGlobal& global = _broken_global;
    286 
    287   va_list ap;
    288   va_start(ap, fmt);
    289   BrokenAPI_printMessage(global._unit_running ? "  " : "", fmt, ap);
    290   va_end(ap);
    291 }
    292 
    293 void BrokenAPI::fail(const char* file, int line, const char* expression, const char* fmt, ...) noexcept {
    294   BrokenGlobal& global = _broken_global;
    295   FILE* dst = global.file();
    296 
    297   fprintf(dst, "  FAILED: %s\n", expression);
    298 
    299   if (fmt) {
    300     va_list ap;
    301     va_start(ap, fmt);
    302     BrokenAPI_printMessage("  REASON: ", fmt, ap);
    303     va_end(ap);
    304   }
    305 
    306   fprintf(dst, "  SOURCE: %s (Line: %d)\n", file, line);
    307   fflush(dst);
    308 
    309   abort();
    310 }