| // Copyright lowRISC contributors. | |
| // Licensed under the Apache License, Version 2.0, see LICENSE for details. | |
| // SPDX-License-Identifier: Apache-2.0 | |
| #ifndef OPENTITAN_SW_DEVICE_LIB_TESTING_TEST_FRAMEWORK_CHECK_H_ | |
| #define OPENTITAN_SW_DEVICE_LIB_TESTING_TEST_FRAMEWORK_CHECK_H_ | |
| #include <stdbool.h> | |
| #include "sw/device/lib/base/macros.h" | |
| #include "sw/device/lib/base/memory.h" | |
| #include "sw/device/lib/base/status.h" | |
| #include "sw/device/lib/dif/dif_base.h" | |
| #include "sw/device/lib/runtime/hart.h" | |
| #include "sw/device/lib/runtime/log.h" | |
| #include "sw/device/lib/testing/test_framework/status.h" | |
| /** | |
| * Runtime assertion macros with log.h integration. | |
| */ | |
| #ifdef __cplusplus | |
| #error "This file is C-only; it is not a polyglot header!" | |
| #endif | |
| /** | |
| * Checks that the given condition is true. If the condition is false, this | |
| * function logs and then aborts. | |
| * | |
| * @param condition An expression to check. | |
| * @param ... Arguments to a LOG_* macro, which are evaluated if the check | |
| * fails. | |
| */ | |
| #define CHECK(condition, ...) \ | |
| do { \ | |
| if (!(condition)) { \ | |
| /* NOTE: because the condition in this if \ | |
| statement can be statically determined, \ | |
| only one of the below string constants \ | |
| will be included in the final binary.*/ \ | |
| if (OT_VA_ARGS_COUNT(_, ##__VA_ARGS__) == 0) { \ | |
| LOG_ERROR("CHECK-fail: " #condition); \ | |
| } else { \ | |
| LOG_ERROR("CHECK-fail: " __VA_ARGS__); \ | |
| } \ | |
| /* Currently, this macro will call into \ | |
| the test failure code, which logs \ | |
| "FAIL" and aborts. In the future, \ | |
| we will try to condition on whether \ | |
| or not this is a test.*/ \ | |
| test_status_set(kTestStatusFailed); \ | |
| } \ | |
| } while (false) | |
| // Note that this is *not* a polyglot header, so we can use the C11-only | |
| // _Generic keyword safely. | |
| // See: https://en.cppreference.com/w/c/language/generic | |
| // clang-format off | |
| #define SHOW_MISMATCH_FMT_STR_(a) _Generic((a), \ | |
| bool: "CHECK-fail: [%d] got: 0x%02x; want: 0x%02x", \ | |
| int8_t: "CHECK-fail: [%d] got: 0x%02x; want: 0x%02x", \ | |
| uint8_t: "CHECK-fail: [%d] got: 0x%02x; want: 0x%02x", \ | |
| int16_t: "CHECK-fail: [%d] got: 0x%04x; want: 0x%04x", \ | |
| uint16_t: "CHECK-fail: [%d] got: 0x%04x; want: 0x%04x", \ | |
| int32_t: "CHECK-fail: [%d] got: 0x%08x; want: 0x%08x", \ | |
| uint32_t: "CHECK-fail: [%d] got: 0x%08x; want: 0x%08x", \ | |
| int64_t: "CHECK-fail: [%d] got: 0x%016x; want: 0x%016x", \ | |
| uint64_t: "CHECK-fail: [%d] got: 0x%016x; want: 0x%016x") | |
| #define SHOW_MATCH_FMT_STR_(a) _Generic((a), \ | |
| bool: "CHECK-fail: [%d] both equal: 0x%02x", \ | |
| int8_t: "CHECK-fail: [%d] both equal: 0x%02x", \ | |
| uint8_t: "CHECK-fail: [%d] both equal: 0x%02x", \ | |
| int16_t: "CHECK-fail: [%d] both equal: 0x%04x", \ | |
| uint16_t: "CHECK-fail: [%d] both equal: 0x%04x", \ | |
| int32_t: "CHECK-fail: [%d] both equal: 0x%08x", \ | |
| uint32_t: "CHECK-fail: [%d] both equal: 0x%08x", \ | |
| int64_t: "CHECK-fail: [%d] both equal: 0x%016x", \ | |
| uint64_t: "CHECK-fail: [%d] both equal: 0x%016x") | |
| // clang-format on | |
| /** | |
| * Compare `num_items_` of `actual_` against `expected_` buffer. | |
| * | |
| * Prints differences between `actual_` and `expected_` before logging an error. | |
| * Note, the differences between the actual and expected buffer values are | |
| * logged via LOG_INFO _before_ the error is logged with LOG_ERROR, since by | |
| * default DV simulations are configured to terminate upon the first error. | |
| * | |
| * @param actual_ Buffer containing actual values. | |
| * @param expected_ Buffer containing expected values. | |
| * @param num_items_ Number of items to compare. | |
| * @param ... Arguments to a LOG_* macro, which are evaluated if the check. | |
| */ | |
| #define CHECK_ARRAYS_EQ(actual_, expected_, num_items_, ...) \ | |
| do { \ | |
| static_assert(sizeof(*(actual_)) == sizeof(*(expected_)), \ | |
| "CHECK_ARRAYS requires arguments of equal size."); \ | |
| bool fail = false; \ | |
| for (size_t i = 0; i < num_items_; ++i) { \ | |
| if ((actual_)[i] != (expected_)[i]) { \ | |
| if (!fail) { \ | |
| /* Print a failure message as soon as possible. */ \ | |
| if (OT_VA_ARGS_COUNT(_, ##__VA_ARGS__) == 0) { \ | |
| LOG_ERROR("CHECK-fail: " #actual_ " does not match " #expected_); \ | |
| } else { \ | |
| LOG_ERROR("CHECK-fail: " __VA_ARGS__); \ | |
| } \ | |
| } \ | |
| \ | |
| LOG_ERROR(SHOW_MISMATCH_FMT_STR_((actual_)[i]), i, (actual_)[i], \ | |
| (expected_)[i]); \ | |
| fail = true; \ | |
| } \ | |
| } \ | |
| if (fail) { \ | |
| /* Currently, this macro will call into \ | |
| the test failure code, which logs \ | |
| "FAIL" and aborts. In the future, \ | |
| we will try to condition on whether \ | |
| or not this is a test.*/ \ | |
| test_status_set(kTestStatusFailed); \ | |
| } \ | |
| } while (false) | |
| /** | |
| * Compare `num_items_` of `actual_` against `not_expected_` buffer. | |
| * | |
| * Prints matches between `actual_` and `not_expected_` before logging an error. | |
| * Note, the matches between the actual and not_expected buffer values are | |
| * logged via LOG_INFO _before_ the error is logged with LOG_ERROR, since by | |
| * default DV simulations are configured to terminate upon the first error. | |
| * | |
| * @param actual_ Buffer containing actual values. | |
| * @param not_expected_ Buffer containing not expected values. | |
| * @param num_items_ Number of items to compare. | |
| * @param ... Arguments to a LOG_* macro, which are evaluated if the check. | |
| */ | |
| #define CHECK_ARRAYS_NE(actual_, not_expected_, num_items_, ...) \ | |
| do { \ | |
| static_assert(sizeof(*(actual_)) == sizeof(*(not_expected_)), \ | |
| "CHECK_ARRAYS requires arguments of equal size."); \ | |
| if (memcmp((actual_), (not_expected_), num_items_ * sizeof(*(actual_))) == \ | |
| 0) { \ | |
| if (OT_VA_ARGS_COUNT(_, ##__VA_ARGS__) == 0) { \ | |
| LOG_ERROR("CHECK-fail: " #actual_ " matches " #not_expected_); \ | |
| } else { \ | |
| LOG_ERROR("CHECK-fail: " __VA_ARGS__); \ | |
| } \ | |
| for (size_t i = 0; i < num_items_; ++i) { \ | |
| LOG_ERROR(SHOW_MATCH_FMT_STR_((actual_)[i]), i, (actual_)[i]); \ | |
| } \ | |
| /* Currently, this macro will call into \ | |
| the test failure code, which logs \ | |
| "FAIL" and aborts. In the future, \ | |
| we will try to condition on whether \ | |
| or not this is a test.*/ \ | |
| test_status_set(kTestStatusFailed); \ | |
| } \ | |
| } while (false) | |
| /** | |
| * Checks the characters of two strings are the same, | |
| * up to and including the first null character. | |
| * The CHECK macro is called on each character pair. | |
| * | |
| * @param actual_ The first string in the comparison. | |
| * @param expected_ The second string in the comparison. | |
| */ | |
| #define CHECK_STR_EQ(actual_, expected_) \ | |
| do { \ | |
| size_t i = 0; \ | |
| const char *expected = (expected_); \ | |
| const char *actual = (actual_); \ | |
| do { \ | |
| CHECK(actual[i] == expected[i], \ | |
| "Strings differ at char %d, so \"%s\" != \"%s\".", i, actual, \ | |
| expected); \ | |
| ++i; \ | |
| } while (actual[i] != '\0' || expected[i] != '\0'); \ | |
| } while (false) | |
| /** | |
| * Checks that the given DIF call returns kDifOk. If the DIF call returns a | |
| * different dif_result_t value (defined in sw/device/lib/dif/dif_base.h), this | |
| * function logs and then aborts. | |
| * | |
| * @param dif_call DIF call to invoke and check its return value. | |
| * @param ... Arguments to a LOG_* macro, which are evaluated if the check | |
| * fails. | |
| */ | |
| #define CHECK_DIF_OK(dif_call, ...) \ | |
| do { \ | |
| dif_result_t dif_result = dif_call; \ | |
| if (dif_result != kDifOk) { \ | |
| /* NOTE: because the condition in this if \ | |
| statement can be statically determined, \ | |
| only one of the below string constants \ | |
| will be included in the final binary.*/ \ | |
| if (OT_VA_ARGS_COUNT(_, ##__VA_ARGS__) == 0) { \ | |
| LOG_ERROR("DIF-fail: " #dif_call " returns %d", dif_result); \ | |
| } else { \ | |
| LOG_ERROR("DIF-fail: " __VA_ARGS__); \ | |
| } \ | |
| /* Currently, this macro will call into \ | |
| the test failure code, which logs \ | |
| "FAIL" and aborts. In the future, \ | |
| we will try to condition on whether \ | |
| or not this is a test.*/ \ | |
| test_status_set(kTestStatusFailed); \ | |
| } \ | |
| } while (false) | |
| /** | |
| * Checks that the `status_t` represents a non-error value. | |
| * | |
| * Prints a human-readable error message if the status represents an error. | |
| * | |
| * @param expr An expression which evaluates to a `status_t`. | |
| */ | |
| #define CHECK_STATUS_OK(expr, ...) \ | |
| do { \ | |
| status_t status_ = expr; \ | |
| if (!status_ok(status_)) { \ | |
| LOG_ERROR("CHECK-STATUS-fail: %r", status_); \ | |
| test_status_set(kTestStatusFailed); \ | |
| } \ | |
| } while (false) | |
| #endif // OPENTITAN_SW_DEVICE_LIB_TESTING_TEST_FRAMEWORK_CHECK_H_ |