blob: 7907b2aa3096850171e9e6ffcfb3f031868aa9f2 [file] [log] [blame]
// 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_