mirror of
https://github.com/vee1e/tflite-micro.git
synced 2026-09-01 17:57:27 +00:00
383 lines
21 KiB
C++
383 lines
21 KiB
C++
/* Copyright 2024 The TensorFlow Authors. All Rights Reserved.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
==============================================================================*/
|
|
|
|
// An ultra-lightweight testing framework designed for use with microcontroller
|
|
// applications. This is designed to be usable even
|
|
// when no standard C or C++ libraries are available, and without any dynamic
|
|
// memory allocation or reliance on global constructors.
|
|
//
|
|
// To build a test, you use syntax similar to gunit, but with some extra
|
|
// decoration to create a hidden 'main' function containing each of the tests to
|
|
// be run. Your code should look something like:
|
|
// ----------------------------------------------------------------------------
|
|
// #include "path/to/this/header"
|
|
//
|
|
// TF_LITE_MICRO_TESTS_BEGIN
|
|
//
|
|
// TF_LITE_MICRO_TEST(SomeTest) {
|
|
// TF_LITE_LOG_EXPECT_EQ(true, true);
|
|
// }
|
|
//
|
|
// TF_LITE_MICRO_TESTS_END
|
|
// ----------------------------------------------------------------------------
|
|
// If you compile this for your platform, you'll get a normal binary that you
|
|
// should be able to run. Executing it will output logging information like this
|
|
// to stderr:
|
|
// ----------------------------------------------------------------------------
|
|
// Testing SomeTest
|
|
// 1/1 tests passed
|
|
// ~~~ALL TESTS PASSED~~~
|
|
// ----------------------------------------------------------------------------
|
|
// This is designed to be human-readable, so you can just run tests manually,
|
|
// but the string "~~~ALL TESTS PASSED~~~" should only appear if all of the
|
|
// tests do pass. This makes it possible to integrate with automated test
|
|
// systems by scanning the output logs and looking for that magic value.
|
|
//
|
|
// This framework is intended to be a rudimentary alternative to no testing at
|
|
// all on systems that struggle to run more conventional approaches, so use with
|
|
// caution!
|
|
|
|
#ifndef TENSORFLOW_LITE_MICRO_TESTING_MICRO_TEST_H_
|
|
#define TENSORFLOW_LITE_MICRO_TESTING_MICRO_TEST_H_
|
|
#include <limits>
|
|
#include <type_traits>
|
|
|
|
#include "tensorflow/lite/c/common.h"
|
|
#include "tensorflow/lite/micro/micro_log.h"
|
|
#include "tensorflow/lite/micro/system_setup.h"
|
|
|
|
namespace micro_test {
|
|
extern int tests_passed;
|
|
extern int tests_failed;
|
|
extern bool is_test_complete;
|
|
extern bool did_test_fail;
|
|
} // namespace micro_test
|
|
|
|
namespace tflite {
|
|
|
|
// This additional helper function is used (instead of directly calling
|
|
// tflite::InitializeTarget from the TF_LITE_MICRO_TESTS_BEGIN macro) to avoid
|
|
// adding a dependency from every bazel test target to micro:system_setp (which
|
|
// is the target that implements InitializeTarget().
|
|
//
|
|
// The underlying issue here is that the use of the macros results in
|
|
// dependencies that can be contained within the micro/testing:micro_test
|
|
// target bleeding on to all the tests.
|
|
inline void InitializeTest() { InitializeTarget(); }
|
|
} // namespace tflite
|
|
|
|
#define TF_LITE_MICRO_TESTS_BEGIN \
|
|
namespace micro_test { \
|
|
int tests_passed; \
|
|
int tests_failed; \
|
|
bool is_test_complete; \
|
|
bool did_test_fail; \
|
|
} \
|
|
\
|
|
int main(int argc, char** argv) { \
|
|
micro_test::tests_passed = 0; \
|
|
micro_test::tests_failed = 0; \
|
|
tflite::InitializeTest();
|
|
|
|
#define TF_LITE_MICRO_TESTS_END \
|
|
MicroPrintf("%d/%d tests passed", micro_test::tests_passed, \
|
|
(micro_test::tests_failed + micro_test::tests_passed)); \
|
|
if (micro_test::tests_failed == 0) { \
|
|
MicroPrintf("~~~ALL TESTS PASSED~~~\n"); \
|
|
return kTfLiteOk; \
|
|
} else { \
|
|
MicroPrintf("~~~SOME TESTS FAILED~~~\n"); \
|
|
return kTfLiteError; \
|
|
} \
|
|
}
|
|
|
|
// TODO(petewarden): I'm going to hell for what I'm doing to this poor for loop.
|
|
#define TF_LITE_MICRO_TEST(name) \
|
|
MicroPrintf("Testing " #name); \
|
|
for (micro_test::is_test_complete = false, \
|
|
micro_test::did_test_fail = false; \
|
|
!micro_test::is_test_complete; micro_test::is_test_complete = true, \
|
|
micro_test::tests_passed += (micro_test::did_test_fail) ? 0 : 1, \
|
|
micro_test::tests_failed += (micro_test::did_test_fail) ? 1 : 0)
|
|
|
|
#define TF_LITE_MICRO_EXPECT(x) \
|
|
do { \
|
|
if (!(x)) { \
|
|
MicroPrintf(#x " failed at %s:%d", __FILE__, __LINE__); \
|
|
micro_test::did_test_fail = true; \
|
|
} \
|
|
} while (false)
|
|
|
|
#define TF_LITE_MICRO_EXPECT_EQ(x, y) \
|
|
do { \
|
|
auto vx = x; \
|
|
auto vy = y; \
|
|
bool isFloatingX = (std::is_floating_point<decltype(vx)>::value); \
|
|
bool isFloatingY = (std::is_floating_point<decltype(vy)>::value); \
|
|
if (isFloatingX && isFloatingY) { \
|
|
auto delta = ((vx) > (vy)) ? ((vx) - (vy)) : ((vy) - (vx)); \
|
|
if (delta > std::numeric_limits<decltype(delta)>::epsilon()) { \
|
|
MicroPrintf(#x " == " #y " failed at %s:%d (%f vs %f)", __FILE__, \
|
|
__LINE__, static_cast<double>(vx), \
|
|
static_cast<double>(vy)); \
|
|
micro_test::did_test_fail = true; \
|
|
} \
|
|
} else if ((vx) != (vy)) { \
|
|
MicroPrintf(#x " == " #y " failed at %s:%d (%d vs %d)", __FILE__, \
|
|
__LINE__, static_cast<int>(vx), static_cast<int>(vy)); \
|
|
if (isFloatingX || isFloatingY) { \
|
|
MicroPrintf("-----------WARNING-----------"); \
|
|
MicroPrintf("Only one of the values is floating point value."); \
|
|
} \
|
|
micro_test::did_test_fail = true; \
|
|
} \
|
|
} while (false)
|
|
|
|
#define TF_LITE_MICRO_EXPECT_NE(x, y) \
|
|
do { \
|
|
auto vx = x; \
|
|
auto vy = y; \
|
|
bool isFloatingX = (std::is_floating_point<decltype(vx)>::value); \
|
|
bool isFloatingY = (std::is_floating_point<decltype(vy)>::value); \
|
|
if (isFloatingX && isFloatingY) { \
|
|
auto delta = ((vx) > (vy)) ? ((vx) - (vy)) : ((vy) - (vx)); \
|
|
if (delta <= std::numeric_limits<decltype(delta)>::epsilon()) { \
|
|
MicroPrintf(#x " != " #y " failed at %s:%d", __FILE__, __LINE__); \
|
|
micro_test::did_test_fail = true; \
|
|
} \
|
|
} else if ((vx) == (vy)) { \
|
|
MicroPrintf(#x " != " #y " failed at %s:%d", __FILE__, __LINE__); \
|
|
if (isFloatingX || isFloatingY) { \
|
|
MicroPrintf("-----------WARNING-----------"); \
|
|
MicroPrintf("Only one of the values is floating point value."); \
|
|
} \
|
|
micro_test::did_test_fail = true; \
|
|
} \
|
|
} while (false)
|
|
|
|
// TODO(wangtz): Making it more generic once needed.
|
|
#define TF_LITE_MICRO_ARRAY_ELEMENT_EXPECT_NEAR(arr1, idx1, arr2, idx2, \
|
|
epsilon) \
|
|
do { \
|
|
auto delta = ((arr1)[(idx1)] > (arr2)[(idx2)]) \
|
|
? ((arr1)[(idx1)] - (arr2)[(idx2)]) \
|
|
: ((arr2)[(idx2)] - (arr1)[(idx1)]); \
|
|
if (delta > epsilon) { \
|
|
MicroPrintf(#arr1 "[%d] (%f) near " #arr2 "[%d] (%f) failed at %s:%d", \
|
|
static_cast<int>(idx1), static_cast<float>((arr1)[(idx1)]), \
|
|
static_cast<int>(idx2), static_cast<float>((arr2)[(idx2)]), \
|
|
__FILE__, __LINE__); \
|
|
micro_test::did_test_fail = true; \
|
|
} \
|
|
} while (false)
|
|
|
|
// The check vx != vy is needed to properly handle the case where both
|
|
// x and y evaluate to infinity. See #46960 for more details.
|
|
#define TF_LITE_MICRO_EXPECT_NEAR(x, y, epsilon) \
|
|
do { \
|
|
auto vx = (x); \
|
|
auto vy = (y); \
|
|
auto delta = ((vx) > (vy)) ? ((vx) - (vy)) : ((vy) - (vx)); \
|
|
if (vx != vy && delta > epsilon) { \
|
|
MicroPrintf(#x " (%f) near " #y " (%f) failed at %s:%d", \
|
|
static_cast<double>(vx), static_cast<double>(vy), __FILE__, \
|
|
__LINE__); \
|
|
micro_test::did_test_fail = true; \
|
|
} \
|
|
} while (false)
|
|
|
|
#define TF_LITE_MICRO_EXPECT_GT(x, y) \
|
|
do { \
|
|
if ((x) <= (y)) { \
|
|
MicroPrintf(#x " > " #y " failed at %s:%d", __FILE__, __LINE__); \
|
|
micro_test::did_test_fail = true; \
|
|
} \
|
|
} while (false)
|
|
|
|
#define TF_LITE_MICRO_EXPECT_LT(x, y) \
|
|
do { \
|
|
if ((x) >= (y)) { \
|
|
MicroPrintf(#x " < " #y " failed at %s:%d", __FILE__, __LINE__); \
|
|
micro_test::did_test_fail = true; \
|
|
} \
|
|
} while (false)
|
|
|
|
#define TF_LITE_MICRO_EXPECT_GE(x, y) \
|
|
do { \
|
|
if ((x) < (y)) { \
|
|
MicroPrintf(#x " >= " #y " failed at %s:%d", __FILE__, __LINE__); \
|
|
micro_test::did_test_fail = true; \
|
|
} \
|
|
} while (false)
|
|
|
|
#define TF_LITE_MICRO_EXPECT_LE(x, y) \
|
|
do { \
|
|
if ((x) > (y)) { \
|
|
MicroPrintf(#x " <= " #y " failed at %s:%d", __FILE__, __LINE__); \
|
|
micro_test::did_test_fail = true; \
|
|
} \
|
|
} while (false)
|
|
|
|
#define TF_LITE_MICRO_EXPECT_TRUE(x) \
|
|
do { \
|
|
if (!(x)) { \
|
|
MicroPrintf(#x " was not true failed at %s:%d", __FILE__, __LINE__); \
|
|
micro_test::did_test_fail = true; \
|
|
} \
|
|
} while (false)
|
|
|
|
#define TF_LITE_MICRO_EXPECT_FALSE(x) \
|
|
do { \
|
|
if (x) { \
|
|
MicroPrintf(#x " was not false failed at %s:%d", __FILE__, __LINE__); \
|
|
micro_test::did_test_fail = true; \
|
|
} \
|
|
} while (false)
|
|
|
|
#define TF_LITE_MICRO_EXPECT_STRING_EQ(string1, string2) \
|
|
do { \
|
|
for (int i = 0; string1[i] != '\0' && string2[i] != '\0'; i++) { \
|
|
if (string1[i] != string2[i]) { \
|
|
MicroPrintf("FAIL: %s did not match %s", string1, string2, __FILE__, \
|
|
__LINE__); \
|
|
micro_test::did_test_fail = true; \
|
|
break; \
|
|
} \
|
|
} \
|
|
} while (false)
|
|
|
|
#define TF_LITE_MICRO_ASSERT(x) \
|
|
if ((x)) { \
|
|
} else { \
|
|
MicroPrintf(#x " failed at %s:%d", __FILE__, __LINE__); \
|
|
micro_test::did_test_fail = true; \
|
|
continue; \
|
|
}
|
|
|
|
#define TF_LITE_MICRO_ASSERT_EQ(x, y) \
|
|
if ((x) == (y)) { \
|
|
} else { \
|
|
auto vx = x; \
|
|
auto vy = y; \
|
|
bool isFloatingX = (std::is_floating_point<decltype(vx)>::value); \
|
|
bool isFloatingY = (std::is_floating_point<decltype(vy)>::value); \
|
|
if (isFloatingX && isFloatingY) { \
|
|
auto delta = ((vx) > (vy)) ? ((vx) - (vy)) : ((vy) - (vx)); \
|
|
if (delta > std::numeric_limits<decltype(delta)>::epsilon()) { \
|
|
MicroPrintf(#x " == " #y " failed at %s:%d (%f vs %f)", __FILE__, \
|
|
__LINE__, static_cast<double>(vx), \
|
|
static_cast<double>(vy)); \
|
|
micro_test::did_test_fail = true; \
|
|
continue; \
|
|
} \
|
|
} else { \
|
|
MicroPrintf(#x " == " #y " failed at %s:%d (%d vs %d)", __FILE__, \
|
|
__LINE__, static_cast<int>(vx), static_cast<int>(vy)); \
|
|
if (isFloatingX || isFloatingY) { \
|
|
MicroPrintf("-----------WARNING-----------"); \
|
|
MicroPrintf("Only one of the values is floating point value."); \
|
|
} \
|
|
micro_test::did_test_fail = true; \
|
|
continue; \
|
|
} \
|
|
}
|
|
|
|
#define TF_LITE_MICRO_ASSERT_NE(x, y) \
|
|
if (true) { \
|
|
auto vx = x; \
|
|
auto vy = y; \
|
|
bool isFloatingX = (std::is_floating_point<decltype(vx)>::value); \
|
|
bool isFloatingY = (std::is_floating_point<decltype(vy)>::value); \
|
|
if (isFloatingX && isFloatingY) { \
|
|
auto delta = ((vx) > (vy)) ? ((vx) - (vy)) : ((vy) - (vx)); \
|
|
if (delta <= std::numeric_limits<decltype(delta)>::epsilon()) { \
|
|
MicroPrintf(#x " != " #y " failed at %s:%d", __FILE__, __LINE__); \
|
|
micro_test::did_test_fail = true; \
|
|
continue; \
|
|
} \
|
|
} else if ((vx) == (vy)) { \
|
|
MicroPrintf(#x " != " #y " failed at %s:%d", __FILE__, __LINE__); \
|
|
if (isFloatingX || isFloatingY) { \
|
|
MicroPrintf("-----------WARNING-----------"); \
|
|
MicroPrintf("Only one of the values is floating point value."); \
|
|
} \
|
|
micro_test::did_test_fail = true; \
|
|
continue; \
|
|
} \
|
|
} else \
|
|
(void)0
|
|
|
|
#define TF_LITE_MICRO_ASSERT_GT(x, y) \
|
|
if ((x) > (y)) { \
|
|
} else { \
|
|
MicroPrintf(#x " > " #y " failed at %s:%d", __FILE__, __LINE__); \
|
|
micro_test::did_test_fail = true; \
|
|
continue; \
|
|
}
|
|
|
|
#define TF_LITE_MICRO_ASSERT_LT(x, y) \
|
|
if ((x) < (y)) { \
|
|
} else { \
|
|
MicroPrintf(#x " < " #y " failed at %s:%d", __FILE__, __LINE__); \
|
|
micro_test::did_test_fail = true; \
|
|
continue; \
|
|
}
|
|
|
|
#define TF_LITE_MICRO_ASSERT_GE(x, y) \
|
|
if ((x) >= (y)) { \
|
|
} else { \
|
|
MicroPrintf(#x " >= " #y " failed at %s:%d", __FILE__, __LINE__); \
|
|
micro_test::did_test_fail = true; \
|
|
continue; \
|
|
}
|
|
|
|
#define TF_LITE_MICRO_ASSERT_LE(x, y) \
|
|
if ((x) <= (y)) { \
|
|
} else { \
|
|
MicroPrintf(#x " <= " #y " failed at %s:%d", __FILE__, __LINE__); \
|
|
micro_test::did_test_fail = true; \
|
|
continue; \
|
|
}
|
|
|
|
#define TF_LITE_MICRO_ASSERT_TRUE(x) \
|
|
if ((x)) { \
|
|
} else { \
|
|
MicroPrintf(#x " was not true failed at %s:%d", __FILE__, __LINE__); \
|
|
micro_test::did_test_fail = true; \
|
|
continue; \
|
|
}
|
|
|
|
#define TF_LITE_MICRO_ASSERT_FALSE(x) \
|
|
if (!(x)) { \
|
|
} else { \
|
|
MicroPrintf(#x " was not false failed at %s:%d", __FILE__, __LINE__); \
|
|
micro_test::did_test_fail = true; \
|
|
continue; \
|
|
}
|
|
|
|
#define TF_LITE_MICRO_FAIL(msg) \
|
|
do { \
|
|
MicroPrintf("FAIL: %s", msg, __FILE__, __LINE__); \
|
|
micro_test::did_test_fail = true; \
|
|
} while (false)
|
|
|
|
#define TF_LITE_MICRO_CHECK_FAIL() \
|
|
do { \
|
|
if (micro_test::did_test_fail) { \
|
|
return kTfLiteError; \
|
|
} \
|
|
} while (false)
|
|
|
|
#endif // TENSORFLOW_LITE_MICRO_TESTING_MICRO_TEST_H_
|