AUnit  1.7.1
Unit testing framework for Arduino platforms inspired by ArduinoUnit and Google Test.
TestRunner.cpp
1 /*
2 MIT License
3 
4 Copyright (c) 2018 Brian T. Park
5 
6 Permission is hereby granted, free of charge, to any person obtaining a copy
7 of this software and associated documentation files (the "Software"), to deal
8 in the Software without restriction, including without limitation the rights
9 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 copies of the Software, and to permit persons to whom the Software is
11 furnished to do so, subject to the following conditions:
12 
13 The above copyright notice and this permission notice shall be included in all
14 copies or substantial portions of the Software.
15 
16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 SOFTWARE.
23 */
24 
25 #if EPOXY_DUINO
26 #include <stdio.h>
27 #endif
28 #include <Arduino.h> // 'Serial' or SERIAL_PORT_MONITOR
29 #include <string.h>
30 #include <stdint.h>
31 #include "FCString.h"
32 #include "Compare.h"
33 #include "Printer.h"
34 #include "Verbosity.h"
35 #include "Test.h"
36 #include "TestRunner.h"
37 #include "string_util.h"
38 
39 namespace aunit {
40 
41 // Use a function static singleton to avoid the static initialization ordering
42 // problem. It's probably not an issue right now, since TestRunner is expected
43 // to be called only after all static initialization, but future refactoring
44 // could change that so this is defensive programming.
45 TestRunner* TestRunner::getRunner() {
46  static TestRunner singletonRunner;
47  return &singletonRunner;
48 }
49 
50 void TestRunner::setPrinter(Print* printer) {
51  Printer::setPrinter(printer);
52 }
53 
54 void TestRunner::setLifeCycleMatchingPattern(const char* pattern,
55  uint8_t lifeCycle) {
56  // Do an implicit excludeAll() if the first filter is an include().
57  if (!hasBeenFiltered && lifeCycle == Test::kLifeCycleNew) {
58  excludeAll();
59  }
60  hasBeenFiltered = true;
61 
62  size_t length = strlen(pattern);
63  if (length > 0 && pattern[length - 1] == '*') {
64  // prefix match
65  length--;
66  } else {
67  // exact match
68  length++;
69  }
70 
71  for (Test** p = Test::getRoot(); *p != nullptr; p = (*p)->getNext()) {
72  if ((*p)->getName().compareToN(pattern, length) == 0) {
73  (*p)->setLifeCycle(lifeCycle);
74  }
75  }
76 }
77 
78 void TestRunner::setLifeCycleMatchingPattern(const char* testClass,
79  const char* pattern, uint8_t lifeCycle) {
80 
81  // Do an implicit excludeAll() if the first filter is an include().
82  if (!hasBeenFiltered && lifeCycle == Test::kLifeCycleNew) {
83  excludeAll();
84  }
85  hasBeenFiltered = true;
86 
87  // The effective pattern is the join of testClass and pattern with a '_'
88  // delimiter. This must match the algorithm used by testF() and testingF().
89  // We use string_join() instead of String so that AUnit can avoid a direct
90  // dependency on the String class. AUnit thus avoids allocating any memory on
91  // the heap.
92  char fullPattern[kMaxPatternLength];
93  bool status = internal::string_join(fullPattern, kMaxPatternLength, '_',
94  testClass, pattern);
95  if (!status) return;
96 
97  setLifeCycleMatchingPattern(fullPattern, lifeCycle);
98 }
99 
100 void TestRunner::setLifeCycleMatchingSubstring(
101  const char* substring, uint8_t lifeCycle) {
102 
103  // Do an implicit excludeAll() if the first filter is an include().
104  if (!hasBeenFiltered && lifeCycle == Test::kLifeCycleNew) {
105  excludeAll();
106  }
107  hasBeenFiltered = true;
108 
109  for (Test** p = Test::getRoot(); *p != nullptr; p = (*p)->getNext()) {
110  if ((*p)->getName().hasSubstring(substring)) {
111  (*p)->setLifeCycle(lifeCycle);
112  }
113  }
114 }
115 
116 void TestRunner::excludeAll() {
117  for (Test** p = Test::getRoot(); *p != nullptr; p = (*p)->getNext()) {
118  (*p)->setLifeCycle(Test::kLifeCycleExcluded);
119  }
120 }
121 
122 // Count the number of tests in TestRunner instead of Test::insert() to avoid
123 // another C++ static initialization ordering problem.
124 uint16_t TestRunner::countTests() {
125  uint16_t count = 0;
126  for (Test** p = Test::getRoot(); *p != nullptr; p = (*p)->getNext()) {
127  count++;
128  }
129  return count;
130 }
131 
132 namespace {
133 
139 void printSeconds(Print* printer, unsigned long timeMillis) {
140  int s = timeMillis / 1000;
141  int ms = timeMillis % 1000;
142  printer->print(s);
143  printer->print('.');
144  if (ms < 100) printer->print('0');
145  if (ms < 10) printer->print('0');
146  printer->print(ms);
147 }
148 
149 }
150 
151 void TestRunner::printStartRunner() const {
153 
154  Print* printer = Printer::getPrinter();
155  printer->print(F("TestRunner started on "));
156  printer->print(mCount);
157  printer->println(F(" test(s)."));
158 }
159 
160 void TestRunner::resolveRun() const {
162  Print* printer = Printer::getPrinter();
163 
164  unsigned long elapsedTime = mEndTime - mStartTime;
165  printer->print(F("TestRunner duration: "));
166  printSeconds(printer, elapsedTime);
167  printer->println(" seconds.");
168 
169  printer->print(F("TestRunner summary: "));
170  printer->print(mPassedCount);
171  printer->print(F(" passed, "));
172  printer->print(mFailedCount);
173  printer->print(F(" failed, "));
174  printer->print(mSkippedCount);
175  printer->print(F(" skipped, "));
176  printer->print(mExpiredCount);
177  printer->print(F(" timed out, out of "));
178  printer->print(mCount);
179  printer->println(F(" test(s)."));
180 }
181 
182 void TestRunner::setRunnerTimeout(TimeoutType timeout) {
183  mTimeout = timeout;
184 }
185 
186 //----------------------------------------------------------------------------
187 // Command line argument processing on EpoxyDuino
188 //----------------------------------------------------------------------------
189 
190 #if EPOXY_DUINO
191 static void shift(int& argc, const char* const*& argv) {
192  argc--;
193  argv++;
194 }
195 
196 static bool argEquals(const char* s, const char* t) {
197  return strcmp(s, t) == 0;
198 }
199 
200 static void usageAndExit(int status) {
201  fprintf(
202  stderr,
203  "Usage: %s [--help|-h]\n"
204  " [--include pattern,...] [--exclude pattern,...]\n"
205  " [--includesub substring,...] [--excludesub substring,...]\n"
206  " [--] [substring ...]\n",
207  epoxy_argv[0]
208  );
209  exit(status);
210 }
211 
212 void TestRunner::processCommaList(
213  const char* const commaList, FilterType filterType) {
214 
215  const int kArgumentSize = 64;
216  const char* list = commaList;
217 
218  // Loop for each comma-separate list of words. Call the filtering command
219  // defined by the filterType.
220  char argument[kArgumentSize];
221  while (*list != '\0') {
222  const char* comma = strchr(list, ',');
223  int length = (comma) ? (comma - list) : strlen(list);
224 
225  // Copy each word of the comma-separated list.
226  if (length >= kArgumentSize - 1) {
227  length = kArgumentSize - 1;
228  }
229  memcpy(argument, list, length);
230  argument[length] = '\0';
231 
232  switch (filterType) {
233  case FilterType::kInclude:
234  include(argument);
235  break;
236  case FilterType::kExclude:
237  exclude(argument);
238  break;
239  case FilterType::kIncludeSub:
240  includesub(argument);
241  break;
242  case FilterType::kExcludeSub:
243  excludesub(argument);
244  break;
245  }
246 
247  list += (comma) ? length + 1 : length;
248  }
249 }
250 
255 int TestRunner::parseFlags(int argc, const char* const* argv) {
256  int argc_original = argc;
257  shift(argc, argv);
258  while (argc > 0) {
259  if (argEquals(argv[0], "--include")) {
260  shift(argc, argv);
261  if (argc == 0) usageAndExit(1);
262  processCommaList(argv[0], FilterType::kInclude);
263  } else if (argEquals(argv[0], "--exclude")) {
264  shift(argc, argv);
265  if (argc == 0) usageAndExit(1);
266  processCommaList(argv[0], FilterType::kExclude);
267  } else if (argEquals(argv[0], "--includesub")) {
268  shift(argc, argv);
269  if (argc == 0) usageAndExit(1);
270  processCommaList(argv[0], FilterType::kIncludeSub);
271  } else if (argEquals(argv[0], "--excludesub")) {
272  shift(argc, argv);
273  if (argc == 0) usageAndExit(1);
274  processCommaList(argv[0], FilterType::kExcludeSub);
275  } else if (argEquals(argv[0], "--")) {
276  shift(argc, argv);
277  break;
278  } else if (argEquals(argv[0], "--help") || argEquals(argv[0], "-h")) {
279  usageAndExit(0);
280  break;
281  } else if (argv[0][0] == '-') {
282  fprintf(stderr, "Unknonwn flag '%s'\n", argv[0]);
283  usageAndExit(1);
284  } else {
285  break;
286  }
287  shift(argc, argv);
288  }
289 
290  return argc_original - argc;
291 }
292 
293 void TestRunner::processCommandLine() {
294  int args = parseFlags(epoxy_argc, epoxy_argv);
295 
296  // Process any remaining *space*-separated arguments using includesub().
297  for (int i = args; i < epoxy_argc; i++) {
298  includesub(epoxy_argv[i]);
299  }
300 }
301 
302 #endif
303 
304 }
This file provides overloaded compareXxx(a, b) functions which are used by the various assertXxx(a,...
static void setPrinter(Print *printer)
Set the printer.
Definition: Printer.h:51
static Print * getPrinter()
Get the output printer used by the various assertion() methods and the TestRunner.
Definition: Printer.h:48
static void include(const char *pattern)
Include the tests which match the pattern.
Definition: TestRunner.h:90
static bool isVerbosity(uint8_t verbosity)
Returns true if ANY of the bit flags of 'verbosity' is set.
Definition: TestRunner.h:123
static void includesub(const char *substring)
Include the tests which match the substring.
Definition: TestRunner.h:112
static void list()
Print out the known tests.
Definition: TestRunner.h:62
static void setPrinter(Print *printer)
Set the output printer.
Definition: TestRunner.cpp:50
static void excludesub(const char *substring)
Exclude the tests which match the substring.
Definition: TestRunner.h:106
static void exclude(const char *pattern)
Exclude the tests which match the pattern.
Definition: TestRunner.h:70
static const uint8_t kLifeCycleNew
Test is new, needs to be setup.
Definition: Test.h:57
static Test ** getRoot()
Get the pointer to the root pointer.
Definition: Test.cpp:36
static const uint8_t kLifeCycleExcluded
Test is Excluded by an exclude() method.
Definition: Test.h:65
Test ** getNext()
Return the next pointer as a pointer to the pointer, similar to getRoot().
Definition: Test.h:188
static const uint8_t kTestRunSummary
Print TestRunner summary message.
Definition: Verbosity.h:58