capnproto

FORK: Cap'n Proto serialization/RPC system - core tools and C++ library
git clone https://git.neptards.moe/neptards/capnproto.git
Log | Files | Refs | README | LICENSE

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);