1 /* Copyright 2018 The TensorFlow Authors. All Rights Reserved. 2 3 Licensed under the Apache License, Version 2.0 (the "License"); 4 you may not use this file except in compliance with the License. 5 You may obtain a copy of the License at 6 7 http://www.apache.org/licenses/LICENSE-2.0 8 9 Unless required by applicable law or agreed to in writing, software 10 distributed under the License is distributed on an "AS IS" BASIS, 11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 See the License for the specific language governing permissions and 13 limitations under the License. 14 ==============================================================================*/ 15 16 // An ultra-lightweight testing framework designed for use with microcontroller 17 // applications. Its only dependency is on TensorFlow Lite's ErrorReporter 18 // interface, where log messages are output. This is designed to be usable even 19 // when no standard C or C++ libraries are available, and without any dynamic 20 // memory allocation or reliance on global constructors. 21 // 22 // To build a test, you use syntax similar to gunit, but with some extra 23 // decoration to create a hidden 'main' function containing each of the tests to 24 // be run. Your code should look something like: 25 // ---------------------------------------------------------------------------- 26 // #include "path/to/this/header" 27 // 28 // TF_LITE_MICRO_TESTS_BEGIN 29 // 30 // TF_LITE_MICRO_TEST(SomeTest) { 31 // TF_LITE_LOG_EXPECT_EQ(true, true); 32 // } 33 // 34 // TF_LITE_MICRO_TESTS_END 35 // ---------------------------------------------------------------------------- 36 // If you compile this for your platform, you'll get a normal binary that you 37 // should be able to run. Executing it will output logging information like this 38 // to stderr (or whatever equivalent is available and written to by 39 // ErrorReporter): 40 // ---------------------------------------------------------------------------- 41 // Testing SomeTest 42 // 1/1 tests passed 43 // ~~~ALL TESTS PASSED~~~ 44 // ---------------------------------------------------------------------------- 45 // This is designed to be human-readable, so you can just run tests manually, 46 // but the string "~~~ALL TESTS PASSED~~~" should only appear if all of the 47 // tests do pass. This makes it possible to integrate with automated test 48 // systems by scanning the output logs and looking for that magic value. 49 // 50 // This framework is intended to be a rudimentary alternative to no testing at 51 // all on systems that struggle to run more conventional approaches, so use with 52 // caution! 53 54 #ifndef TENSORFLOW_LITE_MICRO_TESTING_MICRO_TEST_H_ 55 #define TENSORFLOW_LITE_MICRO_TESTING_MICRO_TEST_H_ 56 57 #include "tensorflow/lite/micro/micro_error_reporter.h" 58 59 namespace micro_test { 60 extern int tests_passed; 61 extern int tests_failed; 62 extern bool is_test_complete; 63 extern bool did_test_fail; 64 extern tflite::ErrorReporter* reporter; 65 } // namespace micro_test 66 67 #define TF_LITE_MICRO_TESTS_BEGIN \ 68 namespace micro_test { \ 69 int tests_passed; \ 70 int tests_failed; \ 71 bool is_test_complete; \ 72 bool did_test_fail; \ 73 tflite::ErrorReporter* reporter; \ 74 } \ 75 \ 76 int main(int argc, char** argv) { \ 77 micro_test::tests_passed = 0; \ 78 micro_test::tests_failed = 0; \ 79 tflite::MicroErrorReporter error_reporter; \ 80 micro_test::reporter = &error_reporter; 81 82 #define TF_LITE_MICRO_TESTS_END \ 83 micro_test::reporter->Report( \ 84 "%d/%d tests passed", micro_test::tests_passed, \ 85 (micro_test::tests_failed + micro_test::tests_passed)); \ 86 if (micro_test::tests_failed == 0) { \ 87 micro_test::reporter->Report("~~~ALL TESTS PASSED~~~\n"); \ 88 } else { \ 89 micro_test::reporter->Report("~~~SOME TESTS FAILED~~~\n"); \ 90 } \ 91 } 92 93 // TODO(petewarden): I'm going to hell for what I'm doing to this poor for loop. 94 #define TF_LITE_MICRO_TEST(name) \ 95 micro_test::reporter->Report("Testing %s", #name); \ 96 for (micro_test::is_test_complete = false, \ 97 micro_test::did_test_fail = false; \ 98 !micro_test::is_test_complete; micro_test::is_test_complete = true, \ 99 micro_test::tests_passed += (micro_test::did_test_fail) ? 0 : 1, \ 100 micro_test::tests_failed += (micro_test::did_test_fail) ? 1 : 0) 101 102 #define TF_LITE_MICRO_EXPECT(x) \ 103 do { \ 104 if (!(x)) { \ 105 micro_test::reporter->Report(#x " failed at %s:%d", __FILE__, __LINE__); \ 106 micro_test::did_test_fail = true; \ 107 } \ 108 } while (false) 109 110 #define TF_LITE_MICRO_EXPECT_EQ(x, y) \ 111 do { \ 112 if ((x) != (y)) { \ 113 micro_test::reporter->Report(#x " == " #y " failed at %s:%d (%d vs %d)", \ 114 __FILE__, __LINE__, (x), (y)); \ 115 micro_test::did_test_fail = true; \ 116 } \ 117 } while (false) 118 119 #define TF_LITE_MICRO_EXPECT_NE(x, y) \ 120 do { \ 121 if ((x) == (y)) { \ 122 micro_test::reporter->Report(#x " != " #y " failed at %s:%d", __FILE__, \ 123 __LINE__); \ 124 micro_test::did_test_fail = true; \ 125 } \ 126 } while (false) 127 128 // TODO(wangtz): Making it more generic once needed. 129 #define TF_LITE_MICRO_ARRAY_ELEMENT_EXPECT_NEAR(arr1, idx1, arr2, idx2, \ 130 epsilon) \ 131 do { \ 132 auto delta = ((arr1)[(idx1)] > (arr2)[(idx2)]) \ 133 ? ((arr1)[(idx1)] - (arr2)[(idx2)]) \ 134 : ((arr2)[(idx2)] - (arr1)[(idx1)]); \ 135 if (delta > epsilon) { \ 136 micro_test::reporter->Report( \ 137 #arr1 "[%d] (%f) near " #arr2 "[%d] (%f) failed at %s:%d", \ 138 static_cast<int>(idx1), static_cast<float>((arr1)[(idx1)]), \ 139 static_cast<int>(idx2), static_cast<float>((arr2)[(idx2)]), \ 140 __FILE__, __LINE__); \ 141 micro_test::did_test_fail = true; \ 142 } \ 143 } while (false) 144 145 #define TF_LITE_MICRO_EXPECT_NEAR(x, y, epsilon) \ 146 do { \ 147 auto delta = ((x) > (y)) ? ((x) - (y)) : ((y) - (x)); \ 148 if (delta > epsilon) { \ 149 micro_test::reporter->Report( \ 150 #x " (%f) near " #y " (%f) failed at %s:%d", static_cast<float>(x), \ 151 static_cast<float>(y), __FILE__, __LINE__); \ 152 micro_test::did_test_fail = true; \ 153 } \ 154 } while (false) 155 156 #define TF_LITE_MICRO_EXPECT_GT(x, y) \ 157 do { \ 158 if ((x) <= (y)) { \ 159 micro_test::reporter->Report(#x " > " #y " failed at %s:%d", __FILE__, \ 160 __LINE__); \ 161 micro_test::did_test_fail = true; \ 162 } \ 163 } while (false) 164 165 #define TF_LITE_MICRO_EXPECT_LT(x, y) \ 166 do { \ 167 if ((x) >= (y)) { \ 168 micro_test::reporter->Report(#x " < " #y " failed at %s:%d", __FILE__, \ 169 __LINE__); \ 170 micro_test::did_test_fail = true; \ 171 } \ 172 } while (false) 173 174 #define TF_LITE_MICRO_EXPECT_GE(x, y) \ 175 do { \ 176 if ((x) < (y)) { \ 177 micro_test::reporter->Report(#x " >= " #y " failed at %s:%d", __FILE__, \ 178 __LINE__); \ 179 micro_test::did_test_fail = true; \ 180 } \ 181 } while (false) 182 183 #define TF_LITE_MICRO_EXPECT_LE(x, y) \ 184 do { \ 185 if ((x) > (y)) { \ 186 micro_test::reporter->Report(#x " <= " #y " failed at %s:%d", __FILE__, \ 187 __LINE__); \ 188 micro_test::did_test_fail = true; \ 189 } \ 190 } while (false) 191 192 #define TF_LITE_MICRO_EXPECT_TRUE(x) \ 193 do { \ 194 if (!(x)) { \ 195 micro_test::reporter->Report(#x " was not true failed at %s:%d", \ 196 __FILE__, __LINE__); \ 197 micro_test::did_test_fail = true; \ 198 } \ 199 } while (false) 200 201 #define TF_LITE_MICRO_EXPECT_FALSE(x) \ 202 do { \ 203 if (x) { \ 204 micro_test::reporter->Report(#x " was not false failed at %s:%d", \ 205 __FILE__, __LINE__); \ 206 micro_test::did_test_fail = true; \ 207 } \ 208 } while (false) 209 210 #define TF_LITE_MICRO_FAIL(msg) \ 211 do { \ 212 micro_test::reporter->Report("FAIL: %s", msg, __FILE__, __LINE__); \ 213 micro_test::did_test_fail = true; \ 214 } while (false) 215 216 #endif // TENSORFLOW_LITE_MICRO_TESTING_MICRO_TEST_H_ 217