/* 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 containted 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_FAIL(msg)                       \
  do {                                                \
    MicroPrintf("FAIL: %s", msg, __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_CHECK_FAIL()   \
  do {                               \
    if (micro_test::did_test_fail) { \
      return kTfLiteError;           \
    }                                \
  } while (false)

#endif  // TENSORFLOW_LITE_MICRO_TESTING_MICRO_TEST_H_
