From b66faf5068a0c9a221ff8ebeee2d1befdd1adb0a Mon Sep 17 00:00:00 2001 From: Brian Park Date: Mon, 12 Mar 2018 21:52:49 -0700 Subject: [PATCH 01/22] Initial upload to GitHub --- CHANGELOG.md | 4 + README.md | 542 ++++++++++++++++++++++++++- examples/AUnitTest/AUnitTest.ino | 314 ++++++++++++++++ examples/advanced/advanced.ino | 110 ++++++ examples/basic/basic.ino | 39 ++ examples/continuous/continuous.ino | 42 +++ examples/filter/filter.ino | 47 +++ keywords.txt | 89 +++++ library.properties | 9 + src/AUnit.h | 14 + src/aunit/Assertion.h | 228 ++++++++++++ src/aunit/Compare.cpp | 110 ++++++ src/aunit/Compare.h | 577 +++++++++++++++++++++++++++++ src/aunit/FCString.h | 46 +++ src/aunit/Printer.cpp | 8 + src/aunit/Printer.h | 39 ++ src/aunit/Test.cpp | 57 +++ src/aunit/Test.h | 148 ++++++++ src/aunit/TestRunner.cpp | 179 +++++++++ src/aunit/TestRunner.h | 114 ++++++ src/aunit/Verbosity.h | 35 ++ 21 files changed, 2749 insertions(+), 2 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 examples/AUnitTest/AUnitTest.ino create mode 100644 examples/advanced/advanced.ino create mode 100644 examples/basic/basic.ino create mode 100644 examples/continuous/continuous.ino create mode 100644 examples/filter/filter.ino create mode 100644 keywords.txt create mode 100644 library.properties create mode 100644 src/AUnit.h create mode 100644 src/aunit/Assertion.h create mode 100644 src/aunit/Compare.cpp create mode 100644 src/aunit/Compare.h create mode 100644 src/aunit/FCString.h create mode 100644 src/aunit/Printer.cpp create mode 100644 src/aunit/Printer.h create mode 100644 src/aunit/Test.cpp create mode 100644 src/aunit/Test.h create mode 100644 src/aunit/TestRunner.cpp create mode 100644 src/aunit/TestRunner.h create mode 100644 src/aunit/Verbosity.h diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..713eedf --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,4 @@ +# Changelog + +* 0.1.0 (2018-03-12) + * Initial upload to GitHub. diff --git a/README.md b/README.md index c82018a..1a03251 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,540 @@ -# AUnit -Unit testing framework for Arduino platforms inspired by ArduinoUnit +# AUnit - Unit Testing Framework for Arduino Platforms + +## Summary + +**AUnit** (rhymes with "JUnit") is a unit testing framework based +on the venerable [ArduinoUnit](https://github.com/mmurdoch/arduinounit) +framework. It is almost a drop-in replacement for the API implemented by +ArduinoUnit version 2.2. Just like ArduinoUnit, the unit tests run directly on +the microcontrollers themselves, not on emulators or simulators. + +AUnit was created to solve 2 problems with ArduinoUnit: +* ArduinoUnit consumes too much flash memory on an AVR platform (e.g. + Arduino UNO, Nano) as explained in + [issue #70](https://github.com/mmurdoch/arduinounit/issues/70). +* ArduinoUnit does not compile on the ESP8266 platform (see + [issue #68](https://github.com/mmurdoch/arduinounit/issues/68), + [issue #57](https://github.com/mmurdoch/arduinounit/pull/57), and + [issue #55](https://github.com/mmurdoch/arduinounit/issues/55)). + + +In contrast: +* AUnit consumes as much as 66% *less* flash memory than ArduinoUnit on the + AVR platform. On Teensy-ARM, the savings can be as much as 30%. +* AUnit has been tested on AVR, Teensy-ARM and ESP8266. + +For basic unit tests written using ArduinoUnit, only two changes are required to +convert to AUnit: +* `#include ` -> `#include ` +* `Test::run()` -> `aunit::TestRunner::run()` + +Most of the frequently used macros are compatible between ArduinoUnit and AUnit: +* `test()` +* `testing()` +* `assertXxx()` + +AUnit supports a exclude and include filters: +* `TestRunner::exclude()` +* `TestRunner::include()` + +Currently, only a single `*` wildcard is supported and it must occur at the end +if present. + +## Usage + +### Header and Namespace + +To prevent name clashes with other libraries and code, all classes in the AUnit +library are defined in the `aunit` namespace. The user will normally interact +with only the `TestRunner` class. It can be reference with an explicit namespace +qualifier (i.e. `aunit::TestRunner`), or we can use a `using` directive like +this: + +``` +#include +using aunit::TestRunner; +``` +or we can import the entire `aunit` namespace: +``` +#include +using namespace aunit; +``` + +Similar to ArduinoUnit, many of the "functions" in this framework (e.g. +`test()`, `testing()`, `assertXxx()`) are defined as `#define` macros which live +in the global namespace, so it is usually not necessary to import the entire +`aunit` namespace. + +### Defining the Tests + +The usage of **AUnit** is almost identical to **ArduinoUnit**. A test case is +defined by a fragment of code inside the `{ }` just after the `test()` or +`testing()` macros. The argument to these macros are the name of the test case. +(The name is available within the test code through the `const +__FlashStringHelper* getName()` method). It is used to generate a class whose +parents are `aunit::TestOnce` and `aunit::Test` respectively. + +The macros also generate code to create an instance of the subclass. +The code following after the `test()` and `testing()` macros becomes +the body of the virtual `TestOnce::once()` and `Test::loop` methods +(respectively). + +When the instance of the test case is statically initialized, it adds itself to +a linked list. The root of that singly linked list is given by +`Test::getRoot()`. The `TestRunner::run()` method traverses the linked list, +executing each test case until it passes, fails or is skipped. + +Here is an outline of what an AUnit unit test sketch: + +``` +#line 2 AUnitTest.ino + +#include + +using aunit::TestRunner; + +test(example_test) { + ... code ... + assertEqual(a, b); +} + +testing(looping_test) { + ... code ... + if (...) { + pass(); + } else if (...) { + fail(); + } else { + skip(); + } +} + +void setup() { + Serial.begin(74880); // 74880 is the default for some ESP8266 boards + while (! Serial); // Wait until Serial is ready - Leonardo + + TestRunner::exclude("*"); + TestRunner::include("looping*"); +} + +void loop() { + TestRunner::run(); +} +``` + +### Supported Macros + +These are identical to ArduinoUnit: + +* `test()` +* `testing()` + +### Supported Assertions + +Inside the `test()` and `testing()` macros, the following assertions +are available. These are essentially identical to ArduinoUnit: + +* `assertEqual(a, b)` +* `assertNotEqual(a, b)` +* `assertLess(a, b)` +* `assertMore(a, b)` +* `assertLessOrEqual(a, b)` +* `assertMoreOrEqual(a, b)` + +The type inference logic of two `(a, b)` arguments in the `assertXxx(a, b)` is +slightly different than ArduinoUnit. For non-String types, `a` and `b` must have +the same type, after the usual implicit type converisons. For example, the +following implicit conversions will occur: +* `signed char` -> `int` +* `unsigned char` -> `int` +* `short` -> `int` +* `unsigned short` -> `int` or `unsigned int` (depending on `sizeof(int)`) +* `char*` -> `const char*`. +* `char[N]` -> `const char*` +* `float` -> `double` + +(Note that `char`, `signed char`, and `unsigned char` are 3 distinct types in +C++.) + +Mixing the parameter types will often produce compiler errors. See comments +and solutions in the "Migration from ArduinoUnit to AUnit" section below. + +For the 3 string types (`char*`, `String`, and `__FlashStringHelper*`), +all 9 combinatorial mixes are supported. + +In summary, the following types of `(a, b)` are defined for the various +`assertXxx()` macros: + +* `(bool, bool)` +* `(char, char)` +* `(int, int)` +* `(unsigned int, unsigned int)` +* `(long, long)` +* `(unsigned long, unsigned long)` +* `(double, double)` +* `(const char *, const char *)` +* `(const char *, const String&)` +* `(const char *, const __FlashStringHelper*)` +* `(const String&, const char*)` +* `(const String&, const String&)` +* `(const String&, const __FlashStringHelper*)` +* `(const __FlashStringHelper*, const char*)` +* `(const __FlashStringHelper*, const __FlashStringHelper*)` +* `(const __FlashStringHelper*, const String&)` + +The following boolean asserts are also available, just like ArduinoUnit: + +* `assertTrue(condition)` +* `assertFalse(condition)` + +### Supported Methods in a Test Case + +These are identical to the equivalent methods in ArduinoUnit, and are available +in a `test()` or `testing()` macro: + +* `pass()` +* `fail()` +* `skip()` + +### Supported Methods on Test Class + +These are identical to ArduinoUnit: + +* `setup()` +* `loop()` +* `once()` + +### Running the Tests + +Similar to ArduinioUnit, we run the test cases in the global `loop()` method by +calling `TestRunner::run()`. Each call to the `run()` method causes one test +case to run and be resolved. The next call to `run()` executes the next test +case. This design allows the `loop()` method to perform a small amount of work +and return periodically to allow the system to perform some actions. On some +systems, such as the ESP8266, an error is generated if `loop()` takes too much +CPU time. + +``` +... +void loop() { + TestRunner::run(); +} +``` + +### Excluding and Including Test Cases + +We can `exclude()` or `include()` test cases using a pattern match, +just like ArduinoUnit. The names are slightly different: + +* `TestRunner::exclude()` (equivalent to `Test::exclude()` +* `TestRunner::include()` (equivalent to `Test::include()` + +These methods are called from the global `setup()` method: + +``` +void setup() { + TestRunner::exclude("*"); + TestRunner::include("looping*"); + ... +} +``` + +The `TestRunner::exclude()` and `TestRunner::include()` methods in AUnit +are not as powerful as the ones provided by ArduinoUnit. AUnit supports only a +single wildcard character `*` and that character can appear only at the end if +it is present. For example, the following are accepted: + +* `TestRunner::exclude("*");` +* `TestRunner::exclude("f*");` +* `TestRunner::include("flash_*");` +* `TestRunner::exclude("looping*");` +* `TestRunner::include("flashTest");` + +### Output Printer + +The default output printer is the `Serial` instance. This can be +changed using the `TestRunner::setPrinter()` method: + +``` +#include +using aunit::TestRunner; +... + +void setup() { + Serial1.begin(...); + + TestRunner::setPrinter(&Serial1); + ... +} + +void loop() { + TestRunner::run(); +} +``` + +This is the equivalent of the `Test::out` static member variable in +ArduinoUnit. In AUnit, the member variable is not exposed, it is accessed and +changed throught the `TestRunner::setPrinter()` and `TestRunner::getPrinter()` +methods. + +### Controlling the Verbosity + +The verbosity of the test results can be controlled using the the +`setVerbosity()` method: +``` +#include +using aunit::TestRunner; +using aunit::Verbosity; +... +void setup() { + ... + TestRunner::setVerbosity(Verbosity::kAll); + ... +} +``` + +The verbosity rules are more primitive (and simpler) than ArduinoUnit The each +flag below is a bit field that controls whether certain messages are enabled +or disabled. There is no concept of a "minimum" or "maximum" verbosity. Also, +the verbosity of an individual test case cannot be independently controlled, the +`TestRunner` verbosity setting applies to all tests. + +The names of the bit field flags are different from ArduinoUnit to avoid name +collisions with other `#define` macros which have global scope. AUnit uses +static constants of the `Verbosity` utility class: + +* `Verbosity::kTestRunSummary` +* `Verbosity::kTestFailed` +* `Verbosity::kTestPassed` +* `Verbosity::kTestSkipped` +* `Verbosity::kTestAll` +* `Verbosity::kAssertionFailed` +* `Verbosity::kAssertionPassed` +* `Verbosity::kAssertionAll` +* `Verbosity::kDefault`, equivalent to setting the following + * `Verbosity::kAssertionFailed` + * `Verbosity::kTestFailed` + * `Verbosity::kTestPassed` + * `Verbosity::kTestSkipped` + * `Verbosity::kTestRunSummary` +* `Verbosity::kAll` - enables all messages +* `Verbosity::kNone` - disables all messages + +### Line Number Mismatch + +AUnit suffers from the same compiler/preprocessor bug as ArduinoUnit that causes +the built-in `__LINE__` macro to be off by one. The solution is to add: +``` +#line 2 {file.ino} +``` +as the first line of a unit test sketch. + +## Migrating from ArduinoUnit to AUnit + +### Header + +Use +``` +#include +``` +instead of +``` +#include +``` + +### Test Runner + +The `Test::run()` method has been moved to a new `TestRunner` class: + +* `Test::run()` -> `TestRunner::run()` + +### Printer + +The `Test::out` static variable can be set using the static method on +`TestRunner`: + +* `Test::out = &Serial1` -> `TestRunner::setPrinter(&Serial1)` + +(It can be accessed through `aunit::Printer::getPrinter()` but I don't +expect this to be needed often.) + +### Verbosity + +The following ArduinoUnit variables do not exist: +* `Test::verbosity` +* `Test::min_verbosity` +* `Test::max_verbosity` + +Verbosity can be set only at the `TestRunner` level. Verbosity cannot be set at +the test case (i.e. `Test` or `TestOnce` class) level individually. There is no +"min" or "max" verbosity level. Each type of message is controlled by a bit +flag. The bit flag can be set using `TestRunner::setVerbosity()`. The bit field +constants have slightly different names: + +* `TEST_VERBOSITY_TESTS_SUMMARY` -> `Verbosity::kTestRunSummary` +* `TEST_VERBOSITY_TESTS_FAILED` -> `Verbosity::kTestFailed` +* `TEST_VERBOSITY_TESTS_PASSED` -> `Verbosity::kTestPassed` +* `TEST_VERBOSITY_TESTS_SKIPPED` -> `Verbosity::kTestPassed` +* `TEST_VERBOSITY_TESTS_ALL` -> `Verbosity::kTestAll` +* `TEST_VERBOSITY_ASSERTIONS_FAILED` -> `Verbosity::kAssertionFailed` +* `TEST_VERBOSITY_ASSERTIONS_PASSED` -> `Verbosity::kAssertionPassed` +* `TEST_VERBOSITY_ASSERTIONS_ALL` -> `Verbosity::kAssertionAll` +* `TEST_VERBOSITY_ALL` -> `Verbosity::kAll` +* `TEST_VERBOSITY_NONE` -> `Verbosity::kNone` +* {no equivalent} <- `Verbosity::kDefault` + +### Missing ArduinoUnit Features + +The following methods from ArduinoUnit are not yet implemented: + +* `checkTestDone(name)` +* `checkTestNotDone(name)` +* `checkTestPass(name)` +* `checkTestNotPass(name)` +* `checkTestFail(name)` +* `checkTestNotFail(name)` +* `checkTestSkip(name)` +* `checkTestNotSkip(name)` +* `assertTestDone(name)` +* `assertTestNotDone(name)` +* `assertTestPass(name)` +* `assertTestNotPass(name)` +* `assertTestFail(name)` +* `assertTestNotFail(name)` +* `assertTestSkip(name)` +* `assertTestNotSkip(name)` + +### Assertion Parameters Omitted in Messages + +The various `assertXxx()` macros in AUnit print a slightly shorter +message upon pass or fail. For example, if the assertion was: +``` +int expected = 3; +int counter = 4; +assertEquals(expected, counter); +``` + +ArduinoUnit captures the arguments of the `assertEqual()` macro +and prints: + +``` +Assertion failed: (expected=3) == (counter=4), file AUnitTest.ino, line 134. +``` + +AUnit omits the parameters to reduce flash memory space by about 33%: + +``` +Assertion failed: (3) == (4), file AUnitTest.ino, line 134. +``` + +### Assertion Parameters Must Match Types + +In ArduinoUnit, the `assertXxx()` macros could be slightly different types, for +example: +``` +unsigned int uintValue = 5; +assertEqual(5, uintValue); +``` + +If the compiler warnings are enabled, a warning from the compiler is printed: + +``` +../ArduinoUnit/src/ArduinoUnitUtility/Compare.h:17:28: warning: + comparison between signed and unsigned integer expressions [-Wsign-compare] + return (!(a, unsigned int&)' is ambiguous + if (!aunit::assertion(__FILE__,__LINE__,(arg1),opName,op,(arg2)))\ +... +``` +The compiler cannot find an appropriate overloaded version of `assertEqual()`. + +The solution is to make the parameters the same type: +``` +assertEqual(5U, uintValue); +``` + +On the AVR platform, both a (short) and (int) are 16-bit types, so the following +will produce a compiler error: +``` +unsigned short ushortValue = 5; +assertEqual(5U, ushortValue); +``` +But on Teensy-ARM and ESP8266, a 16-bit (short) can be promoted to a 32-bit +(int) without loss of precision, so the above will compile just fine. For +portability, the following should be used on all platforms: +``` +unsigned short ushortValue = 5; +assertEqual((unsigned short) 5, ushortValue); +``` + +The integer type promotion rules and function overload matching rules can be +difficult to remember (and sometimes difficult to understand). The best way to +avoid these compiler errors is to make sure that the assertion parameter types +are identical, potentially using explicit casting. + +## Benchmarks + +AUnit consumes as much as 66% less flash memory than ArduinoUnit on an AVR +platform (e.g. Arduino UNO, Nano), and 30% less flash on the Teensy-ARM platform +(e.g. Teensy LC ). Here are the resource consumption (flash and static) numbers +from an actual unit test sketch containing 26 test cases using 331 `assertXxx()` +statements, compiled using AUnit and ArduinoUnit on 4 different +microcontrollers: +``` +Platform (resource) | Max | ArduinoUnit | AUnit | +----------------------+---------+-------------+-------------| +Arduino Nano (flash) | 30720 | 54038 | 18624 | +Arduino Nano (static) | 2048 | 1061 | 908 | +----------------------+---------+-------------+-------------| +Teensy LC (flash) | 63488 | 36196 | 25120 | +Teensy LC (static) | 8192 | 2980 | 2768 | +----------------------+---------+-------------+-------------| +Teensy 3.2 (flash) | 262144 | 51236 | 36136 | +Teensy 3.2 (static) | 65536 | 5328 | 5224 | +----------------------+---------+-------------+-------------| +ESP8266 (flash) | 1044464 | does not | 267391 | +ESP8266 (static) | 81920 | compile | 34564 | +----------------------+---------+-------------+-------------| +``` + +Not all unit test sketches will experience a savings of 66% of flash memory with +AUnit, but a savings of 30-50% seems to be common. + +## Changelog + +See [CHANGELOG.md](CHANGELOG.md). + +## System Requirements + +This library was developed and tested using: +* [Arduino IDE 1.8.5](https://www.arduino.cc/en/Main/Software) +* [Teensyduino 1.41](https://www.pjrc.com/teensy/td_download.html) +* [ESP8266 Arduino Core 2.4.0](https://arduino-esp8266.readthedocs.io/en/2.4.0-rc2/) + +I used MacOS 10.13.3 for most of my development. + +The library has been verified to work on the following hardware: + +* Arduino Nano clone (16 MHz ATmega328P) +* Arduino UNO R3 clone (16 MHz ATmega328P) +* Teensy LC (48 MHz ARM Cortex-M0+) +* Teensy 3.2 (72 MHz ARM Cortex-M4) +* NodeMCU 1.0 clone (ESP-12E module, 80MHz ESP8266) + +## License + +[MIT License](https://opensource.org/licenses/MIT) + +## Authors + +* Created by Brian T. Park (brian@xparks.net). +* The design and syntax of many macros (e.g. `test()`, `assertXxx()`) were + borrowed from the [ArduinoUnit](https://github.com/mmurdoch/arduinounit) + project to allow AUnit to be almost a drop-in replacement. Many thanks to + the ArduinoUnit team for creating such an easy-to-use API. diff --git a/examples/AUnitTest/AUnitTest.ino b/examples/AUnitTest/AUnitTest.ino new file mode 100644 index 0000000..b578873 --- /dev/null +++ b/examples/AUnitTest/AUnitTest.ino @@ -0,0 +1,314 @@ +#line 2 "AUnitTest.ino" +#include + +// If ArduinoUnit is used, this unit test no longer fits in a 32kB +// Arduino UNO or Nano. +#define USE_AUNIT 1 + +#if USE_AUNIT == 1 +#include +using namespace aunit; +#else +#include +#endif + +#ifndef FPSTR +#define FPSTR(pstr_pointer) (reinterpret_cast(pstr_pointer)) +#endif + +signed char sc = 4; +signed char sd = 5; +unsigned char usc = 4; +unsigned char usd = 5; +char c = 'a'; +char d = 'b'; +const char a[] = "a"; +const char b[] = "b"; +char aa[] = "a"; +char bb[] = "b"; +String s = "a"; +String t = "b"; +const char F_PROGMEM[] PROGMEM = "a"; +const char G_PROGMEM[] PROGMEM = "b"; +const __FlashStringHelper* f = FPSTR(F_PROGMEM); +const __FlashStringHelper* g = FPSTR(G_PROGMEM); +const char FF_PROGMEM[] PROGMEM = "abcde"; +const char GG_PROGMEM[] PROGMEM = "abcdef"; +const char HH_PROGMEM[] PROGMEM = "abcdeg"; +const __FlashStringHelper* ff = FPSTR(FF_PROGMEM); +const __FlashStringHelper* gg = FPSTR(GG_PROGMEM); +const __FlashStringHelper* hh = FPSTR(HH_PROGMEM); + +test(type_mismatch) { + unsigned short ushortValue = 5; + unsigned int uintValue = 5; + unsigned long ulongValue = 5; + + // Works because both (signed char) and (unsigned char) are promoted to (int) + assertEqual(sc, usc); + + // compiler error on AVR because both (short) and (int) are 16-bits + //assertEqual(5, ushortValue); + + // compiler error on all platforms + //assertEqual(5U, ushortValue); + + // works on all platforms + assertEqual((unsigned short) 5, ushortValue); + + // compiler error on all platforms + //assertEqual(5, uintValue); + + // works on all platforms + assertEqual(5U, uintValue); + + // compiler error on all platforms + //assertEqual(5, ulongValue); + + // compiler error on all platforms + //assertEqual(5U, ulongValue); + + // works on all platforms + assertEqual(5UL, ulongValue); +} + +test(assertEqual) { + assertEqual(true, true); + assertEqual(c, c); + assertEqual(sc, sc); + assertEqual(usc, usc); + assertEqual((short)4, (short)4); + assertEqual((unsigned short)4, (unsigned short)4); + assertEqual(4, 4); + assertEqual(4U, 4U); + assertEqual(4L, 4L); + assertEqual(4UL, 4UL); + assertEqual(4.0f, 4.0f); + assertEqual(4.0, 4.0); + assertEqual(a, a); + assertEqual(aa, aa); + assertEqual(f, f); + assertEqual(s, s); + assertEqual(a, s); + assertEqual(s, a); + assertEqual(a, f); + assertEqual(f, a); + assertEqual(f, s); + assertEqual(s, f); +} + +test(assertLess) { + assertLess(false, true); + assertLess(c, d); + assertLess(sc, sd); + assertLess(usc, usd); + assertLess((short)3, (short)4); + assertLess((unsigned short)3, (unsigned short)4); + assertLess(3, 4); + assertLess(3U, 4U); + assertLess(3L, 4L); + assertLess(3UL, 4UL); + assertLess(3.0f, 4.0f); + assertLess(3.0, 4.0); + assertLess(a, b); + assertLess(aa, bb); + assertLess(f, g); + assertLess(s, t); + assertLess(a, t); + assertLess(s, b); + assertLess(a, g); + assertLess(f, b); + assertLess(f, t); + assertLess(s, g); +} + +test(assertMore) { + assertMore(true, false); + assertMore(d, c); + assertMore(sd, sc); + assertMore(usd, usc); + assertMore((uint8_t)4, (uint8_t)3); + assertMore((short)4, (short)3); + assertMore(4, 3); + assertMore(4U, 3U); + assertMore(4L, 3L); + assertMore(4UL, 3UL); + assertMore(4.0f, 3.0f); + assertMore(4.0, 3.0); + assertMore(b, a); + assertMore(bb, aa); + assertMore(g, f); + assertMore(t, s); + assertMore(t, a); + assertMore(b, s); + assertMore(g, a); + assertMore(b, f); + assertMore(t, f); + assertMore(g, s); +} + +test(assertLessOrEqual) { + assertLessOrEqual(false, true); + assertLessOrEqual(c, d); + assertLessOrEqual(sc, sd); + assertLessOrEqual(usc, usd); + assertLessOrEqual((short)3, (short)4); + assertLessOrEqual((unsigned short)3, (unsigned short)4); + assertLessOrEqual(3, 4); + assertLessOrEqual(3U, 4U); + assertLessOrEqual(3L, 4L); + assertLessOrEqual(3UL, 4UL); + assertLessOrEqual(4.0f, 4.0f); + assertLessOrEqual(4.0, 4.0); + assertLessOrEqual(a, b); + assertLessOrEqual(aa, bb); + assertLessOrEqual(f, g); + assertLessOrEqual(s, t); + assertLessOrEqual(a, t); + assertLessOrEqual(s, b); + assertLessOrEqual(a, g); + assertLessOrEqual(f, b); + assertLessOrEqual(f, t); + assertLessOrEqual(s, g); +} + +test(assertMoreOrEqual) { + assertMoreOrEqual(true, false); + assertMoreOrEqual(d, c); + assertMoreOrEqual(sd, sc); + assertMoreOrEqual(usd, usc); + assertMoreOrEqual((short)4, (short)3); + assertMoreOrEqual((unsigned short)4, (unsigned short)3); + assertMoreOrEqual(4, 3); + assertMoreOrEqual(4U, 3U); + assertMoreOrEqual(4L, 3L); + assertMoreOrEqual(4UL, 3UL); + assertMoreOrEqual(4.0f, 4.0f); + assertMoreOrEqual(4.0, 4.0); + assertMoreOrEqual(b, a); + assertMoreOrEqual(bb, aa); + assertMoreOrEqual(g, f); + assertMoreOrEqual(t, s); + assertMoreOrEqual(t, a); + assertMoreOrEqual(b, s); + assertMoreOrEqual(g, a); + assertMoreOrEqual(b, f); + assertMoreOrEqual(t, f); + assertMoreOrEqual(g, s); +} + +test(assertNotEqual) { + assertNotEqual(true, false); + assertNotEqual(d, c); + assertNotEqual(sc, sd); + assertNotEqual(usc, usd); + assertNotEqual((short)4, (short)3); + assertNotEqual((unsigned short)4, (unsigned short)3); + assertNotEqual(4, 3); + assertNotEqual(4U, 3U); + assertNotEqual(4L, 3L); + assertNotEqual(4UL, 3UL); + assertNotEqual(4.0f, 3.0f); + assertNotEqual(4.0, 3.0); + assertNotEqual(b, a); + assertNotEqual(bb, aa); + assertNotEqual(g, f); + assertNotEqual(t, s); + assertNotEqual(t, a); + assertNotEqual(b, s); + assertNotEqual(g, a); + assertNotEqual(b, f); + assertNotEqual(t, f); + assertNotEqual(g, s); +} + +test(assertTrue) { + assertTrue(true); + assertFalse(false); +} + +test(flashString) { + assertEqual(ff, ff); + assertEqual(gg, gg); + assertEqual(hh, hh); + assertNotEqual(ff, gg); + assertNotEqual(ff, hh); + assertNotEqual(gg, hh); + assertLess(ff, gg); + assertLess(ff, hh); + assertLess(gg, hh); + assertMore(gg, ff); + assertMore(hh, ff); + assertMore(hh, gg); + assertLessOrEqual(ff, gg); + assertLessOrEqual(ff, hh); + assertLessOrEqual(gg, hh); + assertMoreOrEqual(gg, ff); + assertMoreOrEqual(hh, ff); + assertMoreOrEqual(hh, gg); +} + +#if USE_AUNIT == 1 +test(compareStringN) { + assertEqual(0, compareStringN(ff, "abcde", 5)); + assertEqual(1, compareStringN(ff, "abcd", 5)); + assertEqual(0, compareStringN(ff, "abcd", 4)); + assertEqual(1, compareStringN(ff, "", 1)); + assertEqual(0, compareStringN(ff, "", 0)); + + assertEqual(0, compareStringN(gg, ff, 5)); + assertEqual(1, compareStringN(gg, ff, 6)); +} +#endif + +testing(looping_skip) { + static int count = 0; + count++; + Serial.print(F("looping_skip: iteration ")); + Serial.println(count); + if (count >= 3) { + skip(); + } +} + +testing(looping_fail) { + static int count = 0; + count++; + Serial.print(F("looping_fail: iteration ")); + Serial.println(count); + if (count >= 3) { + fail(); + } +} + +testing(looping_pass) { + static int count = 0; + count++; + Serial.print(F("looping_pass: iteration ")); + Serial.println(count); + if (count >= 3) { + pass(); + } +} + +void setup() { + Serial.begin(74880); // 74880 is the default for some ESP8266 boards + while (! Serial); // Wait until Serial is ready - Leonardo + +#if USE_AUNIT == 1 + TestRunner::setVerbosity(Verbosity::kAll); + TestRunner::exclude("looping_f*"); + TestRunner::list(); +#else + Test::min_verbosity = TEST_VERBOSITY_ALL; + Test::exclude("looping_f*"); +#endif +} + +void loop() { +#if USE_AUNIT == 1 + TestRunner::run(); +#else + Test::run(); +#endif +} diff --git a/examples/advanced/advanced.ino b/examples/advanced/advanced.ino new file mode 100644 index 0000000..97266a3 --- /dev/null +++ b/examples/advanced/advanced.ino @@ -0,0 +1,110 @@ +#line 2 "advanced.ino" + +// Copied from: +// https://github.com/mmurdoch/arduinounit/blob/master/examples/advanced/advanced.ino + +#define USE_AUNIT 1 + +#if USE_AUNIT == 1 +#include +using namespace aunit; +#else +#include +#endif + +test(simple1) +{ + assertTrue(true); +} + +test(simple2) +{ + assertTrue(false); +} + +class MyTestOnce : public TestOnce +{ +public: + // constructor must name test + + MyTestOnce(const char *name) + : TestOnce(name) + { + // lightweight constructor, since + // this test might be skipped. + // you can adjust verbosity here + +#if USE_AUNIT == 0 + verbosity = TEST_VERBOSITY_ALL; +#endif + } + + int n; + + void setup() + { + n = random(6); + if (n == 0) skip(); + } + + void once() + { + for (int i=-n; i<=n; ++i) { + for (int j=-n; j<=n; ++j) { + assertEqual(i+j,j+i); + } + } + } +}; + +MyTestOnce myTestOnce1("myTestOnce1"); +MyTestOnce myTestOnce2("myTestOnce2"); +MyTestOnce myTestOnce3("myTestOnce3"); + +class MyTest : public Test +{ +public: + uint16_t when; + MyTest(const char *name) + : Test(name) + { + when = random(100,200); + } + + void loop() + { + if (millis() >= when) + { + assertLess(random(100),50L); + pass(); // if assertion is ok + } + } +}; + +MyTest myTest1("myTest1"); +MyTest myTest2("myTest2"); +MyTest myTest3("myTest3"); + +void setup() +{ + Serial.begin(74880); // 74880 is default for some ESP8266 boards + while(!Serial); // for the Arduino Leonardo/Micro only + +#if USE_AUNIT == 1 + TestRunner::setVerbosity(Verbosity::kAll); + TestRunner::exclude("myTestOnce2"); + TestRunner::exclude("myTest2"); +#else + Test::min_verbosity |= TEST_VERBOSITY_ASSERTIONS_ALL; + Test::exclude("my*2"); +#endif +} + +void loop() +{ +#if USE_AUNIT == 1 + TestRunner::run(); +#else + Test::run(); +#endif +} diff --git a/examples/basic/basic.ino b/examples/basic/basic.ino new file mode 100644 index 0000000..839c080 --- /dev/null +++ b/examples/basic/basic.ino @@ -0,0 +1,39 @@ +#line 2 "basic.ino" + +// Copied from: +// https://github.com/mmurdoch/arduinounit/blob/master/examples/basic/basic.ino + +#define USE_AUNIT 1 + +#if USE_AUNIT == 1 +#include +#else +#include +#endif + +test(correct) +{ + int x=1; + assertEqual(x,1); +} + +test(incorrect) +{ + int x=1; + assertNotEqual(x,1); +} + +void setup() +{ + Serial.begin(74880); // 74880 is default for some ESP8266 boards + while(!Serial); // for the Arduino Leonardo/Micro only +} + +void loop() +{ +#if USE_AUNIT == 1 + aunit::TestRunner::run(); +#else + Test::run(); +#endif +} diff --git a/examples/continuous/continuous.ino b/examples/continuous/continuous.ino new file mode 100644 index 0000000..f06f9eb --- /dev/null +++ b/examples/continuous/continuous.ino @@ -0,0 +1,42 @@ +#line 2 "continuous.ino" + +// Copied from: +// https://github.com/mmurdoch/arduinounit/blob/master/examples/continuous/continuous.ino + +#define USE_AUNIT 1 + +#if USE_AUNIT == 1 +#include +#else +#include +#endif + +// test-once test named "once" +test(once) +{ + int x=1; + assertLessOrEqual(x,1); +} + +// test-until-skip-pass-or-fail test named "continuous" +testing(continuous) +{ + unsigned long t = millis(); + assertLessOrEqual(t,100UL); + if (t >= 100) pass(); +} + +void setup() +{ + Serial.begin(74880); // 74880 is default for some ESP8266 boards + while(!Serial); // for the Arduino Leonardo/Micro only +} + +void loop() +{ +#if USE_AUNIT == 1 + aunit::TestRunner::run(); +#else + Test::run(); +#endif +} diff --git a/examples/filter/filter.ino b/examples/filter/filter.ino new file mode 100644 index 0000000..24c633e --- /dev/null +++ b/examples/filter/filter.ino @@ -0,0 +1,47 @@ +#line 2 "filter.ino" + +// Copied from: +// https://github.com/mmurdoch/arduinounit/blob/master/examples/filter/filter.ino + +#define USE_AUNIT 1 + +#if USE_AUNIT == 1 +#include +using aunit::TestRunner; +#else +#include +#endif + +// empty tests just to play with +test(net_tcp) { pass(); } +testing(net_udp) { pass(); } +test(net_ftp) { pass(); } +test(crypto_aes) { pass(); } +testing(crypto_rng) { pass(); } +test(crypto_sha256) { pass(); } + +void setup() +{ + Serial.begin(74880); // 74880 is default for some ESP8266 boards + while(!Serial); // for the Arduino Leonardo/Micro only + + // all tests named net_ - something, except net_ftp +#if USE_AUNIT == 1 + TestRunner::exclude("*"); + TestRunner::include("net_*"); + TestRunner::exclude("net_ftp"); +#else + Test::exclude("*"); + Test::include("net_*"); + Test::exclude("net_ftp"); +#endif +} + +void loop() +{ +#if USE_AUNIT == 1 + TestRunner::run(); +#else + Test::run(); +#endif +} diff --git a/keywords.txt b/keywords.txt new file mode 100644 index 0000000..89146b0 --- /dev/null +++ b/keywords.txt @@ -0,0 +1,89 @@ +####################################### +# Syntax Coloring Map for AUnit library +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +Test KEYWORD1 +TestOnce KEYWORD1 +test KEYWORD1 +testing KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +# Public methods from TestRunner.h +run KEYWORD2 +list KEYWORD2 +exclude KEYWORD2 +include KEYWORD2 +setVerbosity KEYWORD2 +isVerbosity KEYWORD2 +setPrinter KEYWORD2 + +# Public methods from Test.h +getRoot KEYWORD2 +setup KEYWORD2 +loop KEYWORD2 +getName KEYWORD2 +getStatus KEYWORD2 +setStatus KEYWORD2 +setPassOrFail KEYWORD2 +getNext KEYWORD2 +# +# Protected methods from Test.h +skip KEYWORD2 +pass KEYWORD2 +fail KEYWORD2 +# TestOnce +once KEYWORD2 + +# Macros from Assertion.h +assertEqual KEYWORD2 +assertNotEqual KEYWORD2 +assertLess KEYWORD2 +assertLessOrEqual KEYWORD2 +assertMore KEYWORD2 +assertMoreOrEqual KEYWORD2 +assertTrue KEYWORD2 +assertFalse KEYWORD2 + +# Overloaded functions from Compare.h +compareString KEYWORD2 +compareStringN KEYWORD2 +compareEqual KEYWORD2 +compareLess KEYWORD2 +compareLessOrEqual KEYWORD2 +compareMoreOrEqual KEYWORD2 +compareNotEqual KEYWORD2 + +####################################### +# Instances (KEYWORD2) +####################################### + +####################################### +# Constants (LITERAL1) +####################################### + +# public constants from Verbosity.h +kTestRunSummary LITERAL1 +kTestFailed LITERAL1 +kTestPassed LITERAL1 +kTestSkipped LITERAL1 +kTestAll LITERAL1 +kAssertionFailed LITERAL1 +kAssertionPassed LITERAL1 +kAssertionAll LITERAL1 +kDefault LITERAL1 +kAll LITERAL1 +kNone LITERAL1 + +# public constants from Test.h +kStatusNew LITERAL1 +kStatusSetup LITERAL1 +kStatusPassed LITERAL1 +kStatusFailed LITERAL1 +kStatusSkipped LITERAL1 diff --git a/library.properties b/library.properties new file mode 100644 index 0000000..7882ea1 --- /dev/null +++ b/library.properties @@ -0,0 +1,9 @@ +name=AUnit +version=0.1.0 +author=Brian T. Park +maintainer=Brian T. Park +sentence=A unit testing framework for Arduino platforms inspired by ArduinoUnit. +paragraph=This is almost a drop-in replacement of ArduinoUnit. It has 2 advantage over ArduinoUnit: First, it can reduce flash memory consumption by as much as 66% on the AVR platform. Second, it works with ESP8266, in addition to the AVR and Teensy platforms. +category=Other +url=https://github.com/bxparks/AUnit +architectures=* diff --git a/src/AUnit.h b/src/AUnit.h new file mode 100644 index 0000000..db9179c --- /dev/null +++ b/src/AUnit.h @@ -0,0 +1,14 @@ +#ifndef AUNIT_AUNIT_H +#define AUNIT_AUNIT_H + +#include "aunit/Verbosity.h" +#include "aunit/Compare.h" +#include "aunit/Printer.h" +#include "aunit/Test.h" +#include "aunit/TestRunner.h" +#include "aunit/Assertion.h" + +// Version string format: "010203" == "1.2.3" +#define AUNIT_VERSION "000100" + +#endif diff --git a/src/aunit/Assertion.h b/src/aunit/Assertion.h new file mode 100644 index 0000000..0bd6d28 --- /dev/null +++ b/src/aunit/Assertion.h @@ -0,0 +1,228 @@ +#ifndef AUNIT_ASSERTION_H +#define AUNIT_ASSERTION_H + +#include "Printer.h" +#include "Verbosity.h" +#include "TestRunner.h" + +// Various assertXxx() macros, implemented using the assertOp() macro. + +#define assertEqual(arg1,arg2) assertOp(arg1,aunit::compareEqual,"==",arg2) + +#define assertNotEqual(arg1,arg2) \ + assertOp(arg1,aunit::compareNotEqual,"!=",arg2) + +#define assertLess(arg1,arg2) assertOp(arg1,aunit::compareLess,"<",arg2) + +#define assertMore(arg1,arg2) assertOp(arg1,aunit::compareMore,">",arg2) + +#define assertLessOrEqual(arg1,arg2) \ + assertOp(arg1,aunit::compareLessOrEqual,"<=",arg2) + +#define assertMoreOrEqual(arg1,arg2) \ + assertOp(arg1,aunit::compareMoreOrEqual,">=",arg2) + +#define assertTrue(arg) assertEqual(arg,true) + +#define assertFalse(arg) assertEqual(arg,false) + +#define assertOp(arg1,op,opName,arg2) do {\ + if (!aunit::assertion(__FILE__,__LINE__,(arg1),opName,op,(arg2)))\ + return;\ +} while (false) + +namespace aunit { + +// This can be a template function because it is accessed only through the +// various assertXxx() methods. Those assertXxx() methods are explicitly +// overloaded for the various types that we want to support. +// +// Prints something like the following: +// Assertion failed: (5) == (6), file Test.ino line 820. +// Assertion passed: (6) == (6), file Test.ino line 820. +template +void printAssertionMessage(bool ok, const char* file, uint16_t line, + const A& lhs, const char *opName, const B& rhs) { + + bool isOutput = + (ok && TestRunner::isVerbosity(Verbosity::kAssertionPassed)) || + (!ok && TestRunner::isVerbosity(Verbosity::kAssertionFailed)); + if (!isOutput) return; + + // Don't use F() strings here because flash memory strings are not deduped by + // the compiler, so each template instantiation of this method causes a + // duplication of all the strings below. See + // https://github.com/mmurdoch/arduinounit/issues/70 + // for more info. + Printer::getPrinter()->print("Assertion "); + Printer::getPrinter()->print(ok ? "passed" : "failed"); + Printer::getPrinter()->print(": ("); + Printer::getPrinter()->print(lhs); + Printer::getPrinter()->print(") "); + Printer::getPrinter()->print(opName); + Printer::getPrinter()->print(" ("); + Printer::getPrinter()->print(rhs); + Printer::getPrinter()->print("), file "); + Printer::getPrinter()->print(file); + Printer::getPrinter()->print(", line "); + Printer::getPrinter()->print(line); + Printer::getPrinter()->println('.'); +} + +// For the same reason as the compareXxx() methods, we use explicit overloaded +// functions, instead of using template specialization. And just as before, I +// was unable to use a template function for primitive integer types, because it +// interfered with the resolution of assertion(char*, char*). The wrong function +// would be called. + +bool assertion(const char* file, uint16_t line, bool lhs, + const char* opName, bool (*op)(bool lhs, bool rhs), + bool rhs) { + bool ok = op(lhs, rhs); + printAssertionMessage(ok, file, line, lhs, opName, rhs); + TestRunner::setPassOrFail(ok); + return ok; +} + +bool assertion(const char* file, uint16_t line, char lhs, + const char* opName, bool (*op)(char lhs, char rhs), + char rhs) { + bool ok = op(lhs, rhs); + printAssertionMessage(ok, file, line, lhs, opName, rhs); + TestRunner::setPassOrFail(ok); + return ok; +} + +bool assertion(const char* file, uint16_t line, int lhs, + const char* opName, bool (*op)(int lhs, int rhs), + int rhs) { + bool ok = op(lhs, rhs); + printAssertionMessage(ok, file, line, lhs, opName, rhs); + TestRunner::setPassOrFail(ok); + return ok; +} + +bool assertion(const char* file, uint16_t line, unsigned int lhs, + const char* opName, bool (*op)(unsigned int lhs, unsigned int rhs), + unsigned int rhs) { + bool ok = op(lhs, rhs); + printAssertionMessage(ok, file, line, lhs, opName, rhs); + TestRunner::setPassOrFail(ok); + return ok; +} + +bool assertion(const char* file, uint16_t line, long lhs, + const char* opName, bool (*op)(long lhs, long rhs), + long rhs) { + bool ok = op(lhs, rhs); + printAssertionMessage(ok, file, line, lhs, opName, rhs); + TestRunner::setPassOrFail(ok); + return ok; +} + +bool assertion(const char* file, uint16_t line, unsigned long lhs, + const char* opName, bool (*op)(unsigned long lhs, unsigned long rhs), + unsigned long rhs) { + bool ok = op(lhs, rhs); + printAssertionMessage(ok, file, line, lhs, opName, rhs); + TestRunner::setPassOrFail(ok); + return ok; +} + +bool assertion(const char* file, uint16_t line, double lhs, + const char* opName, bool (*op)(double lhs, double rhs), + double rhs) { + bool ok = op(lhs, rhs); + printAssertionMessage(ok, file, line, lhs, opName, rhs); + TestRunner::setPassOrFail(ok); + return ok; +} + +bool assertion(const char* file, uint16_t line, const char* lhs, + const char* opName, bool (*op)(const char* lhs, const char* rhs), + const char* rhs) { + bool ok = op(lhs, rhs); + printAssertionMessage(ok, file, line, lhs, opName, rhs); + TestRunner::setPassOrFail(ok); + return ok; +} + +bool assertion(const char* file, uint16_t line, const char* lhs, + const char *opName, bool (*op)(const char* lhs, const String& rhs), + const String& rhs) { + bool ok = op(lhs, rhs); + printAssertionMessage(ok, file, line, lhs, opName, rhs); + TestRunner::setPassOrFail(ok); + return ok; +} + +bool assertion(const char* file, uint16_t line, const char* lhs, + const char *opName, + bool (*op)(const char* lhs, const __FlashStringHelper* rhs), + const __FlashStringHelper* rhs) { + bool ok = op(lhs, rhs); + printAssertionMessage(ok, file, line, lhs, opName, rhs); + TestRunner::setPassOrFail(ok); + return ok; +} + +bool assertion(const char* file, uint16_t line, const String& lhs, + const char *opName, bool (*op)(const String& lhs, const char* rhs), + const char* rhs) { + bool ok = op(lhs, rhs); + printAssertionMessage(ok, file, line, lhs, opName, rhs); + TestRunner::setPassOrFail(ok); + return ok; +} + +bool assertion(const char* file, uint16_t line, const String& lhs, + const char *opName, bool (*op)(const String& lhs, const String& rhs), + const String& rhs) { + bool ok = op(lhs, rhs); + printAssertionMessage(ok, file, line, lhs, opName, rhs); + TestRunner::setPassOrFail(ok); + return ok; +} + +bool assertion(const char* file, uint16_t line, const String& lhs, + const char *opName, + bool (*op)(const String& lhs, const __FlashStringHelper* rhs), + const __FlashStringHelper* rhs) { + bool ok = op(lhs, rhs); + printAssertionMessage(ok, file, line, lhs, opName, rhs); + TestRunner::setPassOrFail(ok); + return ok; +} + +bool assertion(const char* file, uint16_t line, const __FlashStringHelper* lhs, + const char *opName, + bool (*op)(const __FlashStringHelper* lhs, const char* rhs), + const char* rhs) { + bool ok = op(lhs, rhs); + printAssertionMessage(ok, file, line, lhs, opName, rhs); + TestRunner::setPassOrFail(ok); + return ok; +} + +bool assertion(const char* file, uint16_t line, const __FlashStringHelper* lhs, + const char *opName, + bool (*op)(const __FlashStringHelper* lhs, const String& rhs), + const String& rhs) { + bool ok = op(lhs, rhs); + printAssertionMessage(ok, file, line, lhs, opName, rhs); + TestRunner::setPassOrFail(ok); + return ok; +} + +bool assertion(const char* file, uint16_t line, const __FlashStringHelper* lhs, + const char *opName, + bool (*op)(const __FlashStringHelper* lhs, const __FlashStringHelper* rhs), + const __FlashStringHelper* rhs) { + bool ok = op(lhs, rhs); + printAssertionMessage(ok, file, line, lhs, opName, rhs); + TestRunner::setPassOrFail(ok); + return ok; +} + +} +#endif diff --git a/src/aunit/Compare.cpp b/src/aunit/Compare.cpp new file mode 100644 index 0000000..27cad1f --- /dev/null +++ b/src/aunit/Compare.cpp @@ -0,0 +1,110 @@ +#include +#include "Compare.h" +#include "FCString.h" + +namespace aunit { + +// Flash memory must be read using 4-byte alignment on the ESP8266. AVR doesn't +// care. Teensy-ARM fakes the flash memory API but really just uses the normal +// static RAM. The following code will work for all 3 environments. + +int compareString( + const __FlashStringHelper* a, const __FlashStringHelper* b) { + const char* aa = reinterpret_cast(a); + const char* bb = reinterpret_cast(b); + char bufa[4]; + char bufb[4]; + for (uint8_t i = 0; true; i++) { + if ((i & 0x03) == 0) { + memcpy_P(bufa, aa, 4); + memcpy_P(bufb, bb, 4); + aa += 4; + bb += 4; + } + char ca = bufa[i & 0x03]; + char cb = bufb[i & 0x03]; + if (ca < cb) return -1; + if (ca > cb) return 1; + // we hit this condition only if both strings were the same length + if (ca == '\0' || cb == '\0') return 0; + } + return 0; +} + +int compareString(const FCString& a, const FCString& b) { + if (a.getType() == FCString::kCStringType) { + if (b.getType() == FCString::kCStringType) { + return compareString(a.getCString(), b.getCString()); + } else { + return compareString(a.getCString(), b.getFString()); + } + } else { + if (b.getType() == FCString::kCStringType) { + return compareString(a.getFString(), b.getCString()); + } else { + return compareString(a.getFString(), b.getFString()); + } + } +} + +// Not really used anywhere right now, so the linker will +// remove it. But I think I might needit in the future. +int compareStringN(const __FlashStringHelper* a, + const __FlashStringHelper* b, uint16_t n) { + const char* aa = reinterpret_cast(a); + const char* bb = reinterpret_cast(b); + char bufa[4]; + char bufb[4]; + for (uint16_t i = 0; i < n; i++) { + if ((i & 0x03) == 0) { + memcpy_P(bufa, aa, 4); + memcpy_P(bufb, bb, 4); + aa += 4; + bb += 4; + } + char ca = bufa[i & 0x03]; + char cb = bufb[i & 0x03]; + if (ca < cb) return -1; + if (ca > cb) return 1; + // we hit this condition only if both strings were the same length + if (ca == '\0' || cb == '\0') return 0; + } + return 0; +} + +int compareStringN(const __FlashStringHelper* a, const char* b, uint16_t n) { + const char* aa = reinterpret_cast(a); + char bufa[4]; + for (uint16_t i = 0; i < n; i++) { + if ((i & 0x03) == 0) { + memcpy_P(bufa, aa, 4); + aa += 4; + } + char ca = bufa[i & 0x03]; + char cb = b[i]; + if (ca < cb) return -1; + if (ca > cb) return 1; + // we hit this condition only if both strings were the same length + if (ca == '\0' || cb == '\0') return 0; + } + return 0; +} + +int compareStringN(const FCString& a, const __FlashStringHelper* b, + uint16_t n) { + if (a.getType() == FCString::kCStringType) { + return compareStringN(a.getCString(), b, n); + } else { + return compareStringN(a.getFString(), b, n); + } +} + +int compareStringN(const FCString& a, const char* b, uint16_t n) { + if (a.getType() == FCString::kCStringType) { + return compareStringN(a.getCString(), b, n); + } else { + return compareStringN(a.getFString(), b, n); + } +} + +} diff --git a/src/aunit/Compare.h b/src/aunit/Compare.h new file mode 100644 index 0000000..78ecc34 --- /dev/null +++ b/src/aunit/Compare.h @@ -0,0 +1,577 @@ +#ifndef AUNIT_COMPARE_H +#define AUNIT_COMPARE_H + +#ifdef ESP8266 +#include +#else +#include +#endif + +#include +#include + +class FCString; + +/* + * This file provides overloaded compareXxx(a, b) functions which are used by + * the various assertXxx() macros. A primary goal of this file is to allow users + * to use the assertXxx() macros with all combinations of the 3 types of strings + * available in the Arduino platform: + * + * - (const char *) + * - (String&) + * - (const __FlashStringHelper*) + * + * Clearly, there are 9 binary combinations these string types. + * + * Template Specialization: + * ----------------------- + * One way to implement the compareEqual() for these types is to use template + * specialization. The problem with Template specialization is that templates + * use strict type matching, and does not perform the normal implicit type + * conversion, including const-casting. Therefore, all of the various c-style + * string types, for example: + * + * - char* + * - const char* + * - char[1] + * - char[N] + * - const char[1] + * - const char[N] + * + * are considered to be different types under the C++ templating system. This + * causes a combinatorial explosion of template specialization which produces + * code that is difficult to understand, test and maintain. + * An example can be seen in the Compare.h file of the ArduinoUnit project: + * https://github.com/mmurdoch/arduinounit/blob/master/src/ArduinoUnitUtility/Compare.h + * + * Function Overloading: + * --------------------- + * In this project, I used function overloading instead of template + * specialization. Functional overloading handles c-style strings (i.e. + * character arrays) naturally, in the way users expect. For example, (char*) is + * automarically cast to (const char*), and (char[N]) is autonmatically + * converted to (const char*). + * + * Since all the primitive value types (e.g. (char), (int), (unsigned char), + * etc.) can be implemented using the exact same code, I attempted to use a + * generic templatized version, using sonmething like: + * + * template + * compareEqual(const T& a, const T& b) { ... } + * + * However, this template introduced this method: + * + * compareEqual(char* const& a, char* const& b); + * + * that seemed to take precedence over the explicitly defined overload: + * + * compareEqual(const char* a, const char*b); + * + * When the compareEqual() method is called with a (char*) or a (char[N]): + * + * char a[3] = {...}; + * char b[4] = {...}; + * compareEqual(a, b); + * + * This calls compareEqual(char* const&, const* const&), which is the wrong + * version for a c-style string. The only way I could get this to work was to + * avoid templates completely and manually define all the function overloads + * even for primitive integer types. + * + * Implicit Conversions: + * --------------------- + * For basic primitive types, I depend on some casts to avoid having to define + * some functions. I assume that signed and unsigned intergers smaller or equal + * to (int) will be converted to an (int) to match compareEqual(int, int). + * + * I provided an explicit compareEqual(char, char) overload because in C++, a + * (char) type is distinct from (signed char) and (unsigned char). + * + * Technically, there should be a (long long) version and an (unsigned long + * long) version of compareEqual(). However, it turns out that the Arduino + * Print::print() method does not have an overload for these types, so it would + * not do us much good to provide an assertEqual() or compareEqual() for the + * (long long) and (unsigned long long) types. + * + * Custom Assert and Compare Functions: + * ------------------------------------ + * Another advantage of using function overloading instead of template + * specialization is that the user is able to add additional function overloads + * into the 'aunit' namespace. This should allow the user to define the various + * comporeXxx() and assertXxx() functions for a custom class. I have not + * tested this though. + */ +namespace aunit { + +// compareString() + +inline int compareString(const char* a, const char* b) { + return strcmp(a, b); +} + +inline int compareString(const char* a, const String& b) { + return strcmp(a, b.c_str()); +} + +inline int compareString(const char* a, const __FlashStringHelper* b) { + return strcmp_P(a, (const char*)b); +} + +inline int compareString(const String& a, const char* b) { + return strcmp(a.c_str(), b); +} + +inline int compareString(const String& a, const String& b) { + return a.compareTo(b); +} + +inline int compareString(const String& a, const __FlashStringHelper* b) { + return strcmp_P(a.c_str(), (const char*)b); +} + +inline int compareString(const __FlashStringHelper* a, const char* b) { + return -strcmp_P(b, (const char*) a); +} + +int compareString(const __FlashStringHelper* a, const __FlashStringHelper* b); + +inline int compareString(const __FlashStringHelper* a, const String& b) { + return -strcmp_P(b.c_str(), (const char*)a); +} + +int compareString(const FCString& a, const FCString& b); + +// compareStringN() +// These methods are used to implement the TestRunner::exclude() and +// TestRunner::include() features. + +/** Compare only the first n characters of 'b'. */ +int compareStringN(const __FlashStringHelper* a, const __FlashStringHelper* b, + uint16_t n); + +/** Compare only the first n characters of 'b'. */ +int compareStringN(const __FlashStringHelper* a, const char* b, uint16_t n); + +/** Compare only the first n characters of 'b'. */ +inline int compareStringN(const char* a, const char* b, uint16_t n) { + return strncmp(a, b, n); +} + +/** Compare only the first n characters of 'b'. */ +inline int compareStringN(const char* a, const __FlashStringHelper* b, + uint16_t n) { + return strncmp_P(a, (const char*)b, n); +} + +/** Compare only the first n characters of 'b'. */ +int compareStringN(const FCString& a, const __FlashStringHelper* b, uint16_t n); + +/** Compare only the first n characters of 'b'. */ +int compareStringN(const FCString& a, const char* b, uint16_t n); + +// compareEqual() + +inline bool compareEqual(bool a, bool b) { + return (a == b); +} + +inline bool compareEqual(char a, char b) { + return (a == b); +} + +inline bool compareEqual(int a, int b) { + return (a == b); +} + +inline bool compareEqual(unsigned int a, unsigned int b) { + return (a == b); +} + +inline bool compareEqual(long a, long b) { + return (a == b); +} + +inline bool compareEqual(unsigned long a, unsigned long b) { + return (a == b); +} + +inline bool compareEqual(double a, double b) { + return (a == b); +} + +inline bool compareEqual(const char* a, const char* b) { + return compareString(a, b) == 0; +} + +inline bool compareEqual(const char* a, const String& b) { + return compareString(a, b) == 0; +} + +inline bool compareEqual(const char* a, const __FlashStringHelper* b) { + return compareString(a, b) == 0; +} + +inline bool compareEqual(const __FlashStringHelper* a, const char* b) { + return compareString(a, b) == 0; +} + +inline bool compareEqual( + const __FlashStringHelper* a, const __FlashStringHelper* b) { + return compareString(a, b) == 0; +} + +inline bool compareEqual(const __FlashStringHelper* a, const String& b) { + return compareString(a, b) == 0; +} + +inline bool compareEqual(const String& a, const char* b) { + return compareString(a, b) == 0; +} + +inline bool compareEqual(const String& a, const String& b) { + return compareString(a, b) == 0; +} + +inline bool compareEqual(const String& a, const __FlashStringHelper* b) { + return compareString(a, b) == 0; +} + +// compareLess() + +inline bool compareLess(bool a, bool b) { + return (a < b); +} + +inline bool compareLess(char a, char b) { + return (a < b); +} + +inline bool compareLess(int a, int b) { + return (a < b); +} + +inline bool compareLess(unsigned int a, unsigned int b) { + return (a < b); +} + +inline bool compareLess(long a, long b) { + return (a < b); +} + +inline bool compareLess(unsigned long a, unsigned long b) { + return (a < b); +} + +inline bool compareLess(double a, double b) { + return (a < b); +} + +inline bool compareLess(const char* a, const char* b) { + return compareString(a, b) < 0; +} + +inline bool compareLess(const char* a, const String& b) { + return compareString(a, b) < 0; +} + +inline bool compareLess(const char* a, const __FlashStringHelper* b) { + return compareString(a, b) < 0; +} + +inline bool compareLess(const __FlashStringHelper* a, const char* b) { + return compareString(a, b) < 0; +} + +inline bool compareLess( + const __FlashStringHelper* a, const __FlashStringHelper* b) { + return compareString(a, b) < 0; +} + +inline bool compareLess(const __FlashStringHelper* a, const String& b) { + return compareString(a, b) < 0; +} + +inline bool compareLess(const String& a, const char* b) { + return compareString(a, b) < 0; +} + +inline bool compareLess(const String& a, const String& b) { + return compareString(a, b) < 0; +} + +inline bool compareLess(const String& a, const __FlashStringHelper* b) { + return compareString(a, b) < 0; +} + +// compareMore() + +inline bool compareMore(bool a, bool b) { + return (a > b); +} + +inline bool compareMore(char a, char b) { + return (a > b); +} + +inline bool compareMore(int a, int b) { + return (a > b); +} + +inline bool compareMore(unsigned int a, unsigned int b) { + return (a > b); +} + +inline bool compareMore(long a, long b) { + return (a > b); +} + +inline bool compareMore(unsigned long a, unsigned long b) { + return (a > b); +} + +inline bool compareMore(double a, double b) { + return (a > b); +} + +inline bool compareMore(const char* a, const char* b) { + return compareString(a, b) > 0; +} + +inline bool compareMore(const char* a, const String& b) { + return compareString(a, b) > 0; +} + +inline bool compareMore(const char* a, const __FlashStringHelper* b) { + return compareString(a, b) > 0; +} + +inline bool compareMore(const __FlashStringHelper* a, const char* b) { + return compareString(a, b) > 0; +} + +inline bool compareMore( + const __FlashStringHelper* a, const __FlashStringHelper* b) { + return compareString(a, b) > 0; +} + +inline bool compareMore(const __FlashStringHelper* a, const String& b) { + return compareString(a, b) > 0; +} + +inline bool compareMore(const String& a, const char* b) { + return compareString(a, b) > 0; +} + +inline bool compareMore(const String& a, const String& b) { + return compareString(a, b) > 0; +} + +inline bool compareMore(const String& a, const __FlashStringHelper* b) { + return compareString(a, b) > 0; +} + +// compareLessOrEqual + +inline bool compareLessOrEqual(bool a, bool b) { + return (a <= b); +} + +inline bool compareLessOrEqual(char a, char b) { + return (a <= b); +} + +inline bool compareLessOrEqual(int a, int b) { + return (a <= b); +} + +inline bool compareLessOrEqual(unsigned int a, unsigned int b) { + return (a <= b); +} + +inline bool compareLessOrEqual(long a, long b) { + return (a <= b); +} + +inline bool compareLessOrEqual(unsigned long a, unsigned long b) { + return (a <= b); +} + +inline bool compareLessOrEqual(double a, double b) { + return (a <= b); +} + +inline bool compareLessOrEqual(const char* a, const char* b) { + return compareString(a, b) <= 0; +} + +inline bool compareLessOrEqual(const char* a, const String& b) { + return compareString(a, b) <= 0; +} + +inline bool compareLessOrEqual(const char* a, const __FlashStringHelper* b) { + return compareString(a, b) <= 0; +} + +inline bool compareLessOrEqual(const __FlashStringHelper* a, const char* b) { + return compareString(a, b) <= 0; +} + +inline bool compareLessOrEqual( + const __FlashStringHelper* a, const __FlashStringHelper* b) { + return compareString(a, b) <= 0; +} + +inline bool compareLessOrEqual(const __FlashStringHelper* a, const String& b) { + return compareString(a, b) <= 0; +} + +inline bool compareLessOrEqual(const String& a, const char* b) { + return compareString(a, b) <= 0; +} + +inline bool compareLessOrEqual(const String& a, const String& b) { + return compareString(a, b) <= 0; +} + +inline bool compareLessOrEqual(const String& a, const __FlashStringHelper* b) { + return compareString(a, b) <= 0; +} + +// compareMoreOrEqual + +inline bool compareMoreOrEqual(bool a, bool b) { + return (a >= b); +} + +inline bool compareMoreOrEqual(char a, char b) { + return (a >= b); +} + +inline bool compareMoreOrEqual(int a, int b) { + return (a >= b); +} + +inline bool compareMoreOrEqual(unsigned int a, unsigned int b) { + return (a >= b); +} + +inline bool compareMoreOrEqual(long a, long b) { + return (a >= b); +} + +inline bool compareMoreOrEqual(unsigned long a, unsigned long b) { + return (a >= b); +} + +inline bool compareMoreOrEqual(double a, double b) { + return (a >= b); +} + +inline bool compareMoreOrEqual(const char* a, const char* b) { + return compareString(a, b) >= 0; +} + +inline bool compareMoreOrEqual(const char* a, const String& b) { + return compareString(a, b) >= 0; +} + +inline bool compareMoreOrEqual(const char* a, const __FlashStringHelper* b) { + return compareString(a, b) >= 0; +} + +inline bool compareMoreOrEqual(const __FlashStringHelper* a, const char* b) { + return compareString(a, b) >= 0; +} + +inline bool compareMoreOrEqual( + const __FlashStringHelper* a, const __FlashStringHelper* b) { + return compareString(a, b) >= 0; +} + +inline bool compareMoreOrEqual(const __FlashStringHelper* a, const String& b) { + return compareString(a, b) >= 0; +} + +inline bool compareMoreOrEqual(const String& a, const char* b) { + return compareString(a, b) >= 0; +} + +inline bool compareMoreOrEqual(const String& a, const String& b) { + return compareString(a, b) >= 0; +} + +inline bool compareMoreOrEqual(const String& a, const __FlashStringHelper* b) { + return compareString(a, b) >= 0; +} + +// compareNotEqual + +inline bool compareNotEqual(bool a, bool b) { + return (a != b); +} + +inline bool compareNotEqual(char a, char b) { + return (a != b); +} + +inline bool compareNotEqual(int a, int b) { + return (a != b); +} + +inline bool compareNotEqual(unsigned int a, unsigned int b) { + return (a != b); +} + +inline bool compareNotEqual(long a, long b) { + return (a != b); +} + +inline bool compareNotEqual(unsigned long a, unsigned long b) { + return (a != b); +} + +inline bool compareNotEqual(double a, double b) { + return (a != b); +} + +inline bool compareNotEqual(const char* a, const char* b) { + return compareString(a, b) != 0; +} + +inline bool compareNotEqual(const char* a, const String& b) { + return compareString(a, b) != 0; +} + +inline bool compareNotEqual(const char* a, const __FlashStringHelper* b) { + return compareString(a, b) != 0; +} + +inline bool compareNotEqual(const __FlashStringHelper* a, const char* b) { + return compareString(a, b) != 0; +} + +inline bool compareNotEqual( + const __FlashStringHelper* a, const __FlashStringHelper* b) { + return compareString(a, b) != 0; +} + +inline bool compareNotEqual(const __FlashStringHelper* a, const String& b) { + return compareString(a, b) != 0; +} + +inline bool compareNotEqual(const String& a, const char* b) { + return compareString(a, b) != 0; +} + +inline bool compareNotEqual(const String& a, const String& b) { + return compareString(a, b) != 0; +} + +inline bool compareNotEqual(const String& a, const __FlashStringHelper* b) { + return compareString(a, b) != 0; +} + +} + +#endif diff --git a/src/aunit/FCString.h b/src/aunit/FCString.h new file mode 100644 index 0000000..8b50ba6 --- /dev/null +++ b/src/aunit/FCString.h @@ -0,0 +1,46 @@ +#ifndef AUNIT_FSTRING_H +#define AUNIT_FSTRING_H + +namespace aunit { + +/** + * A union of (const char*) and (const __FlashStringHelper*) with a + * discriminator. This allows us to treat these 2 strings like a single object. + * The major reason this class is needed is because the F() cannot be used + * outside a function, it can only be used inside a function, so we are forced + * to use normal c-strings instead of F() strings when manually creating Test or + * TestOnce instances. + */ +class FCString { + public: + static const uint8_t kCStringType = 0; + static const uint8_t kFStringType = 1; + + explicit FCString(const char* s): + mStringType(kCStringType) { + mString.cstring = s; + } + + explicit FCString(const __FlashStringHelper* s): + mStringType(kFStringType) { + mString.fstring = s; + } + + uint8_t getType() const { return mStringType; } + + const char* getCString() const { return mString.cstring; } + + const __FlashStringHelper* getFString() const { return mString.fstring; } + + private: + union { + const char* cstring; + const __FlashStringHelper* fstring; + } mString; + + const uint8_t mStringType; +}; + +} + +#endif diff --git a/src/aunit/Printer.cpp b/src/aunit/Printer.cpp new file mode 100644 index 0000000..628dab8 --- /dev/null +++ b/src/aunit/Printer.cpp @@ -0,0 +1,8 @@ +#include +#include "Printer.h" + +namespace aunit { + +Print* Printer::sPrinter = &Serial; + +} diff --git a/src/aunit/Printer.h b/src/aunit/Printer.h new file mode 100644 index 0000000..68d196d --- /dev/null +++ b/src/aunit/Printer.h @@ -0,0 +1,39 @@ +#ifndef AUNIT_PRINTER_H +#define AUNIT_PRINTER_H + +class Print; + +namespace aunit { + +/** + * Utility class that provides a level of indirection to the Print class where + * test results can be sent. By default, the Print object will be the Serial + * object. This can be changed using the setPrinter() method. + * + * This class assumes that it will be used after all static initializations have + * finished. Because static initialization ordering is undefined, if this + * utility is used during static initialization, the behaviour is undefined. + */ +class Printer { + public: + /** + * Get the output printer used by the various assertion() methods and the + * TestRunner. The default is the predefined Serial object. Can be changed + * using the setPrinter() method. + */ + static Print* getPrinter() { return sPrinter; } + + /** Set the printer. */ + static void setPrinter(Print* printer) { sPrinter = printer; } + + private: + // Disable copy-constructor and assignment operator + Printer(const Printer&) = delete; + Printer& operator=(const Printer&) = delete; + + static Print* sPrinter; +}; + +} + +#endif diff --git a/src/aunit/Test.cpp b/src/aunit/Test.cpp new file mode 100644 index 0000000..465b2ad --- /dev/null +++ b/src/aunit/Test.cpp @@ -0,0 +1,57 @@ +#include "Test.h" + +#include // for declaration of 'Serial' on Teensy and others +#include "Compare.h" + +namespace aunit { + +// Use a static variable inside a function to solve the static initialization +// ordering problem. +Test** Test::getRoot() { + static Test* root; + return &root; +} + +Test::Test(const char* name): + mName(name), + mStatus(kStatusNew), + mNext(nullptr) { + insert(); +} + +Test::Test(const __FlashStringHelper* name): + mName(name), + mStatus(kStatusNew), + mNext(nullptr) { + insert(); +} + +void Test::setPassOrFail(bool ok) { + mStatus = (ok) ? kStatusPassed : kStatusFailed; +} + +// Insert the current test case into the singly linked list, sorted by +// getName(). This is an O(N^2) algorithm, but should be good enough for +// small N ~= 100. If N becomes bigger than that, it's probably better to insert +// using an O(N) here, then sort the elements later in TestRunner::run(). +// Also, we don't increment a static counter here, because that would introduce +// another static initialization ordering problem. +void Test::insert() { + // Find the element p whose p->next sorts after the current test + Test** p = getRoot(); + while (*p != nullptr) { + if (compareString(getName(), (*p)->getName()) < 0) break; + p = &(*p)->mNext; + } + mNext = *p; + *p = this; +} + +void TestOnce::loop() { + once(); + if (getStatus() == kStatusSetup) { + pass(); + } +} + +} diff --git a/src/aunit/Test.h b/src/aunit/Test.h new file mode 100644 index 0000000..c4d69f8 --- /dev/null +++ b/src/aunit/Test.h @@ -0,0 +1,148 @@ +#ifndef AUNIT_TEST_H +#define AUNIT_TEST_H + +#include +#include +#include "FCString.h" + +// On the ESP8266 platform, The F() string cannot be placed in an inline +// context, because it interferes with other PROGMEM strings. See +// https://github.com/esp8266/Arduino/issues/3369. The solution was to move the +// constructor definition out from an inline function into a normal function +// defined outside of the class declaration.. + +// Macro to define a test that will be run only once. +#define test(name) struct test_ ## name : aunit::TestOnce {\ + test_ ## name();\ + virtual void once() override;\ +} test_ ## name ## _instance;\ +test_ ## name :: test_ ## name() : TestOnce(F(#name)) {}\ +void test_ ## name :: once() + +// Macro to define a test that will run repeatly upon each iteration of the +// global loop() method, stopping when the something calls Test::pass(), +// Test::fail() or Test::skip(). +#define testing(name) struct test_ ## name : aunit::Test {\ + test_ ## name();\ + virtual void loop() override;\ +} test_ ## name ## _instance;\ +test_ ## name :: test_ ## name() : Test(F(#name)) {}\ +void test_ ## name :: loop() + +namespace aunit { + +class TestRunner; + +class Test { + public: + static const uint8_t kStatusNew = 0; + static const uint8_t kStatusSetup = 1; + static const uint8_t kStatusPassed = 2; + static const uint8_t kStatusFailed = 3; + static const uint8_t kStatusSkipped = 4; + + /** + * Get the pointer to the root pointer. Implemented as a function static so + * fixes the C++ static initialization problem making it safe to use this in + * other static contexts. + */ + static Test** getRoot(); + + /** + * Constructor taking the name of the given test case. Also performs + * self-registration into the linked list of all test cases defined by + * Test::sRoot. + */ + explicit Test(const char* name); + + /** + * Constructor taking the name of the given test case. Also performs + * self-registration into the linked list of all test cases defined by + * Test::sRoot. + */ + explicit Test(const __FlashStringHelper* name); + + /** + * Optional method that performs any initialization. The assertXxx() macros, + * as well as pass(), fail() and skip() functions can be called in here. + */ + virtual void setup() {} + + /** + * The user-provided test case function. EEach call to Test::run() makes one + * call to this loop() method. The assertXxx() macros, as well as pass(), + * fail() and skip() functions can be called in here. + */ + virtual void loop() = 0; + + /** Get the name of the test. */ + const FCString& getName() { return mName; } + + /** Get the status of the test. */ + uint8_t getStatus() { return mStatus; } + + /** Set the status of the test. */ + void setStatus(uint8_t status) { mStatus = status; } + + /** Set the status to Passed or Failed depending on ok. */ + void setPassOrFail(bool ok); + + /** + * Return the next pointer as a pointer to the pointer, similar to + * getRoot(). This makes it much easier to manipulate a singly-linked list. + * Also makes setNext() method unnecessary. + */ + Test** getNext() { return &mNext; } + + /** Mark the test as skipped. */ + void skip() { mStatus = kStatusSkipped; } + + protected: + /** Mark the test as failed. */ + void fail() { mStatus = kStatusFailed; } + + /** Mark the test as passed. */ + void pass() { mStatus = kStatusPassed; } + + private: + // Disable copy-constructor and assignment operator + Test(const Test&) = delete; + Test& operator=(const Test&) = delete; + + /** Insert into the linked list. */ + void insert(); + + const FCString mName; + uint8_t mStatus; + Test* mNext; +}; + +/** Similar to Test but performs the loop() method only once. */ +class TestOnce: public Test { + public: + /** Constructor. */ + explicit TestOnce(const char* name): + Test(name) {} + + /** Constructor. */ + explicit TestOnce(const __FlashStringHelper* name): + Test(name) {} + + /** + * Calls the user-provided once() method. If no other assertXxx() macros set + * the internal status, then this calls pass() to make sure that this test + * case will be called only once from Test::run(). + */ + virtual void loop(); + + /** User-provided test case. */ + virtual void once() = 0; + + private: + // Disable copy-constructor and assignment operator + TestOnce(const TestOnce&) = delete; + TestOnce& operator=(const TestOnce&) = delete; +}; + +} +#endif diff --git a/src/aunit/TestRunner.cpp b/src/aunit/TestRunner.cpp new file mode 100644 index 0000000..7d30ff9 --- /dev/null +++ b/src/aunit/TestRunner.cpp @@ -0,0 +1,179 @@ +#include // import 'Serial' +#include + +#include "FCString.h" +#include "Compare.h" +#include "Printer.h" +#include "Verbosity.h" +#include "Test.h" +#include "TestRunner.h" + +namespace aunit { + +// Use a function static singleton to avoid the static initialization ordering +// problem. It's probably not an issue right now, since TestRunner is expected +// to be called only after all static initialization, but future refactoring +// could change that so this is defensive programming. +TestRunner* TestRunner::getRunner() { + static TestRunner singletonRunner; + return &singletonRunner; +} + +void TestRunner::setPrinter(Print* printer) { + Printer::setPrinter(printer); +} + +void TestRunner::setStatusMatchingPattern(const char* pattern, uint8_t status) { + uint16_t length = strlen(pattern); + if (length > 0 && pattern[length - 1] == '*') { + // prefix match + length -= 1; + } else { + // exact match + length += 1; + } + + for (Test** p = Test::getRoot(); *p != nullptr; p = (*p)->getNext()) { + if (compareStringN((*p)->getName(), pattern, length) == 0) { + (*p)->setStatus(status); + } + } +} + +TestRunner::TestRunner(): + mCurrent(nullptr), + mIsResolved(false), + mIsSetup(false), + mVerbosity(Verbosity::kDefault), + mCount(0), + mPassedCount(0), + mFailedCount(0), + mSkippedCount(0) {} + +void TestRunner::runTest() { + setupRunner(); + + // If no more test cases, then print out summary of run. + if (*Test::getRoot() == nullptr) { + if (!mIsResolved) { + resolveRun(); + } + return; + } + + // If reached the end and there are still test cases left, start from the + // beginning again. + if (*mCurrent == nullptr) { + mCurrent = Test::getRoot(); + } + + // Implement a finite state machine that calls the (*mCurrent)->setup() or + // (*mCurrent)->loop(), then changes the test case's mStatus. + switch ((*mCurrent)->getStatus()) { + case Test::kStatusNew: + (*mCurrent)->setup(); + + // support assertXxx() statements inside the setup() method + if ((*mCurrent)->getStatus() == Test::kStatusNew) { + (*mCurrent)->setStatus(Test::kStatusSetup); + } + break; + case Test::kStatusSetup: + (*mCurrent)->loop(); + // skip to the next one, but keep current test in the list + mCurrent = (*mCurrent)->getNext(); + break; + case Test::kStatusSkipped: + mSkippedCount++; + resolveTest((*mCurrent)); + // skip to the next one by taking current test out of the list + (*mCurrent) = *(*mCurrent)->getNext(); + break; + case Test::kStatusPassed: + mPassedCount++; + resolveTest((*mCurrent)); + // skip to the next one by taking current test out of the list + (*mCurrent) = *(*mCurrent)->getNext(); + break; + case Test::kStatusFailed: + mFailedCount++; + resolveTest((*mCurrent)); + // skip to the next one by taking current test out of the list + (*mCurrent) = *(*mCurrent)->getNext(); + break; + } +} + +void TestRunner::setupRunner() { + if (!mIsSetup) { + mIsSetup = true; + mCount = countTests(); + mCurrent = Test::getRoot(); + } +} + +// Count the number of tests in TestRunner instead of Test::insert() to avoid +// another C++ static initialization ordering problem. +uint16_t TestRunner::countTests() { + uint16_t count = 0; + for (Test** p = Test::getRoot(); *p != nullptr; p = (*p)->getNext()) { + count++; + } + return count; +} + +void TestRunner::resolveTest(Test* testCase) { + bool isOutput = isVerbosity( + Verbosity::kTestFailed | Verbosity::kTestSkipped | Verbosity::kTestPassed); + if (!isOutput) return; + + Printer::getPrinter()->print(F("Test ")); + const FCString& name = testCase->getName(); + if (name.getType() == FCString::kCStringType) { + Printer::getPrinter()->print(name.getCString()); + } else { + Printer::getPrinter()->print(name.getFString()); + } + if (testCase->getStatus() == Test::kStatusSkipped) { + Printer::getPrinter()->println(F(" skipped.")); + } else if (testCase->getStatus() == Test::kStatusFailed) { + Printer::getPrinter()->println(F(" failed.")); + } else if (testCase->getStatus ()== Test::kStatusPassed) { + Printer::getPrinter()->println(F(" passed.")); + } +} + +void TestRunner::resolveRun() { + if (!isVerbosity(Verbosity::kTestRunSummary)) return; + + Printer::getPrinter()->print(F("Test summary: ")); + Printer::getPrinter()->print(mPassedCount); + Printer::getPrinter()->print(F(" passed, ")); + Printer::getPrinter()->print(mFailedCount); + Printer::getPrinter()->print(F(" failed, and ")); + Printer::getPrinter()->print(mSkippedCount); + Printer::getPrinter()->print(F(" skipped, out of ")); + Printer::getPrinter()->print(mCount); + Printer::getPrinter()->println(F(" test(s).")); + + mIsResolved = true; +} + +void TestRunner::listTests() { + setupRunner(); + + Printer::getPrinter()->print("Test count: "); + Printer::getPrinter()->println(mCount); + for (Test** p = Test::getRoot(); (*p) != nullptr; p = (*p)->getNext()) { + Printer::getPrinter()->print(F("Test ")); + const FCString& name = (*p)->getName(); + if (name.getType() == FCString::kCStringType) { + Printer::getPrinter()->print(name.getCString()); + } else { + Printer::getPrinter()->print(name.getFString()); + } + Printer::getPrinter()->println(F(" found.")); + } +} + +} diff --git a/src/aunit/TestRunner.h b/src/aunit/TestRunner.h new file mode 100644 index 0000000..2df073a --- /dev/null +++ b/src/aunit/TestRunner.h @@ -0,0 +1,114 @@ +#ifndef AUNIT_TEST_RUNNER_H +#define AUNIT_TEST_RUNNER_H + +#include +#include "Test.h" + +class Print; + +namespace aunit { + +/** + * The class that runs the various test cases defined by the test() and + * testing() macros. It prints the summary of each test as well as the final + * summary of the entire run at the end. In the future, it may be possible to + * allow a different TestRunner to be used. + */ +class TestRunner { + public: + /** Run all tests using the current runner. */ + static void run() { getRunner()->runTest(); } + + /** Print out the known tests. For debugging only. */ + static void list() { getRunner()->listTests(); } + + /** + * Exclude the tests which match the pattern. + * Currently supports only a trailing '*'. For example, exclude("flash*"). + */ + static void exclude(const char* pattern) { + getRunner()->setStatusMatchingPattern(pattern, Test::kStatusSkipped); + } + + /** + * Include the tests which match the pattern. + * Currently supports only a trailing '*'. For example, include("flash*"). + */ + static void include(const char* pattern) { + getRunner()->setStatusMatchingPattern(pattern, Test::kStatusSetup); + } + + /** Set the verbosity flag. */ + static void setVerbosity(uint8_t verbosity) { + getRunner()->setVerbosityFlag(verbosity); + } + + /** Determine the current verbosity. */ + static bool isVerbosity(uint8_t verbosity) { + return getRunner()->isVerbosityFlag(verbosity); + } + + /** Set the output printer. */ + static void setPrinter(Print* printer); + + /** Set the pass/fail status of the current test. */ + static void setPassOrFail(bool ok) { getRunner()->setTestPassOrFail(ok); } + + private: + /** Return the singleton TestRunner. */ + static TestRunner* getRunner(); + + /** Count the number of tests. */ + static uint16_t countTests(); + + // Disable copy-constructor and assignment operator + TestRunner(const TestRunner&) = delete; + TestRunner& operator=(const TestRunner&) = delete; + + /** Contructor. */ + TestRunner(); + + /** Run the current test case and print out the result. */ + void runTest(); + + /** Print out the known tests. For debugging only. */ + void listTests(); + + /** Print out the summary of the entire test suite. */ + void resolveRun(); + + /** Print out the summary of the current test. */ + void resolveTest(Test* testCase); + + /** Perform any TestRunner initialization. */ + void setupRunner(); + + /** Enables the given verbosity. */ + void setVerbosityFlag(uint8_t verbosity) { mVerbosity = verbosity; } + + /** Determine the current verbosity. */ + bool isVerbosityFlag(uint8_t verbosity) { return mVerbosity & verbosity; } + + /** Set the status of the tests which match the pattern. */ + void setStatusMatchingPattern(const char* pattern, uint8_t status); + + /** Set the pass/fail status of the current test. */ + void setTestPassOrFail(bool ok) { (*mCurrent)->setPassOrFail(ok); } + + // The current test case is represented by a pointer to a pointer. This + // allows treating the root node the same as all the other nodes, and + // simplifies the code traversing the singly-linked list significantly. + Test** mCurrent; + + bool mIsResolved; + bool mIsSetup; + uint8_t mVerbosity; + uint16_t mCount; + uint16_t mPassedCount; + uint16_t mFailedCount; + uint16_t mSkippedCount; +}; + +} + +#endif diff --git a/src/aunit/Verbosity.h b/src/aunit/Verbosity.h new file mode 100644 index 0000000..cdf66f4 --- /dev/null +++ b/src/aunit/Verbosity.h @@ -0,0 +1,35 @@ +#ifndef AUNIT_VERBOSITY_H +#define AUNIT_VERBOSITY_H + +namespace aunit { + +/** + * Utility class to hold the Verbosity constants. Current used only by + * TestRunner but potentially could be used by Test and TestOnce in the future, + * so it seemed better to pull these out into a separate file. + */ +class Verbosity { + public: + static const uint8_t kTestRunSummary = 0x01; + static const uint8_t kTestFailed = 0x02; + static const uint8_t kTestPassed = 0x04; + static const uint8_t kTestSkipped = 0x08; + static const uint8_t kTestAll = 0x0F; + static const uint8_t kAssertionFailed = 0x10; + static const uint8_t kAssertionPassed = 0x20; + static const uint8_t kAssertionAll = 0x30; + static const uint8_t kDefault = ( + kAssertionFailed | kTestAll); + static const uint8_t kAll = 0xFF; + static const uint8_t kNone = 0x00; + + private: + // Disable constructor, copy-constructor and assignment operator + Verbosity() = delete; + Verbosity(const Verbosity&) = delete; + Verbosity& operator=(const Verbosity&) = delete; +}; + +} + +#endif From 5dbb200c76ad7cface1b3af894ad120971f96be9 Mon Sep 17 00:00:00 2001 From: Brian Park Date: Mon, 12 Mar 2018 22:07:33 -0700 Subject: [PATCH 02/22] Add MIT License text into various files --- LICENSE | 2 +- examples/AUnitTest/AUnitTest.ino | 24 ++++++++++++++++++++++++ src/AUnit.h | 24 ++++++++++++++++++++++++ src/aunit/Assertion.h | 24 ++++++++++++++++++++++++ src/aunit/Compare.cpp | 26 +++++++++++++++++++++++++- src/aunit/Compare.h | 24 ++++++++++++++++++++++++ src/aunit/FCString.h | 24 ++++++++++++++++++++++++ src/aunit/Printer.cpp | 24 ++++++++++++++++++++++++ src/aunit/Printer.h | 24 ++++++++++++++++++++++++ src/aunit/Test.cpp | 24 ++++++++++++++++++++++++ src/aunit/Test.h | 24 ++++++++++++++++++++++++ src/aunit/TestRunner.cpp | 24 ++++++++++++++++++++++++ src/aunit/TestRunner.h | 24 ++++++++++++++++++++++++ src/aunit/Verbosity.h | 24 ++++++++++++++++++++++++ 14 files changed, 314 insertions(+), 2 deletions(-) diff --git a/LICENSE b/LICENSE index 5226d58..b1d1f2c 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2018 bxparks +Copyright (c) 2018 Brian T. Park Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/examples/AUnitTest/AUnitTest.ino b/examples/AUnitTest/AUnitTest.ino index b578873..1e3ea26 100644 --- a/examples/AUnitTest/AUnitTest.ino +++ b/examples/AUnitTest/AUnitTest.ino @@ -1,4 +1,28 @@ #line 2 "AUnitTest.ino" +/* +MIT License + +Copyright (c) 2018 Brian T. Park + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + #include // If ArduinoUnit is used, this unit test no longer fits in a 32kB diff --git a/src/AUnit.h b/src/AUnit.h index db9179c..3095933 100644 --- a/src/AUnit.h +++ b/src/AUnit.h @@ -1,3 +1,27 @@ +/* +MIT License + +Copyright (c) 2018 Brian T. Park + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + #ifndef AUNIT_AUNIT_H #define AUNIT_AUNIT_H diff --git a/src/aunit/Assertion.h b/src/aunit/Assertion.h index 0bd6d28..523b9ab 100644 --- a/src/aunit/Assertion.h +++ b/src/aunit/Assertion.h @@ -1,3 +1,27 @@ +/* +MIT License + +Copyright (c) 2018 Brian T. Park + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + #ifndef AUNIT_ASSERTION_H #define AUNIT_ASSERTION_H diff --git a/src/aunit/Compare.cpp b/src/aunit/Compare.cpp index 27cad1f..133b093 100644 --- a/src/aunit/Compare.cpp +++ b/src/aunit/Compare.cpp @@ -1,3 +1,27 @@ +/* +MIT License + +Copyright (c) 2018 Brian T. Park + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + #include #include "Compare.h" #include "FCString.h" @@ -48,7 +72,7 @@ int compareString(const FCString& a, const FCString& b) { } // Not really used anywhere right now, so the linker will -// remove it. But I think I might needit in the future. +// remove it. But I think I might need it in the future. int compareStringN(const __FlashStringHelper* a, const __FlashStringHelper* b, uint16_t n) { const char* aa = reinterpret_cast(a); diff --git a/src/aunit/Compare.h b/src/aunit/Compare.h index 78ecc34..5321730 100644 --- a/src/aunit/Compare.h +++ b/src/aunit/Compare.h @@ -1,3 +1,27 @@ +/* +MIT License + +Copyright (c) 2018 Brian T. Park + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + #ifndef AUNIT_COMPARE_H #define AUNIT_COMPARE_H diff --git a/src/aunit/FCString.h b/src/aunit/FCString.h index 8b50ba6..426f422 100644 --- a/src/aunit/FCString.h +++ b/src/aunit/FCString.h @@ -1,3 +1,27 @@ +/* +MIT License + +Copyright (c) 2018 Brian T. Park + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + #ifndef AUNIT_FSTRING_H #define AUNIT_FSTRING_H diff --git a/src/aunit/Printer.cpp b/src/aunit/Printer.cpp index 628dab8..767c6ff 100644 --- a/src/aunit/Printer.cpp +++ b/src/aunit/Printer.cpp @@ -1,3 +1,27 @@ +/* +MIT License + +Copyright (c) 2018 Brian T. Park + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + #include #include "Printer.h" diff --git a/src/aunit/Printer.h b/src/aunit/Printer.h index 68d196d..2a7ae7e 100644 --- a/src/aunit/Printer.h +++ b/src/aunit/Printer.h @@ -1,3 +1,27 @@ +/* +MIT License + +Copyright (c) 2018 Brian T. Park + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + #ifndef AUNIT_PRINTER_H #define AUNIT_PRINTER_H diff --git a/src/aunit/Test.cpp b/src/aunit/Test.cpp index 465b2ad..6fe9715 100644 --- a/src/aunit/Test.cpp +++ b/src/aunit/Test.cpp @@ -1,3 +1,27 @@ +/* +MIT License + +Copyright (c) 2018 Brian T. Park + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + #include "Test.h" #include // for declaration of 'Serial' on Teensy and others diff --git a/src/aunit/Test.h b/src/aunit/Test.h index c4d69f8..9b84d2e 100644 --- a/src/aunit/Test.h +++ b/src/aunit/Test.h @@ -1,3 +1,27 @@ +/* +MIT License + +Copyright (c) 2018 Brian T. Park + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + #ifndef AUNIT_TEST_H #define AUNIT_TEST_H diff --git a/src/aunit/TestRunner.cpp b/src/aunit/TestRunner.cpp index 7d30ff9..7b2ce9a 100644 --- a/src/aunit/TestRunner.cpp +++ b/src/aunit/TestRunner.cpp @@ -1,3 +1,27 @@ +/* +MIT License + +Copyright (c) 2018 Brian T. Park + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + #include // import 'Serial' #include diff --git a/src/aunit/TestRunner.h b/src/aunit/TestRunner.h index 2df073a..49e0255 100644 --- a/src/aunit/TestRunner.h +++ b/src/aunit/TestRunner.h @@ -1,3 +1,27 @@ +/* +MIT License + +Copyright (c) 2018 Brian T. Park + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + #ifndef AUNIT_TEST_RUNNER_H #define AUNIT_TEST_RUNNER_H diff --git a/src/aunit/Verbosity.h b/src/aunit/Verbosity.h index cdf66f4..bbb5a02 100644 --- a/src/aunit/Verbosity.h +++ b/src/aunit/Verbosity.h @@ -1,3 +1,27 @@ +/* +MIT License + +Copyright (c) 2018 Brian T. Park + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + #ifndef AUNIT_VERBOSITY_H #define AUNIT_VERBOSITY_H From 15e4d7435b584869ea67fc4f61481b25c4f43fc8 Mon Sep 17 00:00:00 2001 From: Brian Park Date: Mon, 12 Mar 2018 23:09:28 -0700 Subject: [PATCH 03/22] Update comments to add references to ArduinoUnit files which contributed significantly to the design and implementation of Compare.h, Assertion.h, and Test.h. No functional change. --- src/aunit/Assertion.h | 7 +++++-- src/aunit/Compare.h | 19 +++++++++++-------- src/aunit/Test.h | 3 +++ 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/aunit/Assertion.h b/src/aunit/Assertion.h index 523b9ab..25b3d94 100644 --- a/src/aunit/Assertion.h +++ b/src/aunit/Assertion.h @@ -22,6 +22,9 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +// Significant portions of the design and implementation of this file came from +// https://github.com/mmurdoch/arduinounit/blob/master/src/ArduinoUnit.h + #ifndef AUNIT_ASSERTION_H #define AUNIT_ASSERTION_H @@ -62,8 +65,8 @@ namespace aunit { // overloaded for the various types that we want to support. // // Prints something like the following: -// Assertion failed: (5) == (6), file Test.ino line 820. -// Assertion passed: (6) == (6), file Test.ino line 820. +// Assertion failed: (5) == (6), file Test.ino, line 820. +// Assertion passed: (6) == (6), file Test.ino, line 820. template void printAssertionMessage(bool ok, const char* file, uint16_t line, const A& lhs, const char *opName, const B& rhs) { diff --git a/src/aunit/Compare.h b/src/aunit/Compare.h index 5321730..15eecea 100644 --- a/src/aunit/Compare.h +++ b/src/aunit/Compare.h @@ -22,6 +22,9 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +// Significant portions of the design and implementation of this file came from +// https://github.com/mmurdoch/arduinounit/blob/master/src/ArduinoUnitUtility/Compare.h + #ifndef AUNIT_COMPARE_H #define AUNIT_COMPARE_H @@ -72,14 +75,13 @@ class FCString; * Function Overloading: * --------------------- * In this project, I used function overloading instead of template - * specialization. Functional overloading handles c-style strings (i.e. - * character arrays) naturally, in the way users expect. For example, (char*) is + * specialization. Function overloading handles c-style strings (i.e. character + * arrays) naturally, in the way most users expect. For example, (char*) is * automarically cast to (const char*), and (char[N]) is autonmatically - * converted to (const char*). + * cast to (const char*). * - * Since all the primitive value types (e.g. (char), (int), (unsigned char), - * etc.) can be implemented using the exact same code, I attempted to use a - * generic templatized version, using sonmething like: + * For the primitive value types (e.g. (char), (int), (unsigned char), etc.) I + * attempted to use a generic templatized version, using sonmething like: * * template * compareEqual(const T& a, const T& b) { ... } @@ -92,13 +94,14 @@ class FCString; * * compareEqual(const char* a, const char*b); * - * When the compareEqual() method is called with a (char*) or a (char[N]): + * When the compareEqual() method is called with a (char*) or a (char[N]), + * like this: * * char a[3] = {...}; * char b[4] = {...}; * compareEqual(a, b); * - * This calls compareEqual(char* const&, const* const&), which is the wrong + * this calls compareEqual(char* const&, const* const&), which is the wrong * version for a c-style string. The only way I could get this to work was to * avoid templates completely and manually define all the function overloads * even for primitive integer types. diff --git a/src/aunit/Test.h b/src/aunit/Test.h index 9b84d2e..18b57a3 100644 --- a/src/aunit/Test.h +++ b/src/aunit/Test.h @@ -22,6 +22,9 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +// Significant portions of the design and implementation of this file came from +// https://github.com/mmurdoch/arduinounit/blob/master/src/ArduinoUnit.h + #ifndef AUNIT_TEST_H #define AUNIT_TEST_H From 5f723506d5cef0a7927ea71e74185e45709035d4 Mon Sep 17 00:00:00 2001 From: Brian Park Date: Mon, 12 Mar 2018 23:19:40 -0700 Subject: [PATCH 04/22] README.md: better wording, fix some typos --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 1a03251..8f12106 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ Most of the frequently used macros are compatible between ArduinoUnit and AUnit: * `testing()` * `assertXxx()` -AUnit supports a exclude and include filters: +AUnit supports exclude and include filters: * `TestRunner::exclude()` * `TestRunner::include()` @@ -70,9 +70,9 @@ in the global namespace, so it is usually not necessary to import the entire The usage of **AUnit** is almost identical to **ArduinoUnit**. A test case is defined by a fragment of code inside the `{ }` just after the `test()` or `testing()` macros. The argument to these macros are the name of the test case. -(The name is available within the test code through the `const -__FlashStringHelper* getName()` method). It is used to generate a class whose -parents are `aunit::TestOnce` and `aunit::Test` respectively. +(The name is available within the test code using the `Test::getName()` +method). The `test()` and `testing()` macros use the name to generate a subclass +whose parents are `aunit::TestOnce` and `aunit::Test` respectively. The macros also generate code to create an instance of the subclass. The code following after the `test()` and `testing()` macros becomes @@ -80,11 +80,11 @@ the body of the virtual `TestOnce::once()` and `Test::loop` methods (respectively). When the instance of the test case is statically initialized, it adds itself to -a linked list. The root of that singly linked list is given by +a linked list. The root of that singly-linked list is given by `Test::getRoot()`. The `TestRunner::run()` method traverses the linked list, executing each test case until it passes, fails or is skipped. -Here is an outline of what an AUnit unit test sketch: +Here is a rough outline of an AUnit unit test sketch: ``` #line 2 AUnitTest.ino From c9f65e7f83368274795bb7e864c7a7c62624d1c4 Mon Sep 17 00:00:00 2001 From: Brian Park Date: Tue, 13 Mar 2018 09:30:26 -0700 Subject: [PATCH 05/22] Simplify compareStringN() methods because strncmp_P() is symmetric, add more unit tests --- examples/AUnitTest/AUnitTest.ino | 21 ++++++++------ src/aunit/Compare.cpp | 49 +++++++++++++------------------- src/aunit/Compare.h | 34 ++++++++++++---------- src/aunit/TestRunner.cpp | 9 +++--- 4 files changed, 56 insertions(+), 57 deletions(-) diff --git a/examples/AUnitTest/AUnitTest.ino b/examples/AUnitTest/AUnitTest.ino index 1e3ea26..7556b88 100644 --- a/examples/AUnitTest/AUnitTest.ino +++ b/examples/AUnitTest/AUnitTest.ino @@ -274,14 +274,19 @@ test(flashString) { #if USE_AUNIT == 1 test(compareStringN) { - assertEqual(0, compareStringN(ff, "abcde", 5)); - assertEqual(1, compareStringN(ff, "abcd", 5)); - assertEqual(0, compareStringN(ff, "abcd", 4)); - assertEqual(1, compareStringN(ff, "", 1)); - assertEqual(0, compareStringN(ff, "", 0)); - - assertEqual(0, compareStringN(gg, ff, 5)); - assertEqual(1, compareStringN(gg, ff, 6)); + assertEqual(compareStringN(ff, "abcde", 5), 0); + assertEqual(compareStringN("abcde", ff, 5), 0); + assertMore(compareStringN(ff, "abcd", 5), 0); + assertLess(compareStringN("abcd", ff, 5), 0); + assertEqual(compareStringN(ff, "abcd", 4), 0); + assertEqual(compareStringN("abcd", ff, 4), 0); + assertMore(compareStringN(ff, "", 1), 0); + assertLess(compareStringN("", ff, 1), 0); + assertEqual(compareStringN(ff, "", 0), 0); + assertEqual(compareStringN("", ff, 0), 0); + + assertEqual(compareStringN(gg, ff, 5), 0); + assertMore(compareStringN(gg, ff, 6), 0); } #endif diff --git a/src/aunit/Compare.cpp b/src/aunit/Compare.cpp index 133b093..28c4cee 100644 --- a/src/aunit/Compare.cpp +++ b/src/aunit/Compare.cpp @@ -26,25 +26,32 @@ SOFTWARE. #include "Compare.h" #include "FCString.h" -namespace aunit { - // Flash memory must be read using 4-byte alignment on the ESP8266. AVR doesn't // care. Teensy-ARM fakes the flash memory API but really just uses the normal // static RAM. The following code will work for all 3 environments. -int compareString( - const __FlashStringHelper* a, const __FlashStringHelper* b) { +namespace aunit { + +int compareString(const __FlashStringHelper* a, const __FlashStringHelper* b) { const char* aa = reinterpret_cast(a); const char* bb = reinterpret_cast(b); char bufa[4]; char bufb[4]; + + // Using uint8_t instead of size_t is not a bug because we only need the + // least significant 2 bits of the counter. Rolling over the uint8_t counter + // is ok too. for (uint8_t i = 0; true; i++) { if ((i & 0x03) == 0) { + // NOTE: There ought to be a simpler, more efficient way of extracting 4 + // characters from flash memory the pointer is already 4-byte aligned. + // But I don't know what it is right now. memcpy_P(bufa, aa, 4); memcpy_P(bufb, bb, 4); aa += 4; bb += 4; } + // Avoid '%' operator since some 8-bit processors lack hardware division. char ca = bufa[i & 0x03]; char cb = bufb[i & 0x03]; if (ca < cb) return -1; @@ -71,16 +78,17 @@ int compareString(const FCString& a, const FCString& b) { } } -// Not really used anywhere right now, so the linker will -// remove it. But I think I might need it in the future. -int compareStringN(const __FlashStringHelper* a, - const __FlashStringHelper* b, uint16_t n) { +int compareStringN(const __FlashStringHelper* a, const __FlashStringHelper* b, + size_t n) { const char* aa = reinterpret_cast(a); const char* bb = reinterpret_cast(b); char bufa[4]; char bufb[4]; - for (uint16_t i = 0; i < n; i++) { + for (size_t i = 0; i < n; i++) { if ((i & 0x03) == 0) { + // NOTE: There ought to be a simpler, more efficient way of extracting 4 + // characters from flash memory the pointer is already 4-byte aligned. + // But I don't know what it is right now. memcpy_P(bufa, aa, 4); memcpy_P(bufb, bb, 4); aa += 4; @@ -96,26 +104,7 @@ int compareStringN(const __FlashStringHelper* a, return 0; } -int compareStringN(const __FlashStringHelper* a, const char* b, uint16_t n) { - const char* aa = reinterpret_cast(a); - char bufa[4]; - for (uint16_t i = 0; i < n; i++) { - if ((i & 0x03) == 0) { - memcpy_P(bufa, aa, 4); - aa += 4; - } - char ca = bufa[i & 0x03]; - char cb = b[i]; - if (ca < cb) return -1; - if (ca > cb) return 1; - // we hit this condition only if both strings were the same length - if (ca == '\0' || cb == '\0') return 0; - } - return 0; -} - -int compareStringN(const FCString& a, const __FlashStringHelper* b, - uint16_t n) { +int compareStringN(const FCString& a, const char* b, size_t n) { if (a.getType() == FCString::kCStringType) { return compareStringN(a.getCString(), b, n); } else { @@ -123,7 +112,7 @@ int compareStringN(const FCString& a, const __FlashStringHelper* b, } } -int compareStringN(const FCString& a, const char* b, uint16_t n) { +int compareStringN(const FCString& a, const __FlashStringHelper* b, size_t n) { if (a.getType() == FCString::kCStringType) { return compareStringN(a.getCString(), b, n); } else { diff --git a/src/aunit/Compare.h b/src/aunit/Compare.h index 15eecea..1904dcb 100644 --- a/src/aunit/Compare.h +++ b/src/aunit/Compare.h @@ -170,32 +170,36 @@ inline int compareString(const __FlashStringHelper* a, const String& b) { int compareString(const FCString& a, const FCString& b); // compareStringN() +// // These methods are used to implement the TestRunner::exclude() and // TestRunner::include() features. -/** Compare only the first n characters of 'b'. */ -int compareStringN(const __FlashStringHelper* a, const __FlashStringHelper* b, - uint16_t n); - -/** Compare only the first n characters of 'b'. */ -int compareStringN(const __FlashStringHelper* a, const char* b, uint16_t n); - -/** Compare only the first n characters of 'b'. */ -inline int compareStringN(const char* a, const char* b, uint16_t n) { +/** Compare only the first n characters of 'a' or 'b'. */ +inline int compareStringN(const char* a, const char* b, size_t n) { return strncmp(a, b, n); } -/** Compare only the first n characters of 'b'. */ +/** Compare only the first n characters of 'a' or 'b'. */ inline int compareStringN(const char* a, const __FlashStringHelper* b, - uint16_t n) { + size_t n) { return strncmp_P(a, (const char*)b, n); } -/** Compare only the first n characters of 'b'. */ -int compareStringN(const FCString& a, const __FlashStringHelper* b, uint16_t n); +/** Compare only the first n characters of 'a' or 'b'. */ +inline int compareStringN(const __FlashStringHelper* a, const char* b, + size_t n) { + return -strncmp_P(b, (const char*)a, n); +} + +/** Compare only the first n characters of 'a' or 'b'. */ +int compareStringN(const __FlashStringHelper* a, const __FlashStringHelper* b, + size_t n); + +/** Compare only the first n characters of 'a' or 'b'. */ +int compareStringN(const FCString& a, const char* b, size_t n); -/** Compare only the first n characters of 'b'. */ -int compareStringN(const FCString& a, const char* b, uint16_t n); +/** Compare only the first n characters of 'a' or 'b'. */ +int compareStringN(const FCString& a, const __FlashStringHelper* b, size_t n); // compareEqual() diff --git a/src/aunit/TestRunner.cpp b/src/aunit/TestRunner.cpp index 7b2ce9a..7cf470e 100644 --- a/src/aunit/TestRunner.cpp +++ b/src/aunit/TestRunner.cpp @@ -48,13 +48,13 @@ void TestRunner::setPrinter(Print* printer) { } void TestRunner::setStatusMatchingPattern(const char* pattern, uint8_t status) { - uint16_t length = strlen(pattern); + size_t length = strlen(pattern); if (length > 0 && pattern[length - 1] == '*') { // prefix match - length -= 1; + length--; } else { // exact match - length += 1; + length++; } for (Test** p = Test::getRoot(); *p != nullptr; p = (*p)->getNext()) { @@ -148,7 +148,8 @@ uint16_t TestRunner::countTests() { void TestRunner::resolveTest(Test* testCase) { bool isOutput = isVerbosity( - Verbosity::kTestFailed | Verbosity::kTestSkipped | Verbosity::kTestPassed); + Verbosity::kTestFailed | Verbosity::kTestSkipped | + Verbosity::kTestPassed); if (!isOutput) return; Printer::getPrinter()->print(F("Test ")); From 22cf4dc53ecee161a2eec9e6986c7a00fe6720bb Mon Sep 17 00:00:00 2001 From: Brian Park Date: Tue, 13 Mar 2018 09:31:16 -0700 Subject: [PATCH 06/22] Move AUnitTest from examples/ to tests/ --- {examples => tests}/AUnitTest/AUnitTest.ino | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {examples => tests}/AUnitTest/AUnitTest.ino (100%) diff --git a/examples/AUnitTest/AUnitTest.ino b/tests/AUnitTest/AUnitTest.ino similarity index 100% rename from examples/AUnitTest/AUnitTest.ino rename to tests/AUnitTest/AUnitTest.ino From 5e0006ef59c0a67c492001875d2134d3cba47b8a Mon Sep 17 00:00:00 2001 From: Brian Park Date: Tue, 13 Mar 2018 10:03:13 -0700 Subject: [PATCH 07/22] Simplify compareString() and compareStringN() for (__FlashStringHelper*) using pgm_read_byte() which already handles 4-byte alignment on ESP8266 --- src/aunit/Compare.cpp | 51 +++++++++++++---------------------- tests/AUnitTest/AUnitTest.ino | 4 +-- 2 files changed, 20 insertions(+), 35 deletions(-) diff --git a/src/aunit/Compare.cpp b/src/aunit/Compare.cpp index 28c4cee..2d494d5 100644 --- a/src/aunit/Compare.cpp +++ b/src/aunit/Compare.cpp @@ -35,31 +35,20 @@ namespace aunit { int compareString(const __FlashStringHelper* a, const __FlashStringHelper* b) { const char* aa = reinterpret_cast(a); const char* bb = reinterpret_cast(b); - char bufa[4]; - char bufb[4]; - // Using uint8_t instead of size_t is not a bug because we only need the - // least significant 2 bits of the counter. Rolling over the uint8_t counter - // is ok too. - for (uint8_t i = 0; true; i++) { - if ((i & 0x03) == 0) { - // NOTE: There ought to be a simpler, more efficient way of extracting 4 - // characters from flash memory the pointer is already 4-byte aligned. - // But I don't know what it is right now. - memcpy_P(bufa, aa, 4); - memcpy_P(bufb, bb, 4); - aa += 4; - bb += 4; - } - // Avoid '%' operator since some 8-bit processors lack hardware division. - char ca = bufa[i & 0x03]; - char cb = bufb[i & 0x03]; + // On ESP8266, pgm_read_byte() already takes care of 4-byte alignment, and + // memcpy_P(s, p, 4) makes 4 calls to pgm_read_byte() anyway, so don't bother + // optimizing for 4-byte alignment here. + while (true) { + char ca = pgm_read_byte(aa); + char cb = pgm_read_byte(bb); if (ca < cb) return -1; if (ca > cb) return 1; // we hit this condition only if both strings were the same length if (ca == '\0' || cb == '\0') return 0; + aa++; + bb++; } - return 0; } int compareString(const FCString& a, const FCString& b) { @@ -82,24 +71,20 @@ int compareStringN(const __FlashStringHelper* a, const __FlashStringHelper* b, size_t n) { const char* aa = reinterpret_cast(a); const char* bb = reinterpret_cast(b); - char bufa[4]; - char bufb[4]; - for (size_t i = 0; i < n; i++) { - if ((i & 0x03) == 0) { - // NOTE: There ought to be a simpler, more efficient way of extracting 4 - // characters from flash memory the pointer is already 4-byte aligned. - // But I don't know what it is right now. - memcpy_P(bufa, aa, 4); - memcpy_P(bufb, bb, 4); - aa += 4; - bb += 4; - } - char ca = bufa[i & 0x03]; - char cb = bufb[i & 0x03]; + + // On ESP8266, pgm_read_byte() already takes care of 4-byte alignment, and + // memcpy_P(s, p, 4) makes 4 calls to pgm_read_byte() anyway, so don't bother + // optimizing for 4-byte alignment here. + while (n > 0) { + char ca = pgm_read_byte(aa); + char cb = pgm_read_byte(bb); if (ca < cb) return -1; if (ca > cb) return 1; // we hit this condition only if both strings were the same length if (ca == '\0' || cb == '\0') return 0; + aa++; + bb++; + n--; } return 0; } diff --git a/tests/AUnitTest/AUnitTest.ino b/tests/AUnitTest/AUnitTest.ino index 7556b88..9a3ced7 100644 --- a/tests/AUnitTest/AUnitTest.ino +++ b/tests/AUnitTest/AUnitTest.ino @@ -325,11 +325,11 @@ void setup() { while (! Serial); // Wait until Serial is ready - Leonardo #if USE_AUNIT == 1 - TestRunner::setVerbosity(Verbosity::kAll); + //TestRunner::setVerbosity(Verbosity::kAll); TestRunner::exclude("looping_f*"); TestRunner::list(); #else - Test::min_verbosity = TEST_VERBOSITY_ALL; + //Test::min_verbosity = TEST_VERBOSITY_ALL; Test::exclude("looping_f*"); #endif } From 742a6cd06721a54f770eef510d589cf326fefba0 Mon Sep 17 00:00:00 2001 From: Brian Park Date: Tue, 13 Mar 2018 10:09:54 -0700 Subject: [PATCH 08/22] Change AUNIT_VERSION macro from string to integer so that integer comparison can be used in #if statements --- src/AUnit.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/AUnit.h b/src/AUnit.h index 3095933..cb56885 100644 --- a/src/AUnit.h +++ b/src/AUnit.h @@ -32,7 +32,7 @@ SOFTWARE. #include "aunit/TestRunner.h" #include "aunit/Assertion.h" -// Version string format: "010203" == "1.2.3" -#define AUNIT_VERSION "000100" +// Version format: 010203 == "1.2.3" +#define AUNIT_VERSION 000100 #endif From 79eb70de897b1b8a4e51f12eb76774f8f6c9abd9 Mon Sep 17 00:00:00 2001 From: Brian Park Date: Tue, 13 Mar 2018 10:22:20 -0700 Subject: [PATCH 09/22] Tweak TestRunner::runTest() so that a TestOnce test is resolved right after a loop(), instead of having to wait for the entire cycle to come around; also remove extraneous () characters leftover from a previous search-and-replace --- src/aunit/TestRunner.cpp | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/aunit/TestRunner.cpp b/src/aunit/TestRunner.cpp index 7cf470e..3577b0f 100644 --- a/src/aunit/TestRunner.cpp +++ b/src/aunit/TestRunner.cpp @@ -104,26 +104,33 @@ void TestRunner::runTest() { break; case Test::kStatusSetup: (*mCurrent)->loop(); - // skip to the next one, but keep current test in the list - mCurrent = (*mCurrent)->getNext(); + + // If test status is unresolved (i.e. still in kStatusSetup state) after + // loop(), then this is a continuous testing() test case, so skip to the + // next test. Otherwise, stay on the current test so that the next + // iteration can resolve the current test. + if ((*mCurrent)->getStatus() == Test::kStatusSetup) { + // skip to the next one, but keep current test in the list + mCurrent = (*mCurrent)->getNext(); + } break; case Test::kStatusSkipped: mSkippedCount++; resolveTest((*mCurrent)); // skip to the next one by taking current test out of the list - (*mCurrent) = *(*mCurrent)->getNext(); + *mCurrent = *(*mCurrent)->getNext(); break; case Test::kStatusPassed: mPassedCount++; resolveTest((*mCurrent)); // skip to the next one by taking current test out of the list - (*mCurrent) = *(*mCurrent)->getNext(); + *mCurrent = *(*mCurrent)->getNext(); break; case Test::kStatusFailed: mFailedCount++; resolveTest((*mCurrent)); // skip to the next one by taking current test out of the list - (*mCurrent) = *(*mCurrent)->getNext(); + *mCurrent = *(*mCurrent)->getNext(); break; } } From 5c1a497a69bab331ca83e72845155050878b1bd1 Mon Sep 17 00:00:00 2001 From: Brian Park Date: Tue, 13 Mar 2018 10:57:30 -0700 Subject: [PATCH 10/22] README.md: add installation instructions --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index 8f12106..d53d5f8 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,17 @@ AUnit supports exclude and include filters: Currently, only a single `*` wildcard is supported and it must occur at the end if present. +## Installation + +The library will be available in the Arduino IDE Library Manager eventually. + +In the mean time, it can be installed by cloning the +[GitHub repository](https://github.com/bxparks/AUnit), then +manually copying over the contents to the `./libraries` directory used +by the Arduino IDE. (The result is a directory named `./libraries/AUnit`.) +See the Preferences menu for the location of the Arduino sketch directory. +You may need to restart the Arduino IDE to let it see the new library. + ## Usage ### Header and Namespace From 240406e36339cbccb7854ab11c3959e1d94a48ab Mon Sep 17 00:00:00 2001 From: Brian Park Date: Tue, 13 Mar 2018 11:36:01 -0700 Subject: [PATCH 11/22] Move definitions of assertion() into Assertion.cpp; fix some type dependencies in header files; update README.md benchmarks --- README.md | 6 +- src/aunit/Assertion.cpp | 178 +++++++++++++++++++++++++++++++++++++++ src/aunit/Assertion.h | 125 +++++---------------------- src/aunit/FCString.h | 4 + src/aunit/Test.cpp | 3 +- src/aunit/Test.h | 3 +- src/aunit/TestRunner.cpp | 4 +- src/aunit/Verbosity.h | 2 + 8 files changed, 215 insertions(+), 110 deletions(-) create mode 100644 src/aunit/Assertion.cpp diff --git a/README.md b/README.md index d53d5f8..d3b9f10 100644 --- a/README.md +++ b/README.md @@ -500,16 +500,16 @@ microcontrollers: ``` Platform (resource) | Max | ArduinoUnit | AUnit | ----------------------+---------+-------------+-------------| -Arduino Nano (flash) | 30720 | 54038 | 18624 | +Arduino Nano (flash) | 30720 | 54038 | 18416 | Arduino Nano (static) | 2048 | 1061 | 908 | ----------------------+---------+-------------+-------------| -Teensy LC (flash) | 63488 | 36196 | 25120 | +Teensy LC (flash) | 63488 | 36196 | 25088 | Teensy LC (static) | 8192 | 2980 | 2768 | ----------------------+---------+-------------+-------------| Teensy 3.2 (flash) | 262144 | 51236 | 36136 | Teensy 3.2 (static) | 65536 | 5328 | 5224 | ----------------------+---------+-------------+-------------| -ESP8266 (flash) | 1044464 | does not | 267391 | +ESP8266 (flash) | 1044464 | does not | 267343 | ESP8266 (static) | 81920 | compile | 34564 | ----------------------+---------+-------------+-------------| ``` diff --git a/src/aunit/Assertion.cpp b/src/aunit/Assertion.cpp new file mode 100644 index 0000000..13475a7 --- /dev/null +++ b/src/aunit/Assertion.cpp @@ -0,0 +1,178 @@ +/* +MIT License + +Copyright (c) 2018 Brian T. Park + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "Assertion.h" + +namespace aunit { + +bool assertion(const char* file, uint16_t line, bool lhs, + const char* opName, bool (*op)(bool lhs, bool rhs), + bool rhs) { + bool ok = op(lhs, rhs); + printAssertionMessage(ok, file, line, lhs, opName, rhs); + TestRunner::setPassOrFail(ok); + return ok; +} + +bool assertion(const char* file, uint16_t line, char lhs, + const char* opName, bool (*op)(char lhs, char rhs), + char rhs) { + bool ok = op(lhs, rhs); + printAssertionMessage(ok, file, line, lhs, opName, rhs); + TestRunner::setPassOrFail(ok); + return ok; +} + +bool assertion(const char* file, uint16_t line, int lhs, + const char* opName, bool (*op)(int lhs, int rhs), + int rhs) { + bool ok = op(lhs, rhs); + printAssertionMessage(ok, file, line, lhs, opName, rhs); + TestRunner::setPassOrFail(ok); + return ok; +} + +bool assertion(const char* file, uint16_t line, unsigned int lhs, + const char* opName, bool (*op)(unsigned int lhs, unsigned int rhs), + unsigned int rhs) { + bool ok = op(lhs, rhs); + printAssertionMessage(ok, file, line, lhs, opName, rhs); + TestRunner::setPassOrFail(ok); + return ok; +} + +bool assertion(const char* file, uint16_t line, long lhs, + const char* opName, bool (*op)(long lhs, long rhs), + long rhs) { + bool ok = op(lhs, rhs); + printAssertionMessage(ok, file, line, lhs, opName, rhs); + TestRunner::setPassOrFail(ok); + return ok; +} + +bool assertion(const char* file, uint16_t line, unsigned long lhs, + const char* opName, bool (*op)(unsigned long lhs, unsigned long rhs), + unsigned long rhs) { + bool ok = op(lhs, rhs); + printAssertionMessage(ok, file, line, lhs, opName, rhs); + TestRunner::setPassOrFail(ok); + return ok; +} + +bool assertion(const char* file, uint16_t line, double lhs, + const char* opName, bool (*op)(double lhs, double rhs), + double rhs) { + bool ok = op(lhs, rhs); + printAssertionMessage(ok, file, line, lhs, opName, rhs); + TestRunner::setPassOrFail(ok); + return ok; +} + +bool assertion(const char* file, uint16_t line, const char* lhs, + const char* opName, bool (*op)(const char* lhs, const char* rhs), + const char* rhs) { + bool ok = op(lhs, rhs); + printAssertionMessage(ok, file, line, lhs, opName, rhs); + TestRunner::setPassOrFail(ok); + return ok; +} + +bool assertion(const char* file, uint16_t line, const char* lhs, + const char *opName, bool (*op)(const char* lhs, const String& rhs), + const String& rhs) { + bool ok = op(lhs, rhs); + printAssertionMessage(ok, file, line, lhs, opName, rhs); + TestRunner::setPassOrFail(ok); + return ok; +} + +bool assertion(const char* file, uint16_t line, const char* lhs, + const char *opName, + bool (*op)(const char* lhs, const __FlashStringHelper* rhs), + const __FlashStringHelper* rhs) { + bool ok = op(lhs, rhs); + printAssertionMessage(ok, file, line, lhs, opName, rhs); + TestRunner::setPassOrFail(ok); + return ok; +} + +bool assertion(const char* file, uint16_t line, const String& lhs, + const char *opName, bool (*op)(const String& lhs, const char* rhs), + const char* rhs) { + bool ok = op(lhs, rhs); + printAssertionMessage(ok, file, line, lhs, opName, rhs); + TestRunner::setPassOrFail(ok); + return ok; +} + +bool assertion(const char* file, uint16_t line, const String& lhs, + const char *opName, bool (*op)(const String& lhs, const String& rhs), + const String& rhs) { + bool ok = op(lhs, rhs); + printAssertionMessage(ok, file, line, lhs, opName, rhs); + TestRunner::setPassOrFail(ok); + return ok; +} + +bool assertion(const char* file, uint16_t line, const String& lhs, + const char *opName, + bool (*op)(const String& lhs, const __FlashStringHelper* rhs), + const __FlashStringHelper* rhs) { + bool ok = op(lhs, rhs); + printAssertionMessage(ok, file, line, lhs, opName, rhs); + TestRunner::setPassOrFail(ok); + return ok; +} + +bool assertion(const char* file, uint16_t line, + const __FlashStringHelper* lhs, const char *opName, + bool (*op)(const __FlashStringHelper* lhs, const char* rhs), + const char* rhs) { + bool ok = op(lhs, rhs); + printAssertionMessage(ok, file, line, lhs, opName, rhs); + TestRunner::setPassOrFail(ok); + return ok; +} + +bool assertion(const char* file, uint16_t line, + const __FlashStringHelper* lhs, const char *opName, + bool (*op)(const __FlashStringHelper* lhs, const String& rhs), + const String& rhs) { + bool ok = op(lhs, rhs); + printAssertionMessage(ok, file, line, lhs, opName, rhs); + TestRunner::setPassOrFail(ok); + return ok; +} + +bool assertion(const char* file, uint16_t line, + const __FlashStringHelper* lhs, const char *opName, + bool (*op)(const __FlashStringHelper* lhs, const __FlashStringHelper* rhs), + const __FlashStringHelper* rhs) { + bool ok = op(lhs, rhs); + printAssertionMessage(ok, file, line, lhs, opName, rhs); + TestRunner::setPassOrFail(ok); + return ok; +} + +} diff --git a/src/aunit/Assertion.h b/src/aunit/Assertion.h index 25b3d94..b46f384 100644 --- a/src/aunit/Assertion.h +++ b/src/aunit/Assertion.h @@ -28,6 +28,7 @@ SOFTWARE. #ifndef AUNIT_ASSERTION_H #define AUNIT_ASSERTION_H +#include // definition of Print #include "Printer.h" #include "Verbosity.h" #include "TestRunner.h" @@ -104,152 +105,72 @@ void printAssertionMessage(bool ok, const char* file, uint16_t line, bool assertion(const char* file, uint16_t line, bool lhs, const char* opName, bool (*op)(bool lhs, bool rhs), - bool rhs) { - bool ok = op(lhs, rhs); - printAssertionMessage(ok, file, line, lhs, opName, rhs); - TestRunner::setPassOrFail(ok); - return ok; -} + bool rhs); bool assertion(const char* file, uint16_t line, char lhs, const char* opName, bool (*op)(char lhs, char rhs), - char rhs) { - bool ok = op(lhs, rhs); - printAssertionMessage(ok, file, line, lhs, opName, rhs); - TestRunner::setPassOrFail(ok); - return ok; -} + char rhs); bool assertion(const char* file, uint16_t line, int lhs, const char* opName, bool (*op)(int lhs, int rhs), - int rhs) { - bool ok = op(lhs, rhs); - printAssertionMessage(ok, file, line, lhs, opName, rhs); - TestRunner::setPassOrFail(ok); - return ok; -} + int rhs); bool assertion(const char* file, uint16_t line, unsigned int lhs, const char* opName, bool (*op)(unsigned int lhs, unsigned int rhs), - unsigned int rhs) { - bool ok = op(lhs, rhs); - printAssertionMessage(ok, file, line, lhs, opName, rhs); - TestRunner::setPassOrFail(ok); - return ok; -} + unsigned int rhs); bool assertion(const char* file, uint16_t line, long lhs, const char* opName, bool (*op)(long lhs, long rhs), - long rhs) { - bool ok = op(lhs, rhs); - printAssertionMessage(ok, file, line, lhs, opName, rhs); - TestRunner::setPassOrFail(ok); - return ok; -} + long rhs); bool assertion(const char* file, uint16_t line, unsigned long lhs, const char* opName, bool (*op)(unsigned long lhs, unsigned long rhs), - unsigned long rhs) { - bool ok = op(lhs, rhs); - printAssertionMessage(ok, file, line, lhs, opName, rhs); - TestRunner::setPassOrFail(ok); - return ok; -} + unsigned long rhs); bool assertion(const char* file, uint16_t line, double lhs, const char* opName, bool (*op)(double lhs, double rhs), - double rhs) { - bool ok = op(lhs, rhs); - printAssertionMessage(ok, file, line, lhs, opName, rhs); - TestRunner::setPassOrFail(ok); - return ok; -} + double rhs); bool assertion(const char* file, uint16_t line, const char* lhs, const char* opName, bool (*op)(const char* lhs, const char* rhs), - const char* rhs) { - bool ok = op(lhs, rhs); - printAssertionMessage(ok, file, line, lhs, opName, rhs); - TestRunner::setPassOrFail(ok); - return ok; -} + const char* rhs); bool assertion(const char* file, uint16_t line, const char* lhs, const char *opName, bool (*op)(const char* lhs, const String& rhs), - const String& rhs) { - bool ok = op(lhs, rhs); - printAssertionMessage(ok, file, line, lhs, opName, rhs); - TestRunner::setPassOrFail(ok); - return ok; -} + const String& rhs); bool assertion(const char* file, uint16_t line, const char* lhs, const char *opName, bool (*op)(const char* lhs, const __FlashStringHelper* rhs), - const __FlashStringHelper* rhs) { - bool ok = op(lhs, rhs); - printAssertionMessage(ok, file, line, lhs, opName, rhs); - TestRunner::setPassOrFail(ok); - return ok; -} + const __FlashStringHelper* rhs); bool assertion(const char* file, uint16_t line, const String& lhs, const char *opName, bool (*op)(const String& lhs, const char* rhs), - const char* rhs) { - bool ok = op(lhs, rhs); - printAssertionMessage(ok, file, line, lhs, opName, rhs); - TestRunner::setPassOrFail(ok); - return ok; -} + const char* rhs); bool assertion(const char* file, uint16_t line, const String& lhs, const char *opName, bool (*op)(const String& lhs, const String& rhs), - const String& rhs) { - bool ok = op(lhs, rhs); - printAssertionMessage(ok, file, line, lhs, opName, rhs); - TestRunner::setPassOrFail(ok); - return ok; -} + const String& rhs); bool assertion(const char* file, uint16_t line, const String& lhs, const char *opName, bool (*op)(const String& lhs, const __FlashStringHelper* rhs), - const __FlashStringHelper* rhs) { - bool ok = op(lhs, rhs); - printAssertionMessage(ok, file, line, lhs, opName, rhs); - TestRunner::setPassOrFail(ok); - return ok; -} + const __FlashStringHelper* rhs); -bool assertion(const char* file, uint16_t line, const __FlashStringHelper* lhs, - const char *opName, +bool assertion(const char* file, uint16_t line, + const __FlashStringHelper* lhs, const char *opName, bool (*op)(const __FlashStringHelper* lhs, const char* rhs), - const char* rhs) { - bool ok = op(lhs, rhs); - printAssertionMessage(ok, file, line, lhs, opName, rhs); - TestRunner::setPassOrFail(ok); - return ok; -} + const char* rhs); -bool assertion(const char* file, uint16_t line, const __FlashStringHelper* lhs, - const char *opName, +bool assertion(const char* file, uint16_t line, + const __FlashStringHelper* lhs, const char *opName, bool (*op)(const __FlashStringHelper* lhs, const String& rhs), - const String& rhs) { - bool ok = op(lhs, rhs); - printAssertionMessage(ok, file, line, lhs, opName, rhs); - TestRunner::setPassOrFail(ok); - return ok; -} + const String& rhs); -bool assertion(const char* file, uint16_t line, const __FlashStringHelper* lhs, - const char *opName, +bool assertion(const char* file, uint16_t line, + const __FlashStringHelper* lhs, const char *opName, bool (*op)(const __FlashStringHelper* lhs, const __FlashStringHelper* rhs), - const __FlashStringHelper* rhs) { - bool ok = op(lhs, rhs); - printAssertionMessage(ok, file, line, lhs, opName, rhs); - TestRunner::setPassOrFail(ok); - return ok; -} + const __FlashStringHelper* rhs); } #endif diff --git a/src/aunit/FCString.h b/src/aunit/FCString.h index 426f422..9efbce2 100644 --- a/src/aunit/FCString.h +++ b/src/aunit/FCString.h @@ -25,6 +25,10 @@ SOFTWARE. #ifndef AUNIT_FSTRING_H #define AUNIT_FSTRING_H +#include + +class __FlashStringHelper; + namespace aunit { /** diff --git a/src/aunit/Test.cpp b/src/aunit/Test.cpp index 6fe9715..b08ee5c 100644 --- a/src/aunit/Test.cpp +++ b/src/aunit/Test.cpp @@ -22,9 +22,8 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#include "Test.h" - #include // for declaration of 'Serial' on Teensy and others +#include "Test.h" #include "Compare.h" namespace aunit { diff --git a/src/aunit/Test.h b/src/aunit/Test.h index 18b57a3..62dcf09 100644 --- a/src/aunit/Test.h +++ b/src/aunit/Test.h @@ -29,9 +29,10 @@ SOFTWARE. #define AUNIT_TEST_H #include -#include #include "FCString.h" +class __FlashStringHelper; + // On the ESP8266 platform, The F() string cannot be placed in an inline // context, because it interferes with other PROGMEM strings. See // https://github.com/esp8266/Arduino/issues/3369. The solution was to move the diff --git a/src/aunit/TestRunner.cpp b/src/aunit/TestRunner.cpp index 3577b0f..f534a95 100644 --- a/src/aunit/TestRunner.cpp +++ b/src/aunit/TestRunner.cpp @@ -22,9 +22,9 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#include // import 'Serial' +#include // definition of 'Serial' #include - +#include #include "FCString.h" #include "Compare.h" #include "Printer.h" diff --git a/src/aunit/Verbosity.h b/src/aunit/Verbosity.h index bbb5a02..1c06f6b 100644 --- a/src/aunit/Verbosity.h +++ b/src/aunit/Verbosity.h @@ -25,6 +25,8 @@ SOFTWARE. #ifndef AUNIT_VERBOSITY_H #define AUNIT_VERBOSITY_H +#include + namespace aunit { /** From 84f47ccf760c5da62eeb4625c177e77f59ccb22f Mon Sep 17 00:00:00 2001 From: Brian Park Date: Tue, 13 Mar 2018 13:37:29 -0700 Subject: [PATCH 12/22] Move inlined functions from Compare.h to Compare.cpp; update README.md benchmarks --- README.md | 4 +- src/aunit/Compare.cpp | 565 +++++++++++++++++++++++++++++++++++++++++- src/aunit/Compare.h | 540 +++++++++------------------------------- 3 files changed, 675 insertions(+), 434 deletions(-) diff --git a/README.md b/README.md index d3b9f10..2f365ca 100644 --- a/README.md +++ b/README.md @@ -503,13 +503,13 @@ Platform (resource) | Max | ArduinoUnit | AUnit | Arduino Nano (flash) | 30720 | 54038 | 18416 | Arduino Nano (static) | 2048 | 1061 | 908 | ----------------------+---------+-------------+-------------| -Teensy LC (flash) | 63488 | 36196 | 25088 | +Teensy LC (flash) | 63488 | 36196 | 25092 | Teensy LC (static) | 8192 | 2980 | 2768 | ----------------------+---------+-------------+-------------| Teensy 3.2 (flash) | 262144 | 51236 | 36136 | Teensy 3.2 (static) | 65536 | 5328 | 5224 | ----------------------+---------+-------------+-------------| -ESP8266 (flash) | 1044464 | does not | 267343 | +ESP8266 (flash) | 1044464 | does not | 267375 | ESP8266 (static) | 81920 | compile | 34564 | ----------------------+---------+-------------+-------------| ``` diff --git a/src/aunit/Compare.cpp b/src/aunit/Compare.cpp index 2d494d5..768a3d0 100644 --- a/src/aunit/Compare.cpp +++ b/src/aunit/Compare.cpp @@ -22,16 +22,155 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/* +Design Notes: +============ +This file provides overloaded compareXxx(a, b) functions which are used by +the various assertXxx() macros. A primary goal of this file is to allow users +to use the assertXxx() macros with all combinations of the 3 types of strings +available in the Arduino platform: + + - (const char *) + - (String&) + - (const __FlashStringHelper*) + +Clearly, there are 9 binary combinations these string types. + +Template Specialization: +----------------------- +One way to implement the compareEqual() for these types is to use template +specialization. The problem with Template specialization is that templates +use strict type matching, and does not perform the normal implicit type +conversion, including const-casting. Therefore, all of the various c-style +string types, for example: + + - char* + - const char* + - char[1] + - char[N] + - const char[1] + - const char[N] + +are considered to be different types under the C++ templating system. This +causes a combinatorial explosion of template specialization which produces +code that is difficult to understand, test and maintain. +An example can be seen in the Compare.h file of the ArduinoUnit project: +https://github.com/mmurdoch/arduinounit/blob/master/src/ArduinoUnitUtility/Compare.h + +Function Overloading: +--------------------- +In this project, I used function overloading instead of template +specialization. Function overloading handles c-style strings (i.e. character +arrays) naturally, in the way most users expect. For example, (char*) is +automarically cast to (const char*), and (char[N]) is autonmatically +cast to (const char*). + +For the primitive value types (e.g. (char), (int), (unsigned char), etc.) I +attempted to use a generic templatized version, using sonmething like: + + template + compareEqual(const T& a, const T& b) { ... } + +However, this template introduced this method: + + compareEqual(char* const& a, char* const& b); + +that seemed to take precedence over the explicitly defined overload: + + compareEqual(const char* a, const char*b); + +When the compareEqual() method is called with a (char*) or a (char[N]), +like this: + + char a[3] = {...}; + char b[4] = {...}; + compareEqual(a, b); + +this calls compareEqual(char* const&, const* const&), which is the wrong +version for a c-style string. The only way I could get this to work was to +avoid templates completely and manually define all the function overloads +even for primitive integer types. + +Implicit Conversions: +--------------------- +For basic primitive types, I depend on some casts to avoid having to define +some functions. I assume that signed and unsigned intergers smaller or equal +to (int) will be converted to an (int) to match compareEqual(int, int). + +I provided an explicit compareEqual(char, char) overload because in C++, a +(char) type is distinct from (signed char) and (unsigned char). + +Technically, there should be a (long long) version and an (unsigned long +long) version of compareEqual(). However, it turns out that the Arduino +Print::print() method does not have an overload for these types, so it would +not do us much good to provide an assertEqual() or compareEqual() for the +(long long) and (unsigned long long) types. + +Custom Assert and Compare Functions: +------------------------------------ +Another advantage of using function overloading instead of template +specialization is that the user is able to add additional function overloads +into the 'aunit' namespace. This should allow the user to define the various +comporeXxx() and assertXxx() functions for a custom class. I have not +tested this though. + +Comparing Flash Strings: +---------------------- +Flash memory must be read using 4-byte alignment on the ESP8266. AVR doesn't +care. Teensy-ARM fakes the flash memory API but really just uses the normal +static RAM. The following code for comparing two (__FlashStringHelper*) +against each other will work for all 3 environments. + +Inlining: +-------- +Even though most of these functions are one-liners, there is no advantage to +inlining them because they are almost always used through a function pointer. +*/ + #include +#include + +#ifdef ESP8266 +#include +#else +#include +#endif + #include "Compare.h" #include "FCString.h" -// Flash memory must be read using 4-byte alignment on the ESP8266. AVR doesn't -// care. Teensy-ARM fakes the flash memory API but really just uses the normal -// static RAM. The following code will work for all 3 environments. - namespace aunit { +// compareString() + +int compareString(const char* a, const char* b) { + return strcmp(a, b); +} + +int compareString(const char* a, const String& b) { + return strcmp(a, b.c_str()); +} + +int compareString(const char* a, const __FlashStringHelper* b) { + return strcmp_P(a, (const char*)b); +} + +int compareString(const String& a, const char* b) { + return strcmp(a.c_str(), b); +} + +int compareString(const String& a, const String& b) { + return a.compareTo(b); +} + +int compareString(const String& a, const __FlashStringHelper* b) { + return strcmp_P(a.c_str(), (const char*)b); +} + +int compareString(const __FlashStringHelper* a, const char* b) { + return -strcmp_P(b, (const char*) a); +} + int compareString(const __FlashStringHelper* a, const __FlashStringHelper* b) { const char* aa = reinterpret_cast(a); const char* bb = reinterpret_cast(b); @@ -51,6 +190,10 @@ int compareString(const __FlashStringHelper* a, const __FlashStringHelper* b) { } } +int compareString(const __FlashStringHelper* a, const String& b) { + return -strcmp_P(b.c_str(), (const char*)a); +} + int compareString(const FCString& a, const FCString& b) { if (a.getType() == FCString::kCStringType) { if (b.getType() == FCString::kCStringType) { @@ -67,6 +210,20 @@ int compareString(const FCString& a, const FCString& b) { } } +// compareStringN() + +int compareStringN(const char* a, const char* b, size_t n) { + return strncmp(a, b, n); +} + +int compareStringN(const char* a, const __FlashStringHelper* b, size_t n) { + return strncmp_P(a, (const char*)b, n); +} + +int compareStringN(const __FlashStringHelper* a, const char* b, size_t n) { + return -strncmp_P(b, (const char*)a, n); +} + int compareStringN(const __FlashStringHelper* a, const __FlashStringHelper* b, size_t n) { const char* aa = reinterpret_cast(a); @@ -105,4 +262,404 @@ int compareStringN(const FCString& a, const __FlashStringHelper* b, size_t n) { } } +// compareEqual() + +bool compareEqual(bool a, bool b) { + return (a == b); +} + +bool compareEqual(char a, char b) { + return (a == b); +} + +bool compareEqual(int a, int b) { + return (a == b); +} + +bool compareEqual(unsigned int a, unsigned int b) { + return (a == b); +} + +bool compareEqual(long a, long b) { + return (a == b); +} + +bool compareEqual(unsigned long a, unsigned long b) { + return (a == b); +} + +bool compareEqual(double a, double b) { + return (a == b); +} + +bool compareEqual(const char* a, const char* b) { + return compareString(a, b) == 0; +} + +bool compareEqual(const char* a, const String& b) { + return compareString(a, b) == 0; +} + +bool compareEqual(const char* a, const __FlashStringHelper* b) { + return compareString(a, b) == 0; +} + +bool compareEqual(const __FlashStringHelper* a, const char* b) { + return compareString(a, b) == 0; +} + +bool compareEqual(const __FlashStringHelper* a, const __FlashStringHelper* b) { + return compareString(a, b) == 0; +} + +bool compareEqual(const __FlashStringHelper* a, const String& b) { + return compareString(a, b) == 0; +} + +bool compareEqual(const String& a, const char* b) { + return compareString(a, b) == 0; +} + +bool compareEqual(const String& a, const String& b) { + return compareString(a, b) == 0; +} + +bool compareEqual(const String& a, const __FlashStringHelper* b) { + return compareString(a, b) == 0; +} + +// compareLess() + +bool compareLess(bool a, bool b) { + return (a < b); +} + +bool compareLess(char a, char b) { + return (a < b); +} + +bool compareLess(int a, int b) { + return (a < b); +} + +bool compareLess(unsigned int a, unsigned int b) { + return (a < b); +} + +bool compareLess(long a, long b) { + return (a < b); +} + +bool compareLess(unsigned long a, unsigned long b) { + return (a < b); +} + +bool compareLess(double a, double b) { + return (a < b); +} + +bool compareLess(const char* a, const char* b) { + return compareString(a, b) < 0; +} + +bool compareLess(const char* a, const String& b) { + return compareString(a, b) < 0; +} + +bool compareLess(const char* a, const __FlashStringHelper* b) { + return compareString(a, b) < 0; +} + +bool compareLess(const __FlashStringHelper* a, const char* b) { + return compareString(a, b) < 0; +} + +bool compareLess( + const __FlashStringHelper* a, const __FlashStringHelper* b) { + return compareString(a, b) < 0; +} + +bool compareLess(const __FlashStringHelper* a, const String& b) { + return compareString(a, b) < 0; +} + +bool compareLess(const String& a, const char* b) { + return compareString(a, b) < 0; +} + +bool compareLess(const String& a, const String& b) { + return compareString(a, b) < 0; +} + +bool compareLess(const String& a, const __FlashStringHelper* b) { + return compareString(a, b) < 0; +} + +// compareMore() + +bool compareMore(bool a, bool b) { + return (a > b); +} + +bool compareMore(char a, char b) { + return (a > b); +} + +bool compareMore(int a, int b) { + return (a > b); +} + +bool compareMore(unsigned int a, unsigned int b) { + return (a > b); +} + +bool compareMore(long a, long b) { + return (a > b); +} + +bool compareMore(unsigned long a, unsigned long b) { + return (a > b); +} + +bool compareMore(double a, double b) { + return (a > b); +} + +bool compareMore(const char* a, const char* b) { + return compareString(a, b) > 0; +} + +bool compareMore(const char* a, const String& b) { + return compareString(a, b) > 0; +} + +bool compareMore(const char* a, const __FlashStringHelper* b) { + return compareString(a, b) > 0; +} + +bool compareMore(const __FlashStringHelper* a, const char* b) { + return compareString(a, b) > 0; +} + +bool compareMore(const __FlashStringHelper* a, const __FlashStringHelper* b) { + return compareString(a, b) > 0; +} + +bool compareMore(const __FlashStringHelper* a, const String& b) { + return compareString(a, b) > 0; +} + +bool compareMore(const String& a, const char* b) { + return compareString(a, b) > 0; +} + +bool compareMore(const String& a, const String& b) { + return compareString(a, b) > 0; +} + +bool compareMore(const String& a, const __FlashStringHelper* b) { + return compareString(a, b) > 0; +} + +// compareLessOrEqual + +bool compareLessOrEqual(bool a, bool b) { + return (a <= b); +} + +bool compareLessOrEqual(char a, char b) { + return (a <= b); +} + +bool compareLessOrEqual(int a, int b) { + return (a <= b); +} + +bool compareLessOrEqual(unsigned int a, unsigned int b) { + return (a <= b); +} + +bool compareLessOrEqual(long a, long b) { + return (a <= b); +} + +bool compareLessOrEqual(unsigned long a, unsigned long b) { + return (a <= b); +} + +bool compareLessOrEqual(double a, double b) { + return (a <= b); +} + +bool compareLessOrEqual(const char* a, const char* b) { + return compareString(a, b) <= 0; +} + +bool compareLessOrEqual(const char* a, const String& b) { + return compareString(a, b) <= 0; +} + +bool compareLessOrEqual(const char* a, const __FlashStringHelper* b) { + return compareString(a, b) <= 0; +} + +bool compareLessOrEqual(const __FlashStringHelper* a, const char* b) { + return compareString(a, b) <= 0; +} + +bool compareLessOrEqual( + const __FlashStringHelper* a, const __FlashStringHelper* b) { + return compareString(a, b) <= 0; +} + +bool compareLessOrEqual(const __FlashStringHelper* a, const String& b) { + return compareString(a, b) <= 0; +} + +bool compareLessOrEqual(const String& a, const char* b) { + return compareString(a, b) <= 0; +} + +bool compareLessOrEqual(const String& a, const String& b) { + return compareString(a, b) <= 0; +} + +bool compareLessOrEqual(const String& a, const __FlashStringHelper* b) { + return compareString(a, b) <= 0; +} + +// compareMoreOrEqual + +bool compareMoreOrEqual(bool a, bool b) { + return (a >= b); +} + +bool compareMoreOrEqual(char a, char b) { + return (a >= b); +} + +bool compareMoreOrEqual(int a, int b) { + return (a >= b); +} + +bool compareMoreOrEqual(unsigned int a, unsigned int b) { + return (a >= b); +} + +bool compareMoreOrEqual(long a, long b) { + return (a >= b); +} + +bool compareMoreOrEqual(unsigned long a, unsigned long b) { + return (a >= b); +} + +bool compareMoreOrEqual(double a, double b) { + return (a >= b); +} + +bool compareMoreOrEqual(const char* a, const char* b) { + return compareString(a, b) >= 0; +} + +bool compareMoreOrEqual(const char* a, const String& b) { + return compareString(a, b) >= 0; +} + +bool compareMoreOrEqual(const char* a, const __FlashStringHelper* b) { + return compareString(a, b) >= 0; +} + +bool compareMoreOrEqual(const __FlashStringHelper* a, const char* b) { + return compareString(a, b) >= 0; +} + +bool compareMoreOrEqual( + const __FlashStringHelper* a, const __FlashStringHelper* b) { + return compareString(a, b) >= 0; +} + +bool compareMoreOrEqual(const __FlashStringHelper* a, const String& b) { + return compareString(a, b) >= 0; +} + +bool compareMoreOrEqual(const String& a, const char* b) { + return compareString(a, b) >= 0; +} + +bool compareMoreOrEqual(const String& a, const String& b) { + return compareString(a, b) >= 0; +} + +bool compareMoreOrEqual(const String& a, const __FlashStringHelper* b) { + return compareString(a, b) >= 0; +} + +// compareNotEqual + +bool compareNotEqual(bool a, bool b) { + return (a != b); +} + +bool compareNotEqual(char a, char b) { + return (a != b); +} + +bool compareNotEqual(int a, int b) { + return (a != b); +} + +bool compareNotEqual(unsigned int a, unsigned int b) { + return (a != b); +} + +bool compareNotEqual(long a, long b) { + return (a != b); +} + +bool compareNotEqual(unsigned long a, unsigned long b) { + return (a != b); +} + +bool compareNotEqual(double a, double b) { + return (a != b); +} + +bool compareNotEqual(const char* a, const char* b) { + return compareString(a, b) != 0; +} + +bool compareNotEqual(const char* a, const String& b) { + return compareString(a, b) != 0; +} + +bool compareNotEqual(const char* a, const __FlashStringHelper* b) { + return compareString(a, b) != 0; +} + +bool compareNotEqual(const __FlashStringHelper* a, const char* b) { + return compareString(a, b) != 0; +} + +bool compareNotEqual( + const __FlashStringHelper* a, const __FlashStringHelper* b) { + return compareString(a, b) != 0; +} + +bool compareNotEqual(const __FlashStringHelper* a, const String& b) { + return compareString(a, b) != 0; +} + +bool compareNotEqual(const String& a, const char* b) { + return compareString(a, b) != 0; +} + +bool compareNotEqual(const String& a, const String& b) { + return compareString(a, b) != 0; +} + +bool compareNotEqual(const String& a, const __FlashStringHelper* b) { + return compareString(a, b) != 0; +} + } diff --git a/src/aunit/Compare.h b/src/aunit/Compare.h index 1904dcb..957711e 100644 --- a/src/aunit/Compare.h +++ b/src/aunit/Compare.h @@ -28,144 +28,31 @@ SOFTWARE. #ifndef AUNIT_COMPARE_H #define AUNIT_COMPARE_H -#ifdef ESP8266 -#include -#else -#include -#endif - -#include -#include - +class String; +class __FlashStringHelper; class FCString; -/* - * This file provides overloaded compareXxx(a, b) functions which are used by - * the various assertXxx() macros. A primary goal of this file is to allow users - * to use the assertXxx() macros with all combinations of the 3 types of strings - * available in the Arduino platform: - * - * - (const char *) - * - (String&) - * - (const __FlashStringHelper*) - * - * Clearly, there are 9 binary combinations these string types. - * - * Template Specialization: - * ----------------------- - * One way to implement the compareEqual() for these types is to use template - * specialization. The problem with Template specialization is that templates - * use strict type matching, and does not perform the normal implicit type - * conversion, including const-casting. Therefore, all of the various c-style - * string types, for example: - * - * - char* - * - const char* - * - char[1] - * - char[N] - * - const char[1] - * - const char[N] - * - * are considered to be different types under the C++ templating system. This - * causes a combinatorial explosion of template specialization which produces - * code that is difficult to understand, test and maintain. - * An example can be seen in the Compare.h file of the ArduinoUnit project: - * https://github.com/mmurdoch/arduinounit/blob/master/src/ArduinoUnitUtility/Compare.h - * - * Function Overloading: - * --------------------- - * In this project, I used function overloading instead of template - * specialization. Function overloading handles c-style strings (i.e. character - * arrays) naturally, in the way most users expect. For example, (char*) is - * automarically cast to (const char*), and (char[N]) is autonmatically - * cast to (const char*). - * - * For the primitive value types (e.g. (char), (int), (unsigned char), etc.) I - * attempted to use a generic templatized version, using sonmething like: - * - * template - * compareEqual(const T& a, const T& b) { ... } - * - * However, this template introduced this method: - * - * compareEqual(char* const& a, char* const& b); - * - * that seemed to take precedence over the explicitly defined overload: - * - * compareEqual(const char* a, const char*b); - * - * When the compareEqual() method is called with a (char*) or a (char[N]), - * like this: - * - * char a[3] = {...}; - * char b[4] = {...}; - * compareEqual(a, b); - * - * this calls compareEqual(char* const&, const* const&), which is the wrong - * version for a c-style string. The only way I could get this to work was to - * avoid templates completely and manually define all the function overloads - * even for primitive integer types. - * - * Implicit Conversions: - * --------------------- - * For basic primitive types, I depend on some casts to avoid having to define - * some functions. I assume that signed and unsigned intergers smaller or equal - * to (int) will be converted to an (int) to match compareEqual(int, int). - * - * I provided an explicit compareEqual(char, char) overload because in C++, a - * (char) type is distinct from (signed char) and (unsigned char). - * - * Technically, there should be a (long long) version and an (unsigned long - * long) version of compareEqual(). However, it turns out that the Arduino - * Print::print() method does not have an overload for these types, so it would - * not do us much good to provide an assertEqual() or compareEqual() for the - * (long long) and (unsigned long long) types. - * - * Custom Assert and Compare Functions: - * ------------------------------------ - * Another advantage of using function overloading instead of template - * specialization is that the user is able to add additional function overloads - * into the 'aunit' namespace. This should allow the user to define the various - * comporeXxx() and assertXxx() functions for a custom class. I have not - * tested this though. - */ namespace aunit { // compareString() -inline int compareString(const char* a, const char* b) { - return strcmp(a, b); -} +int compareString(const char* a, const char* b); -inline int compareString(const char* a, const String& b) { - return strcmp(a, b.c_str()); -} +int compareString(const char* a, const String& b); -inline int compareString(const char* a, const __FlashStringHelper* b) { - return strcmp_P(a, (const char*)b); -} +int compareString(const char* a, const __FlashStringHelper* b); -inline int compareString(const String& a, const char* b) { - return strcmp(a.c_str(), b); -} +int compareString(const String& a, const char* b); -inline int compareString(const String& a, const String& b) { - return a.compareTo(b); -} +int compareString(const String& a, const String& b); -inline int compareString(const String& a, const __FlashStringHelper* b) { - return strcmp_P(a.c_str(), (const char*)b); -} +int compareString(const String& a, const __FlashStringHelper* b); -inline int compareString(const __FlashStringHelper* a, const char* b) { - return -strcmp_P(b, (const char*) a); -} +int compareString(const __FlashStringHelper* a, const char* b); int compareString(const __FlashStringHelper* a, const __FlashStringHelper* b); -inline int compareString(const __FlashStringHelper* a, const String& b) { - return -strcmp_P(b.c_str(), (const char*)a); -} +int compareString(const __FlashStringHelper* a, const String& b); int compareString(const FCString& a, const FCString& b); @@ -175,21 +62,13 @@ int compareString(const FCString& a, const FCString& b); // TestRunner::include() features. /** Compare only the first n characters of 'a' or 'b'. */ -inline int compareStringN(const char* a, const char* b, size_t n) { - return strncmp(a, b, n); -} +int compareStringN(const char* a, const char* b, size_t n); /** Compare only the first n characters of 'a' or 'b'. */ -inline int compareStringN(const char* a, const __FlashStringHelper* b, - size_t n) { - return strncmp_P(a, (const char*)b, n); -} +int compareStringN(const char* a, const __FlashStringHelper* b, size_t n); /** Compare only the first n characters of 'a' or 'b'. */ -inline int compareStringN(const __FlashStringHelper* a, const char* b, - size_t n) { - return -strncmp_P(b, (const char*)a, n); -} +int compareStringN(const __FlashStringHelper* a, const char* b, size_t n); /** Compare only the first n characters of 'a' or 'b'. */ int compareStringN(const __FlashStringHelper* a, const __FlashStringHelper* b, @@ -203,405 +82,210 @@ int compareStringN(const FCString& a, const __FlashStringHelper* b, size_t n); // compareEqual() -inline bool compareEqual(bool a, bool b) { - return (a == b); -} +bool compareEqual(bool a, bool b); -inline bool compareEqual(char a, char b) { - return (a == b); -} +bool compareEqual(char a, char b); -inline bool compareEqual(int a, int b) { - return (a == b); -} +bool compareEqual(int a, int b); -inline bool compareEqual(unsigned int a, unsigned int b) { - return (a == b); -} +bool compareEqual(unsigned int a, unsigned int b); -inline bool compareEqual(long a, long b) { - return (a == b); -} +bool compareEqual(long a, long b); -inline bool compareEqual(unsigned long a, unsigned long b) { - return (a == b); -} +bool compareEqual(unsigned long a, unsigned long b); -inline bool compareEqual(double a, double b) { - return (a == b); -} +bool compareEqual(double a, double b); -inline bool compareEqual(const char* a, const char* b) { - return compareString(a, b) == 0; -} +bool compareEqual(const char* a, const char* b); -inline bool compareEqual(const char* a, const String& b) { - return compareString(a, b) == 0; -} +bool compareEqual(const char* a, const String& b); -inline bool compareEqual(const char* a, const __FlashStringHelper* b) { - return compareString(a, b) == 0; -} +bool compareEqual(const char* a, const __FlashStringHelper* b); -inline bool compareEqual(const __FlashStringHelper* a, const char* b) { - return compareString(a, b) == 0; -} +bool compareEqual(const __FlashStringHelper* a, const char* b); -inline bool compareEqual( - const __FlashStringHelper* a, const __FlashStringHelper* b) { - return compareString(a, b) == 0; -} +bool compareEqual( const __FlashStringHelper* a, const __FlashStringHelper* b); -inline bool compareEqual(const __FlashStringHelper* a, const String& b) { - return compareString(a, b) == 0; -} +bool compareEqual(const __FlashStringHelper* a, const String& b); -inline bool compareEqual(const String& a, const char* b) { - return compareString(a, b) == 0; -} +bool compareEqual(const String& a, const char* b); -inline bool compareEqual(const String& a, const String& b) { - return compareString(a, b) == 0; -} +bool compareEqual(const String& a, const String& b); -inline bool compareEqual(const String& a, const __FlashStringHelper* b) { - return compareString(a, b) == 0; -} +bool compareEqual(const String& a, const __FlashStringHelper* b); // compareLess() -inline bool compareLess(bool a, bool b) { - return (a < b); -} +bool compareLess(bool a, bool b); -inline bool compareLess(char a, char b) { - return (a < b); -} +bool compareLess(char a, char b); -inline bool compareLess(int a, int b) { - return (a < b); -} +bool compareLess(int a, int b); -inline bool compareLess(unsigned int a, unsigned int b) { - return (a < b); -} +bool compareLess(unsigned int a, unsigned int b); -inline bool compareLess(long a, long b) { - return (a < b); -} +bool compareLess(long a, long b); -inline bool compareLess(unsigned long a, unsigned long b) { - return (a < b); -} +bool compareLess(unsigned long a, unsigned long b); -inline bool compareLess(double a, double b) { - return (a < b); -} +bool compareLess(double a, double b); -inline bool compareLess(const char* a, const char* b) { - return compareString(a, b) < 0; -} +bool compareLess(const char* a, const char* b); -inline bool compareLess(const char* a, const String& b) { - return compareString(a, b) < 0; -} +bool compareLess(const char* a, const String& b); -inline bool compareLess(const char* a, const __FlashStringHelper* b) { - return compareString(a, b) < 0; -} +bool compareLess(const char* a, const __FlashStringHelper* b); -inline bool compareLess(const __FlashStringHelper* a, const char* b) { - return compareString(a, b) < 0; -} +bool compareLess(const __FlashStringHelper* a, const char* b); -inline bool compareLess( - const __FlashStringHelper* a, const __FlashStringHelper* b) { - return compareString(a, b) < 0; -} +bool compareLess(const __FlashStringHelper* a, const __FlashStringHelper* b); -inline bool compareLess(const __FlashStringHelper* a, const String& b) { - return compareString(a, b) < 0; -} +bool compareLess(const __FlashStringHelper* a, const String& b); -inline bool compareLess(const String& a, const char* b) { - return compareString(a, b) < 0; -} +bool compareLess(const String& a, const char* b); -inline bool compareLess(const String& a, const String& b) { - return compareString(a, b) < 0; -} +bool compareLess(const String& a, const String& b); -inline bool compareLess(const String& a, const __FlashStringHelper* b) { - return compareString(a, b) < 0; -} +bool compareLess(const String& a, const __FlashStringHelper* b); // compareMore() -inline bool compareMore(bool a, bool b) { - return (a > b); -} +bool compareMore(bool a, bool b); -inline bool compareMore(char a, char b) { - return (a > b); -} +bool compareMore(char a, char b); -inline bool compareMore(int a, int b) { - return (a > b); -} +bool compareMore(int a, int b); -inline bool compareMore(unsigned int a, unsigned int b) { - return (a > b); -} +bool compareMore(unsigned int a, unsigned int b); -inline bool compareMore(long a, long b) { - return (a > b); -} +bool compareMore(long a, long b); -inline bool compareMore(unsigned long a, unsigned long b) { - return (a > b); -} +bool compareMore(unsigned long a, unsigned long b); -inline bool compareMore(double a, double b) { - return (a > b); -} +bool compareMore(double a, double b); -inline bool compareMore(const char* a, const char* b) { - return compareString(a, b) > 0; -} +bool compareMore(const char* a, const char* b); -inline bool compareMore(const char* a, const String& b) { - return compareString(a, b) > 0; -} +bool compareMore(const char* a, const String& b); -inline bool compareMore(const char* a, const __FlashStringHelper* b) { - return compareString(a, b) > 0; -} +bool compareMore(const char* a, const __FlashStringHelper* b); -inline bool compareMore(const __FlashStringHelper* a, const char* b) { - return compareString(a, b) > 0; -} +bool compareMore(const __FlashStringHelper* a, const char* b); -inline bool compareMore( - const __FlashStringHelper* a, const __FlashStringHelper* b) { - return compareString(a, b) > 0; -} +bool compareMore(const __FlashStringHelper* a, const __FlashStringHelper* b); -inline bool compareMore(const __FlashStringHelper* a, const String& b) { - return compareString(a, b) > 0; -} +bool compareMore(const __FlashStringHelper* a, const String& b); -inline bool compareMore(const String& a, const char* b) { - return compareString(a, b) > 0; -} +bool compareMore(const String& a, const char* b); -inline bool compareMore(const String& a, const String& b) { - return compareString(a, b) > 0; -} +bool compareMore(const String& a, const String& b); -inline bool compareMore(const String& a, const __FlashStringHelper* b) { - return compareString(a, b) > 0; -} +bool compareMore(const String& a, const __FlashStringHelper* b); // compareLessOrEqual -inline bool compareLessOrEqual(bool a, bool b) { - return (a <= b); -} +bool compareLessOrEqual(bool a, bool b); -inline bool compareLessOrEqual(char a, char b) { - return (a <= b); -} +bool compareLessOrEqual(char a, char b); -inline bool compareLessOrEqual(int a, int b) { - return (a <= b); -} +bool compareLessOrEqual(int a, int b); -inline bool compareLessOrEqual(unsigned int a, unsigned int b) { - return (a <= b); -} +bool compareLessOrEqual(unsigned int a, unsigned int b); -inline bool compareLessOrEqual(long a, long b) { - return (a <= b); -} +bool compareLessOrEqual(long a, long b); -inline bool compareLessOrEqual(unsigned long a, unsigned long b) { - return (a <= b); -} +bool compareLessOrEqual(unsigned long a, unsigned long b); -inline bool compareLessOrEqual(double a, double b) { - return (a <= b); -} +bool compareLessOrEqual(double a, double b); -inline bool compareLessOrEqual(const char* a, const char* b) { - return compareString(a, b) <= 0; -} +bool compareLessOrEqual(const char* a, const char* b); -inline bool compareLessOrEqual(const char* a, const String& b) { - return compareString(a, b) <= 0; -} +bool compareLessOrEqual(const char* a, const String& b); -inline bool compareLessOrEqual(const char* a, const __FlashStringHelper* b) { - return compareString(a, b) <= 0; -} +bool compareLessOrEqual(const char* a, const __FlashStringHelper* b); -inline bool compareLessOrEqual(const __FlashStringHelper* a, const char* b) { - return compareString(a, b) <= 0; -} +bool compareLessOrEqual(const __FlashStringHelper* a, const char* b); -inline bool compareLessOrEqual( - const __FlashStringHelper* a, const __FlashStringHelper* b) { - return compareString(a, b) <= 0; -} +bool compareLessOrEqual( + const __FlashStringHelper* a, const __FlashStringHelper* b); -inline bool compareLessOrEqual(const __FlashStringHelper* a, const String& b) { - return compareString(a, b) <= 0; -} +bool compareLessOrEqual(const __FlashStringHelper* a, const String& b); -inline bool compareLessOrEqual(const String& a, const char* b) { - return compareString(a, b) <= 0; -} +bool compareLessOrEqual(const String& a, const char* b); -inline bool compareLessOrEqual(const String& a, const String& b) { - return compareString(a, b) <= 0; -} +bool compareLessOrEqual(const String& a, const String& b); -inline bool compareLessOrEqual(const String& a, const __FlashStringHelper* b) { - return compareString(a, b) <= 0; -} +bool compareLessOrEqual(const String& a, const __FlashStringHelper* b); // compareMoreOrEqual -inline bool compareMoreOrEqual(bool a, bool b) { - return (a >= b); -} +bool compareMoreOrEqual(bool a, bool b); -inline bool compareMoreOrEqual(char a, char b) { - return (a >= b); -} +bool compareMoreOrEqual(char a, char b); -inline bool compareMoreOrEqual(int a, int b) { - return (a >= b); -} +bool compareMoreOrEqual(int a, int b); -inline bool compareMoreOrEqual(unsigned int a, unsigned int b) { - return (a >= b); -} +bool compareMoreOrEqual(unsigned int a, unsigned int b); -inline bool compareMoreOrEqual(long a, long b) { - return (a >= b); -} +bool compareMoreOrEqual(long a, long b); -inline bool compareMoreOrEqual(unsigned long a, unsigned long b) { - return (a >= b); -} +bool compareMoreOrEqual(unsigned long a, unsigned long b); -inline bool compareMoreOrEqual(double a, double b) { - return (a >= b); -} +bool compareMoreOrEqual(double a, double b); -inline bool compareMoreOrEqual(const char* a, const char* b) { - return compareString(a, b) >= 0; -} +bool compareMoreOrEqual(const char* a, const char* b); -inline bool compareMoreOrEqual(const char* a, const String& b) { - return compareString(a, b) >= 0; -} +bool compareMoreOrEqual(const char* a, const String& b); -inline bool compareMoreOrEqual(const char* a, const __FlashStringHelper* b) { - return compareString(a, b) >= 0; -} +bool compareMoreOrEqual(const char* a, const __FlashStringHelper* b); -inline bool compareMoreOrEqual(const __FlashStringHelper* a, const char* b) { - return compareString(a, b) >= 0; -} +bool compareMoreOrEqual(const __FlashStringHelper* a, const char* b); -inline bool compareMoreOrEqual( - const __FlashStringHelper* a, const __FlashStringHelper* b) { - return compareString(a, b) >= 0; -} +bool compareMoreOrEqual( + const __FlashStringHelper* a, const __FlashStringHelper* b); -inline bool compareMoreOrEqual(const __FlashStringHelper* a, const String& b) { - return compareString(a, b) >= 0; -} +bool compareMoreOrEqual(const __FlashStringHelper* a, const String& b); -inline bool compareMoreOrEqual(const String& a, const char* b) { - return compareString(a, b) >= 0; -} +bool compareMoreOrEqual(const String& a, const char* b); -inline bool compareMoreOrEqual(const String& a, const String& b) { - return compareString(a, b) >= 0; -} +bool compareMoreOrEqual(const String& a, const String& b); -inline bool compareMoreOrEqual(const String& a, const __FlashStringHelper* b) { - return compareString(a, b) >= 0; -} +bool compareMoreOrEqual(const String& a, const __FlashStringHelper* b); // compareNotEqual -inline bool compareNotEqual(bool a, bool b) { - return (a != b); -} +bool compareNotEqual(bool a, bool b); -inline bool compareNotEqual(char a, char b) { - return (a != b); -} +bool compareNotEqual(char a, char b); -inline bool compareNotEqual(int a, int b) { - return (a != b); -} +bool compareNotEqual(int a, int b); -inline bool compareNotEqual(unsigned int a, unsigned int b) { - return (a != b); -} +bool compareNotEqual(unsigned int a, unsigned int b); -inline bool compareNotEqual(long a, long b) { - return (a != b); -} +bool compareNotEqual(long a, long b); -inline bool compareNotEqual(unsigned long a, unsigned long b) { - return (a != b); -} +bool compareNotEqual(unsigned long a, unsigned long b); -inline bool compareNotEqual(double a, double b) { - return (a != b); -} +bool compareNotEqual(double a, double b); -inline bool compareNotEqual(const char* a, const char* b) { - return compareString(a, b) != 0; -} +bool compareNotEqual(const char* a, const char* b); -inline bool compareNotEqual(const char* a, const String& b) { - return compareString(a, b) != 0; -} +bool compareNotEqual(const char* a, const String& b); -inline bool compareNotEqual(const char* a, const __FlashStringHelper* b) { - return compareString(a, b) != 0; -} +bool compareNotEqual(const char* a, const __FlashStringHelper* b); -inline bool compareNotEqual(const __FlashStringHelper* a, const char* b) { - return compareString(a, b) != 0; -} +bool compareNotEqual(const __FlashStringHelper* a, const char* b); -inline bool compareNotEqual( - const __FlashStringHelper* a, const __FlashStringHelper* b) { - return compareString(a, b) != 0; -} +bool compareNotEqual( + const __FlashStringHelper* a, const __FlashStringHelper* b); -inline bool compareNotEqual(const __FlashStringHelper* a, const String& b) { - return compareString(a, b) != 0; -} +bool compareNotEqual(const __FlashStringHelper* a, const String& b); -inline bool compareNotEqual(const String& a, const char* b) { - return compareString(a, b) != 0; -} +bool compareNotEqual(const String& a, const char* b); -inline bool compareNotEqual(const String& a, const String& b) { - return compareString(a, b) != 0; -} +bool compareNotEqual(const String& a, const String& b); -inline bool compareNotEqual(const String& a, const __FlashStringHelper* b) { - return compareString(a, b) != 0; -} +bool compareNotEqual(const String& a, const __FlashStringHelper* b); } From 664d94dd81804ff3a8de125815ce8fabe1440414 Mon Sep 17 00:00:00 2001 From: Brian Park Date: Tue, 13 Mar 2018 14:04:29 -0700 Subject: [PATCH 13/22] Test.*: small OCD cleanups, no functional change --- src/aunit/Test.cpp | 2 +- src/aunit/Test.h | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/aunit/Test.cpp b/src/aunit/Test.cpp index b08ee5c..09f7bea 100644 --- a/src/aunit/Test.cpp +++ b/src/aunit/Test.cpp @@ -56,7 +56,7 @@ void Test::setPassOrFail(bool ok) { // Insert the current test case into the singly linked list, sorted by // getName(). This is an O(N^2) algorithm, but should be good enough for // small N ~= 100. If N becomes bigger than that, it's probably better to insert -// using an O(N) here, then sort the elements later in TestRunner::run(). +// using an O(N) alorithm, then sort the elements later in TestRunner::run(). // Also, we don't increment a static counter here, because that would introduce // another static initialization ordering problem. void Test::insert() { diff --git a/src/aunit/Test.h b/src/aunit/Test.h index 62dcf09..5dfb5a5 100644 --- a/src/aunit/Test.h +++ b/src/aunit/Test.h @@ -59,8 +59,6 @@ void test_ ## name :: loop() namespace aunit { -class TestRunner; - class Test { public: static const uint8_t kStatusNew = 0; @@ -161,7 +159,7 @@ class TestOnce: public Test { * the internal status, then this calls pass() to make sure that this test * case will be called only once from Test::run(). */ - virtual void loop(); + virtual void loop() override; /** User-provided test case. */ virtual void once() = 0; From dc20ec4eaa3e904bf9c78674752761b01dc0e8c7 Mon Sep 17 00:00:00 2001 From: Brian Park Date: Tue, 13 Mar 2018 14:45:20 -0700 Subject: [PATCH 14/22] README.md: OCD fix: switch the ordering of 2 items in bullet list for consistentcy --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2f365ca..00be112 100644 --- a/README.md +++ b/README.md @@ -190,8 +190,8 @@ In summary, the following types of `(a, b)` are defined for the various * `(const String&, const String&)` * `(const String&, const __FlashStringHelper*)` * `(const __FlashStringHelper*, const char*)` -* `(const __FlashStringHelper*, const __FlashStringHelper*)` * `(const __FlashStringHelper*, const String&)` +* `(const __FlashStringHelper*, const __FlashStringHelper*)` The following boolean asserts are also available, just like ArduinoUnit: From c1128e79a72f558fddd6d76770c1511bac69e71b Mon Sep 17 00:00:00 2001 From: Brian Park Date: Tue, 13 Mar 2018 14:47:09 -0700 Subject: [PATCH 15/22] README.md: remove reference to TestRunner::getPrinter() method which doesn't exist --- README.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 00be112..f42b107 100644 --- a/README.md +++ b/README.md @@ -168,7 +168,7 @@ following implicit conversions will occur: C++.) Mixing the parameter types will often produce compiler errors. See comments -and solutions in the "Migration from ArduinoUnit to AUnit" section below. +and solutions in the *Migrating from ArduinoUnit to AUnit* section below. For the 3 string types (`char*`, `String`, and `__FlashStringHelper*`), all 9 combinatorial mixes are supported. @@ -284,9 +284,8 @@ void loop() { ``` This is the equivalent of the `Test::out` static member variable in -ArduinoUnit. In AUnit, the member variable is not exposed, it is accessed and -changed throught the `TestRunner::setPrinter()` and `TestRunner::getPrinter()` -methods. +ArduinoUnit. In AUnit member variables are not exposed, so changes must be +made through the `TestRunner::setPrinter()` method. ### Controlling the Verbosity @@ -304,7 +303,7 @@ void setup() { } ``` -The verbosity rules are more primitive (and simpler) than ArduinoUnit The each +The verbosity rules are more primitive (and simpler) than ArduinoUnit. Each flag below is a bit field that controls whether certain messages are enabled or disabled. There is no concept of a "minimum" or "maximum" verbosity. Also, the verbosity of an individual test case cannot be independently controlled, the From eb66ab0156b98c2b77f0879de481fd42ff989992 Mon Sep 17 00:00:00 2001 From: Brian Park Date: Wed, 14 Mar 2018 08:31:35 -0700 Subject: [PATCH 16/22] Move duplicate pring(FCString&) code into Printer::print(FCString&) --- README.md | 4 ++-- src/aunit/Compare.h | 3 ++- src/aunit/Printer.cpp | 17 +++++++++++++++++ src/aunit/Printer.h | 8 ++++++++ src/aunit/TestRunner.cpp | 14 ++------------ 5 files changed, 31 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index f42b107..af58730 100644 --- a/README.md +++ b/README.md @@ -502,13 +502,13 @@ Platform (resource) | Max | ArduinoUnit | AUnit | Arduino Nano (flash) | 30720 | 54038 | 18416 | Arduino Nano (static) | 2048 | 1061 | 908 | ----------------------+---------+-------------+-------------| -Teensy LC (flash) | 63488 | 36196 | 25092 | +Teensy LC (flash) | 63488 | 36196 | 25108 | Teensy LC (static) | 8192 | 2980 | 2768 | ----------------------+---------+-------------+-------------| Teensy 3.2 (flash) | 262144 | 51236 | 36136 | Teensy 3.2 (static) | 65536 | 5328 | 5224 | ----------------------+---------+-------------+-------------| -ESP8266 (flash) | 1044464 | does not | 267375 | +ESP8266 (flash) | 1044464 | does not | 267391 | ESP8266 (static) | 81920 | compile | 34564 | ----------------------+---------+-------------+-------------| ``` diff --git a/src/aunit/Compare.h b/src/aunit/Compare.h index 957711e..d1f7707 100644 --- a/src/aunit/Compare.h +++ b/src/aunit/Compare.h @@ -30,10 +30,11 @@ SOFTWARE. class String; class __FlashStringHelper; -class FCString; namespace aunit { +class FCString; + // compareString() int compareString(const char* a, const char* b); diff --git a/src/aunit/Printer.cpp b/src/aunit/Printer.cpp index 767c6ff..5909206 100644 --- a/src/aunit/Printer.cpp +++ b/src/aunit/Printer.cpp @@ -24,9 +24,26 @@ SOFTWARE. #include #include "Printer.h" +#include "FCString.h" namespace aunit { Print* Printer::sPrinter = &Serial; +void Printer::print(const FCString& s) { + if (s.getType() == FCString::kCStringType) { + getPrinter()->print(s.getCString()); + } else { + getPrinter()->print(s.getFString()); + } +} + +void Printer::println(const FCString& s) { + if (s.getType() == FCString::kCStringType) { + getPrinter()->println(s.getCString()); + } else { + getPrinter()->println(s.getFString()); + } +} + } diff --git a/src/aunit/Printer.h b/src/aunit/Printer.h index 2a7ae7e..6ce3365 100644 --- a/src/aunit/Printer.h +++ b/src/aunit/Printer.h @@ -29,6 +29,8 @@ class Print; namespace aunit { +class FCString; + /** * Utility class that provides a level of indirection to the Print class where * test results can be sent. By default, the Print object will be the Serial @@ -50,6 +52,12 @@ class Printer { /** Set the printer. */ static void setPrinter(Print* printer) { sPrinter = printer; } + /** Convenience method for printing an FCString. */ + static void print(const FCString& s); + + /** Convenience method for printing an FCString. */ + static void println(const FCString& s); + private: // Disable copy-constructor and assignment operator Printer(const Printer&) = delete; diff --git a/src/aunit/TestRunner.cpp b/src/aunit/TestRunner.cpp index f534a95..51fae66 100644 --- a/src/aunit/TestRunner.cpp +++ b/src/aunit/TestRunner.cpp @@ -160,12 +160,7 @@ void TestRunner::resolveTest(Test* testCase) { if (!isOutput) return; Printer::getPrinter()->print(F("Test ")); - const FCString& name = testCase->getName(); - if (name.getType() == FCString::kCStringType) { - Printer::getPrinter()->print(name.getCString()); - } else { - Printer::getPrinter()->print(name.getFString()); - } + Printer::print(testCase->getName()); if (testCase->getStatus() == Test::kStatusSkipped) { Printer::getPrinter()->println(F(" skipped.")); } else if (testCase->getStatus() == Test::kStatusFailed) { @@ -198,12 +193,7 @@ void TestRunner::listTests() { Printer::getPrinter()->println(mCount); for (Test** p = Test::getRoot(); (*p) != nullptr; p = (*p)->getNext()) { Printer::getPrinter()->print(F("Test ")); - const FCString& name = (*p)->getName(); - if (name.getType() == FCString::kCStringType) { - Printer::getPrinter()->print(name.getCString()); - } else { - Printer::getPrinter()->print(name.getFString()); - } + Printer::print((*p)->getName()); Printer::getPrinter()->println(F(" found.")); } } From 4fdb0f4468af70744caeea6cbb4d79d84d13d979 Mon Sep 17 00:00:00 2001 From: Brian Park Date: Wed, 14 Mar 2018 08:47:15 -0700 Subject: [PATCH 17/22] OCD fix of docstrings --- src/aunit/Test.h | 4 ++-- src/aunit/TestRunner.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/aunit/Test.h b/src/aunit/Test.h index 5dfb5a5..cd8d0b2 100644 --- a/src/aunit/Test.h +++ b/src/aunit/Test.h @@ -77,14 +77,14 @@ class Test { /** * Constructor taking the name of the given test case. Also performs * self-registration into the linked list of all test cases defined by - * Test::sRoot. + * Test::getRoot(). */ explicit Test(const char* name); /** * Constructor taking the name of the given test case. Also performs * self-registration into the linked list of all test cases defined by - * Test::sRoot. + * Test::getRoot().. */ explicit Test(const __FlashStringHelper* name); diff --git a/src/aunit/TestRunner.h b/src/aunit/TestRunner.h index 49e0255..18bb717 100644 --- a/src/aunit/TestRunner.h +++ b/src/aunit/TestRunner.h @@ -89,7 +89,7 @@ class TestRunner { TestRunner(const TestRunner&) = delete; TestRunner& operator=(const TestRunner&) = delete; - /** Contructor. */ + /** Constructor. */ TestRunner(); /** Run the current test case and print out the result. */ From f1a3da93161b4cf37a1765dc1ebb76f7aa109c28 Mon Sep 17 00:00:00 2001 From: Brian Park Date: Wed, 14 Mar 2018 22:50:32 -0700 Subject: [PATCH 18/22] README.md: Verified to work on ESP-01 --- README.md | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index af58730..d63ed30 100644 --- a/README.md +++ b/README.md @@ -497,20 +497,23 @@ from an actual unit test sketch containing 26 test cases using 331 `assertXxx()` statements, compiled using AUnit and ArduinoUnit on 4 different microcontrollers: ``` -Platform (resource) | Max | ArduinoUnit | AUnit | -----------------------+---------+-------------+-------------| -Arduino Nano (flash) | 30720 | 54038 | 18416 | -Arduino Nano (static) | 2048 | 1061 | 908 | -----------------------+---------+-------------+-------------| -Teensy LC (flash) | 63488 | 36196 | 25108 | -Teensy LC (static) | 8192 | 2980 | 2768 | -----------------------+---------+-------------+-------------| -Teensy 3.2 (flash) | 262144 | 51236 | 36136 | -Teensy 3.2 (static) | 65536 | 5328 | 5224 | -----------------------+---------+-------------+-------------| -ESP8266 (flash) | 1044464 | does not | 267391 | -ESP8266 (static) | 81920 | compile | 34564 | -----------------------+---------+-------------+-------------| +Platform (resource) | Max | ArduinoUnit | AUnit | +---------------------------+---------+-------------+-------------| +Arduino Nano (flash) | 30720 | 54038 | 18416 | +Arduino Nano (static) | 2048 | 1061 | 908 | +---------------------------+---------+-------------+-------------| +Teensy LC (flash) | 63488 | 36196 | 25108 | +Teensy LC (static) | 8192 | 2980 | 2768 | +---------------------------+---------+-------------+-------------| +Teensy 3.2 (flash) | 262144 | 51236 | 36136 | +Teensy 3.2 (static) | 65536 | 5328 | 5224 | +---------------------------+---------+-------------+-------------| +ESP8266 - ESP-12E (flash) | 1044464 | does not | 267391 | +ESP8266 - ESP-12E (static) | 81920 | compile | 34564 | +---------------------------+---------+-------------+-------------| +ESP8266 - ESP-01 (flash) | 499696 | does not | 267391 | +ESP8266 - ESP-01 (static) | 47356 | compile | 34564 | +---------------------------+---------+-------------+-------------| ``` Not all unit test sketches will experience a savings of 66% of flash memory with @@ -536,6 +539,7 @@ The library has been verified to work on the following hardware: * Teensy LC (48 MHz ARM Cortex-M0+) * Teensy 3.2 (72 MHz ARM Cortex-M4) * NodeMCU 1.0 clone (ESP-12E module, 80MHz ESP8266) +* ESP-01 (ESP-01 module, 80MHz ESP8266) ## License From 06f1e6f272d449fff84c8955592600a884fabf23 Mon Sep 17 00:00:00 2001 From: Brian Park Date: Thu, 15 Mar 2018 11:13:14 -0700 Subject: [PATCH 19/22] README.md: add note that this is 'beta stage'; add note about convenient '#define USE_AUNIT' macro; various other clean up --- README.md | 62 ++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 52 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index d63ed30..9a7402f 100644 --- a/README.md +++ b/README.md @@ -11,11 +11,11 @@ the microcontrollers themselves, not on emulators or simulators. AUnit was created to solve 2 problems with ArduinoUnit: * ArduinoUnit consumes too much flash memory on an AVR platform (e.g. Arduino UNO, Nano) as explained in - [issue #70](https://github.com/mmurdoch/arduinounit/issues/70). + [ArduinoUnit#70](https://github.com/mmurdoch/arduinounit/issues/70). * ArduinoUnit does not compile on the ESP8266 platform (see - [issue #68](https://github.com/mmurdoch/arduinounit/issues/68), - [issue #57](https://github.com/mmurdoch/arduinounit/pull/57), and - [issue #55](https://github.com/mmurdoch/arduinounit/issues/55)). + [ArduinoUni#68](https://github.com/mmurdoch/arduinounit/issues/68), + [ArduinoUni#57](https://github.com/mmurdoch/arduinounit/pull/57), and + [ArduinoUni#55](https://github.com/mmurdoch/arduinounit/issues/55)). In contrast: @@ -40,6 +40,13 @@ AUnit supports exclude and include filters: Currently, only a single `*` wildcard is supported and it must occur at the end if present. +Various "Meta Assertions" from ArduinoUnit have not been implemented yet +(see *Migrating from ArduinoUnit to AUnit* section below). + +(**Beta Status**: Although this library has been extensively tested by me, and I +convered my [AceButton](https://github.com/bxparks/AceButton) library to use it, +I consider it currently in "beta stage" until more users have tested it.) + ## Installation The library will be available in the Arduino IDE Library Manager eventually. @@ -354,19 +361,54 @@ instead of ### Test Runner -The `Test::run()` method has been moved to a new `TestRunner` class: +The `Test::run()` method has been moved to a new `TestRunner` class. Use +``` +aunit::TestRunner::run(); +``` +instead of +``` +Test::run(); +``` + +### Compile Time Selection + +I have found that the following macros are useful during the transition: + +``` +#define USE_AUNIT 1 + +#if USE_AUNIT == 1 +#include +#else +#include +#endif + +... -* `Test::run()` -> `TestRunner::run()` +void loop() { +#if USE_AUNIT == 1 +aunit::TestRunner::run(); +#else +Test::run(); +#endif +} +``` ### Printer The `Test::out` static variable can be set using the static method on -`TestRunner`: +`TestRunner`. Use -* `Test::out = &Serial1` -> `TestRunner::setPrinter(&Serial1)` +``` +TestRunner::setPrinter(&Serial1); +``` +instead of +``` +Test::out = &Serial1; +``` -(It can be accessed through `aunit::Printer::getPrinter()` but I don't -expect this to be needed often.) +(The current `Print` object can be accessed through +`aunit::Printer::getPrinter()` but I don't expect this to be needed often.) ### Verbosity From 34fbe157f2b08f3da75d7dc4213bb261b15a7720 Mon Sep 17 00:00:00 2001 From: Brian Park Date: Thu, 15 Mar 2018 11:40:44 -0700 Subject: [PATCH 20/22] Simplify compareString(__FlashStringHelper*, __FlashStringHelper*) and its equivalent compareStringN(); add new tests for compareString(); update README.md benchmarks --- README.md | 8 ++-- src/aunit/Compare.cpp | 10 +++-- tests/AUnitTest/AUnitTest.ino | 69 ++++++++++++++++++++++++++--------- 3 files changed, 61 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 9a7402f..4bd2c19 100644 --- a/README.md +++ b/README.md @@ -541,19 +541,19 @@ microcontrollers: ``` Platform (resource) | Max | ArduinoUnit | AUnit | ---------------------------+---------+-------------+-------------| -Arduino Nano (flash) | 30720 | 54038 | 18416 | +Arduino Nano (flash) | 30720 | 54038 | 18412 | Arduino Nano (static) | 2048 | 1061 | 908 | ---------------------------+---------+-------------+-------------| -Teensy LC (flash) | 63488 | 36196 | 25108 | +Teensy LC (flash) | 63488 | 36196 | 25104 | Teensy LC (static) | 8192 | 2980 | 2768 | ---------------------------+---------+-------------+-------------| Teensy 3.2 (flash) | 262144 | 51236 | 36136 | Teensy 3.2 (static) | 65536 | 5328 | 5224 | ---------------------------+---------+-------------+-------------| -ESP8266 - ESP-12E (flash) | 1044464 | does not | 267391 | +ESP8266 - ESP-12E (flash) | 1044464 | does not | 267375 | ESP8266 - ESP-12E (static) | 81920 | compile | 34564 | ---------------------------+---------+-------------+-------------| -ESP8266 - ESP-01 (flash) | 499696 | does not | 267391 | +ESP8266 - ESP-01 (flash) | 499696 | does not | 267375 | ESP8266 - ESP-01 (static) | 47356 | compile | 34564 | ---------------------------+---------+-------------+-------------| ``` diff --git a/src/aunit/Compare.cpp b/src/aunit/Compare.cpp index 768a3d0..7cc1f72 100644 --- a/src/aunit/Compare.cpp +++ b/src/aunit/Compare.cpp @@ -183,8 +183,9 @@ int compareString(const __FlashStringHelper* a, const __FlashStringHelper* b) { char cb = pgm_read_byte(bb); if (ca < cb) return -1; if (ca > cb) return 1; - // we hit this condition only if both strings were the same length - if (ca == '\0' || cb == '\0') return 0; + // we hit this condition only if both strings are the same length, + // so no need to check both strings for '\0' + if (ca == '\0') return 0; aa++; bb++; } @@ -237,8 +238,9 @@ int compareStringN(const __FlashStringHelper* a, const __FlashStringHelper* b, char cb = pgm_read_byte(bb); if (ca < cb) return -1; if (ca > cb) return 1; - // we hit this condition only if both strings were the same length - if (ca == '\0' || cb == '\0') return 0; + // we hit this condition only if both strings are the same length, + // so no need to check both strings for '\0' + if (ca == '\0') return 0; aa++; bb++; n--; diff --git a/tests/AUnitTest/AUnitTest.ino b/tests/AUnitTest/AUnitTest.ino index 9a3ced7..d2ce285 100644 --- a/tests/AUnitTest/AUnitTest.ino +++ b/tests/AUnitTest/AUnitTest.ino @@ -96,6 +96,57 @@ test(type_mismatch) { assertEqual(5UL, ulongValue); } +#if USE_AUNIT == 1 + +test(compareString) { + assertEqual(compareString(a, a), 0); + assertEqual(compareString(a, f), 0); + assertEqual(compareString(a, s), 0); + + assertEqual(compareString(f, a), 0); + assertEqual(compareString(f, f), 0); + assertEqual(compareString(f, s), 0); + + assertEqual(compareString(s, a), 0); + assertEqual(compareString(s, f), 0); + assertEqual(compareString(s, s), 0); + + assertLess(compareString(a, b), 0); + assertLess(compareString(a, g), 0); + assertLess(compareString(a, t), 0); + + assertLess(compareString(f, b), 0); + assertLess(compareString(f, g), 0); + assertLess(compareString(f, t), 0); + + assertLess(compareString(s, b), 0); + assertLess(compareString(s, g), 0); + assertLess(compareString(s, t), 0); +} + +test(compareStringN) { + assertEqual(compareStringN(ff, "abcde", 5), 0); + assertEqual(compareStringN("abcde", ff, 5), 0); + + assertMore(compareStringN(ff, "abcd", 5), 0); + assertLess(compareStringN("abcd", ff, 5), 0); + + assertEqual(compareStringN(ff, "abcd", 4), 0); + assertEqual(compareStringN("abcd", ff, 4), 0); + + assertMore(compareStringN(ff, "", 1), 0); + assertLess(compareStringN("", ff, 1), 0); + + assertEqual(compareStringN(ff, "", 0), 0); + assertEqual(compareStringN("", ff, 0), 0); + + assertEqual(compareStringN(gg, ff, 5), 0); + + assertMore(compareStringN(gg, ff, 6), 0); +} + +#endif + test(assertEqual) { assertEqual(true, true); assertEqual(c, c); @@ -272,24 +323,6 @@ test(flashString) { assertMoreOrEqual(hh, gg); } -#if USE_AUNIT == 1 -test(compareStringN) { - assertEqual(compareStringN(ff, "abcde", 5), 0); - assertEqual(compareStringN("abcde", ff, 5), 0); - assertMore(compareStringN(ff, "abcd", 5), 0); - assertLess(compareStringN("abcd", ff, 5), 0); - assertEqual(compareStringN(ff, "abcd", 4), 0); - assertEqual(compareStringN("abcd", ff, 4), 0); - assertMore(compareStringN(ff, "", 1), 0); - assertLess(compareStringN("", ff, 1), 0); - assertEqual(compareStringN(ff, "", 0), 0); - assertEqual(compareStringN("", ff, 0), 0); - - assertEqual(compareStringN(gg, ff, 5), 0); - assertMore(compareStringN(gg, ff, 6), 0); -} -#endif - testing(looping_skip) { static int count = 0; count++; From 41efe251a4f3e38e8994d62782b62d4645dd31ff Mon Sep 17 00:00:00 2001 From: Brian Park Date: Thu, 15 Mar 2018 11:42:06 -0700 Subject: [PATCH 21/22] README.md: fix typo --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4bd2c19..7716f45 100644 --- a/README.md +++ b/README.md @@ -44,8 +44,8 @@ Various "Meta Assertions" from ArduinoUnit have not been implemented yet (see *Migrating from ArduinoUnit to AUnit* section below). (**Beta Status**: Although this library has been extensively tested by me, and I -convered my [AceButton](https://github.com/bxparks/AceButton) library to use it, -I consider it currently in "beta stage" until more users have tested it.) +converted my [AceButton](https://github.com/bxparks/AceButton) library to use +it, I consider it currently in "beta stage" until more users have tested it.) ## Installation From 7b64754bd737918c946ebc87f041cc003b908e5f Mon Sep 17 00:00:00 2001 From: Brian Park Date: Thu, 15 Mar 2018 11:51:09 -0700 Subject: [PATCH 22/22] library.properties: update 'paragraph' description section --- library.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library.properties b/library.properties index 7882ea1..5d2be0b 100644 --- a/library.properties +++ b/library.properties @@ -3,7 +3,7 @@ version=0.1.0 author=Brian T. Park maintainer=Brian T. Park sentence=A unit testing framework for Arduino platforms inspired by ArduinoUnit. -paragraph=This is almost a drop-in replacement of ArduinoUnit. It has 2 advantage over ArduinoUnit: First, it can reduce flash memory consumption by as much as 66% on the AVR platform. Second, it works with ESP8266, in addition to the AVR and Teensy platforms. +paragraph=The unit tests run in the embedded controller, not in a simulator or emulator. It is almost a drop-in replacement of ArduinoUnit with 2 advantages. AUnit can reduce flash memory consumption by as much as 66% on the AVR platform. And it works with the ESP8266 platform, as well as the AVR and Teensy platforms. category=Other url=https://github.com/bxparks/AUnit architectures=*