AUnit  1.7.1
Unit testing framework for Arduino platforms inspired by ArduinoUnit and Google Test.
TestRunner.h
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 #ifndef AUNIT_TEST_RUNNER_H
26 #define AUNIT_TEST_RUNNER_H
27 
28 #if defined(EPOXY_DUINO)
29 #include <stdlib.h> // exit()
30 #endif
31 #include <stdint.h>
32 #include <Arduino.h> // SERIAL_PORT_MONITOR, F(), Print
33 #include "Test.h"
34 
35 // ESP32 does not defined SERIAL_PORT_MONITOR
36 #ifndef SERIAL_PORT_MONITOR
37 #define SERIAL_PORT_MONITOR Serial
38 #endif
39 
40 namespace aunit {
41 
48 class TestRunner {
49  public:
54  typedef uint16_t TimeoutType;
55 
57  static void run() {
58  getRunner()->runTest();
59  }
60 
62  static void list() {
63  getRunner()->listTests();
64  }
65 
70  static void exclude(const char* pattern) {
71  getRunner()->setLifeCycleMatchingPattern(
72  pattern, Test::kLifeCycleExcluded);
73  }
74 
81  static void exclude(const char* testClass, const char* pattern) {
82  getRunner()->setLifeCycleMatchingPattern(testClass, pattern,
84  }
85 
90  static void include(const char* pattern) {
91  getRunner()->setLifeCycleMatchingPattern(pattern, Test::kLifeCycleNew);
92  }
93 
100  static void include(const char* testClass, const char* pattern) {
101  getRunner()->setLifeCycleMatchingPattern(testClass, pattern,
103  }
104 
106  static void excludesub(const char* substring) {
107  getRunner()->setLifeCycleMatchingSubstring(
108  substring, Test::kLifeCycleExcluded);
109  }
110 
112  static void includesub(const char* substring) {
113  getRunner()->setLifeCycleMatchingSubstring(
114  substring, Test::kLifeCycleNew);
115  }
116 
118  static void setVerbosity(uint8_t verbosity) {
119  getRunner()->setVerbosityFlag(verbosity);
120  }
121 
123  static bool isVerbosity(uint8_t verbosity) {
124  return getRunner()->isVerbosityFlag(verbosity);
125  }
126 
128  static void setPrinter(Print* printer);
129 
136  static void setTimeout(TimeoutType seconds) {
137  getRunner()->setRunnerTimeout(seconds);
138  }
139 
140  private:
142  static const TimeoutType kTimeoutDefault = 10;
143 
145  static const uint8_t kMaxPatternLength = 63 + 1;
146 
148  static TestRunner* getRunner();
149 
151  static uint16_t countTests();
152 
153  // Disable copy-constructor and assignment operator
154  TestRunner(const TestRunner&) = delete;
155  TestRunner& operator=(const TestRunner&) = delete;
156 
158  TestRunner() {}
159 
168  void runTest() {
169  setupRunner();
170 
171  // Print initial header if this is the first run.
172  if (!mIsRunning) {
173  printStartRunner();
174  mIsRunning = true;
175  }
176 
177  // If no more test cases, then print out summary of run.
178  if (*Test::getRoot() == nullptr) {
179  if (!mIsResolved) {
180  mEndTime = millis();
181  resolveRun();
182  mIsResolved = true;
183  #if EPOXY_DUINO
184  exit((mFailedCount || mExpiredCount) ? 1 : 0);
185  #endif
186  }
187  return;
188  }
189 
190  // If reached the end and there are still test cases left, start from the
191  // beginning again.
192  if (*mCurrent == nullptr) {
193  mCurrent = Test::getRoot();
194  }
195 
196  // Implement a finite state machine that calls the (*mCurrent)->setup() or
197  // (*mCurrent)->loop(), then changes the test case's mStatus.
198  switch ((*mCurrent)->getLifeCycle()) {
199  case Test::kLifeCycleNew:
200  // Transfer the verbosity of the TestRunner to the Test.
201  (*mCurrent)->enableVerbosity(mVerbosity);
202  (*mCurrent)->setup();
203 
204  // Support assertXxx() statements inside the setup() method by
205  // moving to the next lifeCycle state if an assertXxx() did not fail
206  // inside the setup().
207  if ((*mCurrent)->getLifeCycle() == Test::kLifeCycleNew) {
208  (*mCurrent)->setLifeCycle(Test::kLifeCycleSetup);
209  }
210  break;
212  // If a test is excluded, go directly to LifeCycleFinished, without
213  // calling setup() or teardown().
214  (*mCurrent)->enableVerbosity(mVerbosity);
215  (*mCurrent)->setStatus(Test::kStatusSkipped);
216  mSkippedCount++;
217  (*mCurrent)->setLifeCycle(Test::kLifeCycleFinished);
218  break;
220  {
221  // Check for timeout. mTimeout == 0 means infinite timeout. NOTE: It
222  // feels like this code should go into the Test::loop() method (like
223  // the extra bit of code in TestOnce::loop()) because it seems like
224  // we could want the timeout to be configurable on a case by case
225  // basis. This would cause the testing() code to move down into a
226  // new again() virtual method dispatched from Test::loop(),
227  // analogous to once(). But let's keep the code here for now.
228  unsigned long now = millis();
229  if (mTimeout > 0 && now >= mStartTime + 1000L * mTimeout) {
230  (*mCurrent)->expire();
231  } else {
232  (*mCurrent)->loop();
233 
234  // If test status is unresolved (i.e. still in kLifeCycleNew
235  // state) after loop(), then this is a continuous testing() test
236  // case, so skip to the next test. Otherwise, stay on the current
237  // test so that the next iteration of runTest() can resolve the
238  // current test.
239  if ((*mCurrent)->getLifeCycle() == Test::kLifeCycleSetup) {
240  // skip to the next one, but keep current test in the list
241  mCurrent = (*mCurrent)->getNext();
242  }
243  }
244  }
245  break;
247  switch ((*mCurrent)->getStatus()) {
249  mSkippedCount++;
250  break;
251  case Test::kStatusPassed:
252  mPassedCount++;
253  break;
254  case Test::kStatusFailed:
255  mFailedCount++;
256  break;
258  mExpiredCount++;
259  break;
260  default:
261  // should never get here
262  mStatusErrorCount++;
263  break;
264  }
265  (*mCurrent)->teardown();
266  (*mCurrent)->setLifeCycle(Test::kLifeCycleFinished);
267  break;
269  (*mCurrent)->resolve();
270  // skip to the next one by taking current test out of the list
271  *mCurrent = *(*mCurrent)->getNext();
272  break;
273  }
274  }
275 
284  void listTests() {
285  setupRunner();
286 
287  Print* printer = Printer::getPrinter();
288  printer->print(F("TestRunner test count: "));
289  printer->println(mCount);
290  for (Test** p = Test::getRoot(); (*p) != nullptr; p = (*p)->getNext()) {
291  printer->print(F("Test "));
292  (*p)->getName().print(printer);
293  printer->print(F("; lifeCycle: "));
294  printer->println((*p)->getLifeCycle());
295  }
296  }
297 
299  void printStartRunner() const;
300 
302  void resolveRun() const;
303 
359  void setupRunner() {
360  if (mIsSetup) return;
361 
362  if (! Printer::getPrinter()) {
363  Printer::setPrinter(&SERIAL_PORT_MONITOR);
364  }
365 
366  #if EPOXY_DUINO
367  processCommandLine();
368  #endif
369  mIsSetup = true;
370  mCount = countTests();
371  mCurrent = Test::getRoot();
372  mStartTime = millis();
373  }
374 
376  void setVerbosityFlag(uint8_t verbosity) { mVerbosity = verbosity; }
377 
379  bool isVerbosityFlag(uint8_t verbosity) const {
380  return mVerbosity & verbosity;
381  }
382 
384  void setLifeCycleMatchingPattern(const char* pattern, uint8_t lifeCycle);
385 
390  void setLifeCycleMatchingPattern(const char* testClass, const char* pattern,
391  uint8_t lifeCycle);
392 
394  void setLifeCycleMatchingSubstring(
395  const char* substring, uint8_t lifeCycle);
396 
398  void excludeAll();
399 
401  void setRunnerTimeout(TimeoutType seconds);
402 
403  #if EPOXY_DUINO
404  enum class FilterType : uint8_t {
405  kInclude,
406  kExclude,
407  kIncludeSub,
408  kExcludeSub
409  };
410 
412  void processCommandLine();
413 
415  int parseFlags(int argc, const char* const* argv);
416 
421  void processCommaList(const char* commaList, FilterType filterType);
422  #endif
423 
424  private:
425  // The current test case is represented by a pointer to a pointer. This
426  // allows treating the root node the same as all the other nodes, and
427  // simplifies the code traversing the singly-linked list significantly.
428  Test** mCurrent = nullptr;
429 
430  bool mIsResolved = false;
431  bool mIsSetup = false;
432  bool mIsRunning = false;
433  uint8_t mVerbosity = Verbosity::kDefault;
434  // True if any include(), exclude(), includesub(), excludesub() was invoked.
435  bool hasBeenFiltered = false;
436  uint16_t mCount = 0;
437  uint16_t mPassedCount = 0;
438  uint16_t mFailedCount = 0;
439  uint16_t mSkippedCount = 0;
440  uint16_t mExpiredCount = 0;
441  uint16_t mStatusErrorCount = 0;
442  TimeoutType mTimeout = kTimeoutDefault;
443  unsigned long mStartTime;
444  unsigned long mEndTime;
445 };
446 
447 }
448 
449 #endif
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
The class that runs the various test cases defined by the test() and testing() macros.
Definition: TestRunner.h:48
uint16_t TimeoutType
Integer type of the timeout parameter.
Definition: TestRunner.h:54
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 setVerbosity(uint8_t verbosity)
Set the verbosity flag.
Definition: TestRunner.h:118
static void include(const char *testClass, const char *pattern)
Include the tests which match the pattern given by (testClass + "_" + pattern), the same concatenatio...
Definition: TestRunner.h:100
static void includesub(const char *substring)
Include the tests which match the substring.
Definition: TestRunner.h:112
static void run()
Run all tests using the current runner.
Definition: TestRunner.h:57
static void list()
Print out the known tests.
Definition: TestRunner.h:62
static void setTimeout(TimeoutType seconds)
Set test runner timeout across all tests, in seconds.
Definition: TestRunner.h:136
static void exclude(const char *testClass, const char *pattern)
Exclude the tests which match the pattern given by (testClass + "_" + pattern), the same concatenatio...
Definition: TestRunner.h:81
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 kLifeCycleSetup
Test has been set up by calling setup() and ready to execute the test code.
Definition: Test.h:74
static const uint8_t kStatusFailed
Test has failed, or fail() was called.
Definition: Test.h:102
static const uint8_t kLifeCycleFinished
The test has completed its life cycle.
Definition: Test.h:88
static const uint8_t kStatusPassed
Test has passed, or pass() was called.
Definition: Test.h:99
static const uint8_t kStatusExpired
Test has timed out, or expire() called.
Definition: Test.h:108
static const uint8_t kLifeCycleAsserted
Test is asserted (using pass(), fail(), expired() or skipped()) and the getStatus() has been determin...
Definition: Test.h:80
static const uint8_t kStatusSkipped
Test is skipped through the exclude() method or skip() was called.
Definition: Test.h:105
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 kDefault
The default verbosity.
Definition: Verbosity.h:69