test.c++ (11263B)
1 // Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors 2 // Licensed under the MIT License: 3 // 4 // Permission is hereby granted, free of charge, to any person obtaining a copy 5 // of this software and associated documentation files (the "Software"), to deal 6 // in the Software without restriction, including without limitation the rights 7 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 // copies of the Software, and to permit persons to whom the Software is 9 // furnished to do so, subject to the following conditions: 10 // 11 // The above copyright notice and this permission notice shall be included in 12 // all copies or substantial portions of the Software. 13 // 14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 // THE SOFTWARE. 21 22 #ifndef _GNU_SOURCE 23 #define _GNU_SOURCE 24 #endif 25 26 #include "test.h" 27 #include "main.h" 28 #include "io.h" 29 #include "miniposix.h" 30 #include <stdlib.h> 31 #include <signal.h> 32 #include <string.h> 33 #include "time.h" 34 #ifndef _WIN32 35 #include <sys/mman.h> 36 #endif 37 38 namespace kj { 39 40 namespace { 41 42 TestCase* testCasesHead = nullptr; 43 TestCase** testCasesTail = &testCasesHead; 44 45 } // namespace 46 47 TestCase::TestCase(const char* file, uint line, const char* description) 48 : file(file), line(line), description(description), next(nullptr), prev(testCasesTail), 49 matchedFilter(false) { 50 *prev = this; 51 testCasesTail = &next; 52 } 53 54 TestCase::~TestCase() { 55 *prev = next; 56 if (next == nullptr) { 57 testCasesTail = prev; 58 } else { 59 next->prev = prev; 60 } 61 } 62 63 // ======================================================================================= 64 65 namespace _ { // private 66 67 GlobFilter::GlobFilter(const char* pattern): pattern(heapString(pattern)) {} 68 GlobFilter::GlobFilter(ArrayPtr<const char> pattern): pattern(heapString(pattern)) {} 69 70 bool GlobFilter::matches(StringPtr name) { 71 // Get out your computer science books. We're implementing a non-deterministic finite automaton. 72 // 73 // Our NDFA has one "state" corresponding to each character in the pattern. 74 // 75 // As you may recall, an NDFA can be transformed into a DFA where every state in the DFA 76 // represents some combination of states in the NDFA. Therefore, we actually have to store a 77 // list of states here. (Actually, what we really want is a set of states, but because our 78 // patterns are mostly non-cyclic a list of states should work fine and be a bit more efficient.) 79 80 // Our state list starts out pointing only at the start of the pattern. 81 states.resize(0); 82 states.add(0); 83 84 Vector<uint> scratch; 85 86 // Iterate through each character in the name. 87 for (char c: name) { 88 // Pull the current set of states off to the side, so that we can populate `states` with the 89 // new set of states. 90 Vector<uint> oldStates = kj::mv(states); 91 states = kj::mv(scratch); 92 states.resize(0); 93 94 // The pattern can omit a leading path. So if we're at a '/' then enter the state machine at 95 // the beginning on the next char. 96 if (c == '/' || c == '\\') { 97 states.add(0); 98 } 99 100 // Process each state. 101 for (uint state: oldStates) { 102 applyState(c, state); 103 } 104 105 // Store the previous state vector for reuse. 106 scratch = kj::mv(oldStates); 107 } 108 109 // If any one state is at the end of the pattern (or at a wildcard just before the end of the 110 // pattern), we have a match. 111 for (uint state: states) { 112 while (state < pattern.size() && pattern[state] == '*') { 113 ++state; 114 } 115 if (state == pattern.size()) { 116 return true; 117 } 118 } 119 return false; 120 } 121 122 void GlobFilter::applyState(char c, int state) { 123 if (state < pattern.size()) { 124 switch (pattern[state]) { 125 case '*': 126 // At a '*', we both re-add the current state and attempt to match the *next* state. 127 if (c != '/' && c != '\\') { // '*' doesn't match '/'. 128 states.add(state); 129 } 130 applyState(c, state + 1); 131 break; 132 133 case '?': 134 // A '?' matches one character (never a '/'). 135 if (c != '/' && c != '\\') { 136 states.add(state + 1); 137 } 138 break; 139 140 default: 141 // Any other character matches only itself. 142 if (c == pattern[state]) { 143 states.add(state + 1); 144 } 145 break; 146 } 147 } 148 } 149 150 } // namespace _ (private) 151 152 // ======================================================================================= 153 154 namespace { 155 156 class TestExceptionCallback: public ExceptionCallback { 157 public: 158 TestExceptionCallback(ProcessContext& context): context(context) {} 159 160 bool failed() { return sawError; } 161 162 void logMessage(LogSeverity severity, const char* file, int line, int contextDepth, 163 String&& text) override { 164 void* traceSpace[32]; 165 auto trace = getStackTrace(traceSpace, 2); 166 167 if (text.size() == 0) { 168 text = kj::heapString("expectation failed"); 169 } 170 171 text = kj::str(kj::repeat('_', contextDepth), file, ':', line, ": ", kj::mv(text)); 172 173 if (severity == LogSeverity::ERROR || severity == LogSeverity::FATAL) { 174 sawError = true; 175 context.error(kj::str(text, "\nstack: ", strArray(trace, " "), stringifyStackTrace(trace))); 176 } else { 177 context.warning(text); 178 } 179 } 180 181 private: 182 ProcessContext& context; 183 bool sawError = false; 184 }; 185 186 TimePoint readClock() { 187 return systemPreciseMonotonicClock().now(); 188 } 189 190 } // namespace 191 192 class TestRunner { 193 public: 194 explicit TestRunner(ProcessContext& context) 195 : context(context), useColor(isatty(STDOUT_FILENO)) {} 196 197 MainFunc getMain() { 198 return MainBuilder(context, "KJ Test Runner (version not applicable)", 199 "Run all tests that have been linked into the binary with this test runner.") 200 .addOptionWithArg({'f', "filter"}, KJ_BIND_METHOD(*this, setFilter), "<file>[:<line>]", 201 "Run only the specified test case(s). You may use a '*' wildcard in <file>. You may " 202 "also omit any prefix of <file>'s path; test from all matching files will run. " 203 "You may specify multiple filters; any test matching at least one filter will run. " 204 "<line> may be a range, e.g. \"100-500\".") 205 .addOption({'l', "list"}, KJ_BIND_METHOD(*this, setList), 206 "List all test cases that would run, but don't run them. If --filter is specified " 207 "then only the match tests will be listed.") 208 .callAfterParsing(KJ_BIND_METHOD(*this, run)) 209 .build(); 210 } 211 212 MainBuilder::Validity setFilter(StringPtr pattern) { 213 hasFilter = true; 214 ArrayPtr<const char> filePattern = pattern; 215 uint minLine = kj::minValue; 216 uint maxLine = kj::maxValue; 217 218 KJ_IF_MAYBE(colonPos, pattern.findLast(':')) { 219 char* end; 220 StringPtr lineStr = pattern.slice(*colonPos + 1); 221 222 bool parsedRange = false; 223 minLine = strtoul(lineStr.cStr(), &end, 0); 224 if (end != lineStr.begin()) { 225 if (*end == '-') { 226 // A range. 227 const char* part2 = end + 1; 228 maxLine = strtoul(part2, &end, 0); 229 if (end > part2 && *end == '\0') { 230 parsedRange = true; 231 } 232 } else if (*end == '\0') { 233 parsedRange = true; 234 maxLine = minLine; 235 } 236 } 237 238 if (parsedRange) { 239 // We have an exact line number. 240 filePattern = pattern.slice(0, *colonPos); 241 } else { 242 // Can't parse as a number. Maybe the colon is part of a Windows path name or something. 243 // Let's just keep it as part of the file pattern. 244 minLine = kj::minValue; 245 maxLine = kj::maxValue; 246 } 247 } 248 249 _::GlobFilter filter(filePattern); 250 251 for (TestCase* testCase = testCasesHead; testCase != nullptr; testCase = testCase->next) { 252 if (!testCase->matchedFilter && filter.matches(testCase->file) && 253 testCase->line >= minLine && testCase->line <= maxLine) { 254 testCase->matchedFilter = true; 255 } 256 } 257 258 return true; 259 } 260 261 MainBuilder::Validity setList() { 262 listOnly = true; 263 return true; 264 } 265 266 MainBuilder::Validity run() { 267 if (testCasesHead == nullptr) { 268 return "no tests were declared"; 269 } 270 271 // Find the common path prefix of all filenames, so we can strip it off. 272 ArrayPtr<const char> commonPrefix = StringPtr(testCasesHead->file); 273 for (TestCase* testCase = testCasesHead; testCase != nullptr; testCase = testCase->next) { 274 for (size_t i: kj::indices(commonPrefix)) { 275 if (testCase->file[i] != commonPrefix[i]) { 276 commonPrefix = commonPrefix.slice(0, i); 277 break; 278 } 279 } 280 } 281 282 // Back off the prefix to the last '/'. 283 while (commonPrefix.size() > 0 && commonPrefix.back() != '/' && commonPrefix.back() != '\\') { 284 commonPrefix = commonPrefix.slice(0, commonPrefix.size() - 1); 285 } 286 287 // Run the testts. 288 uint passCount = 0; 289 uint failCount = 0; 290 for (TestCase* testCase = testCasesHead; testCase != nullptr; testCase = testCase->next) { 291 if (!hasFilter || testCase->matchedFilter) { 292 auto name = kj::str(testCase->file + commonPrefix.size(), ':', testCase->line, 293 ": ", testCase->description); 294 295 write(BLUE, "[ TEST ]", name); 296 297 if (!listOnly) { 298 bool currentFailed = true; 299 auto start = readClock(); 300 KJ_IF_MAYBE(exception, runCatchingExceptions([&]() { 301 TestExceptionCallback exceptionCallback(context); 302 testCase->run(); 303 currentFailed = exceptionCallback.failed(); 304 })) { 305 context.error(kj::str(*exception)); 306 } 307 auto end = readClock(); 308 309 auto message = kj::str(name, " (", (end - start) / kj::MICROSECONDS, " μs)"); 310 311 if (currentFailed) { 312 write(RED, "[ FAIL ]", message); 313 ++failCount; 314 } else { 315 write(GREEN, "[ PASS ]", message); 316 ++passCount; 317 } 318 } 319 } 320 } 321 322 if (passCount > 0) write(GREEN, kj::str(passCount, " test(s) passed"), ""); 323 if (failCount > 0) write(RED, kj::str(failCount, " test(s) failed"), ""); 324 context.exit(); 325 326 KJ_UNREACHABLE; 327 } 328 329 private: 330 ProcessContext& context; 331 bool useColor; 332 bool hasFilter = false; 333 bool listOnly = false; 334 335 enum Color { 336 RED, 337 GREEN, 338 BLUE 339 }; 340 341 void write(StringPtr text) { 342 FdOutputStream(STDOUT_FILENO).write(text.begin(), text.size()); 343 } 344 345 void write(Color color, StringPtr prefix, StringPtr message) { 346 StringPtr startColor, endColor; 347 if (useColor) { 348 switch (color) { 349 case RED: startColor = "\033[0;1;31m"; break; 350 case GREEN: startColor = "\033[0;1;32m"; break; 351 case BLUE: startColor = "\033[0;1;34m"; break; 352 } 353 endColor = "\033[0m"; 354 } 355 356 String text = kj::str(startColor, prefix, endColor, ' ', message, '\n'); 357 write(text); 358 } 359 }; 360 361 } // namespace kj 362 363 KJ_MAIN(kj::TestRunner);