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