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 
TestCase(const char * file,uint line,const char * description)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 
~TestCase()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 
GlobFilter(const char * pattern)67 GlobFilter::GlobFilter(const char* pattern): pattern(heapString(pattern)) {}
GlobFilter(ArrayPtr<const char> pattern)68 GlobFilter::GlobFilter(ArrayPtr<const char> pattern): pattern(heapString(pattern)) {}
69 
matches(StringPtr name)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 
applyState(char c,int state)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:
TestExceptionCallback(ProcessContext & context)158   TestExceptionCallback(ProcessContext& context): context(context) {}
159 
failed()160   bool failed() { return sawError; }
161 
logMessage(LogSeverity severity,const char * file,int line,int contextDepth,String && text)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 
readClock()186 TimePoint readClock() {
187   return systemPreciseMonotonicClock().now();
188 }
189 
190 }  // namespace
191 
192 class TestRunner {
193 public:
TestRunner(ProcessContext & context)194   explicit TestRunner(ProcessContext& context)
195       : context(context), useColor(isatty(STDOUT_FILENO)) {}
196 
getMain()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 
setFilter(StringPtr pattern)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 
setList()261   MainBuilder::Validity setList() {
262     listOnly = true;
263     return true;
264   }
265 
run()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 
write(StringPtr text)341   void write(StringPtr text) {
342     FdOutputStream(STDOUT_FILENO).write(text.begin(), text.size());
343   }
344 
write(Color color,StringPtr prefix,StringPtr message)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);
364