229 lines
7.3 KiB
C
229 lines
7.3 KiB
C
#ifndef UNITTEST_H
|
|
#define UNITTEST_H
|
|
|
|
#include <sys/linker_set.h>
|
|
#include <compiler.h>
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
|
|
// Used for logging progress during tests
|
|
#define TEST_SILENT (0)
|
|
#define TEST_FAILURE (10)
|
|
#define TEST_INFO (20)
|
|
#define TEST_SPEW (30)
|
|
void tprintf(int verbosity, const char *fmt, ...) __printflike(2, 3);
|
|
|
|
typedef void (*test_case_func_t)(uintptr_t);
|
|
typedef void (*test_suite_setup_func_t)(void);
|
|
typedef void (*test_suite_cleanup_func_t)(void);
|
|
|
|
struct test_case {
|
|
const char *name;
|
|
test_case_func_t function;
|
|
uintptr_t context;
|
|
const char *description;
|
|
};
|
|
|
|
struct test_suite {
|
|
const char *name;
|
|
const char *description;
|
|
test_suite_setup_func_t setup_function;
|
|
test_suite_cleanup_func_t cleanup_function;
|
|
struct test_case test_cases[];
|
|
};
|
|
|
|
#define TEST_CASE_LAST \
|
|
{ NULL, NULL, 0, NULL }
|
|
|
|
#define TEST_SUITE(_suite) LINKER_SET_ENTRY(unit_test_suite, _suite)
|
|
|
|
#define TEST_SETUP_HOOK(_func) LINKER_SET_ENTRY(test_setup_hooks, _func)
|
|
|
|
// constructs a literal array that can be passed into the assert macros
|
|
#define TEST_ARRAY(type, params...) &(const type[]){params}
|
|
|
|
// generates the appropriate printf format string for _val's type
|
|
#define _FMT(_val) \
|
|
_Generic(_val, \
|
|
bool: "%u", \
|
|
unsigned char: "%u", \
|
|
const unsigned char: "%u", \
|
|
signed char: "%d", \
|
|
const signed char: "%d", \
|
|
unsigned short: "%u", \
|
|
const unsigned short: "%u", \
|
|
signed short: "%d", \
|
|
const signed short: "%d", \
|
|
unsigned int: "%u", \
|
|
const unsigned int: "%u", \
|
|
signed int: "%d", \
|
|
const signed int: "%d", \
|
|
unsigned long: "%lu", \
|
|
const unsigned long: "%lu", \
|
|
signed long: "%ld", \
|
|
const signed long: "%ld", \
|
|
unsigned long long: "%llu", \
|
|
const unsigned long long: "%llu", \
|
|
signed long long: "%llu", \
|
|
const signed long long: "%llu" \
|
|
)
|
|
|
|
#define TEST_ASSERT(_a) \
|
|
do { \
|
|
if (!(_a)) { \
|
|
test_assert_fail(__FILE__, __FUNCTION__, __LINE__, \
|
|
"%s", #_a); \
|
|
} \
|
|
} while (0)
|
|
|
|
// For equality testing, we just coerce everything to uint64_t to avoid spurious
|
|
// comparison of signed and unsigned errors; we don't do this for > and friends
|
|
// because that would change the sense of the comparison
|
|
#define TEST_ASSERT_REL_EQ(_a, _rel, _b) \
|
|
do { \
|
|
typeof(_a) _val_a = (_a); \
|
|
typeof(_b) _val_b = (_b); \
|
|
uint64_t _unsigned_a = (uint64_t)_val_a; \
|
|
uint64_t _unsigned_b = (uint64_t)_val_b; \
|
|
if (!(_unsigned_a _rel _unsigned_b)) { \
|
|
char _str_a[40], _str_b[40]; \
|
|
snprintf(_str_a, sizeof(_str_a), _FMT(_a), _val_a); \
|
|
snprintf(_str_b, sizeof(_str_b), _FMT(_b), _val_b); \
|
|
test_assert_fail(__FILE__, __FUNCTION__, __LINE__, \
|
|
"\"%s %s %s\" (%s %s %s)", \
|
|
#_a, #_rel, #_b, _str_a, #_rel, _str_b); \
|
|
} \
|
|
} while(0)
|
|
|
|
#define TEST_ASSERT_EQ(_a, _b) TEST_ASSERT_REL_EQ(_a, ==, _b)
|
|
#define TEST_ASSERT_NEQ(_a, _b) TEST_ASSERT_REL_EQ(_a, !=, _b)
|
|
|
|
#define TEST_ASSERT_REL(_a, _rel, _b) \
|
|
do { \
|
|
typeof(_a) _val_a = (_a); \
|
|
typeof(_b) _val_b = (_b); \
|
|
if (!(_val_a _rel _val_b)) { \
|
|
char _str_a[40], _str_b[40]; \
|
|
snprintf(_str_a, sizeof(_str_a), _FMT(_a), _val_a); \
|
|
snprintf(_str_b, sizeof(_str_b), _FMT(_b), _val_b); \
|
|
test_assert_fail(__FILE__, __FUNCTION__, __LINE__, \
|
|
"\"%s %s %s\" (%s %s %s)", \
|
|
#_a, #_rel, #_b, _str_a, #_rel, _str_b); \
|
|
} \
|
|
} while(0)
|
|
|
|
#define TEST_ASSERT_GT(_a, _b) TEST_ASSERT_REL(_a, >, _b)
|
|
#define TEST_ASSERT_GTE(_a, _b) TEST_ASSERT_REL(_a, >=, _b)
|
|
#define TEST_ASSERT_LT(_a, _b) TEST_ASSERT_REL(_a, <, _b)
|
|
#define TEST_ASSERT_LTE(_a, _b) TEST_ASSERT_REL(_a, <=, _b)
|
|
|
|
#define TEST_ASSERT_PTR_EQ(_a, _b) \
|
|
do { \
|
|
const void *_val_a = (_a); \
|
|
const void *_val_b = (_b); \
|
|
if (!(_val_a == _val_b)) { \
|
|
test_assert_fail(__FILE__, __FUNCTION__, __LINE__, \
|
|
"\"%s == %s\" (%p == %p)", \
|
|
#_a, #_b, _val_a, _val_b); \
|
|
} \
|
|
} while(0)
|
|
|
|
#define TEST_ASSERT_PTR_NEQ(_a, _b) \
|
|
do { \
|
|
const void *_val_a = (_a); \
|
|
const void *_val_b = (_b); \
|
|
if (!(_val_a != _val_b)) { \
|
|
test_assert_fail(__FILE__, __FUNCTION__, __LINE__, \
|
|
"\"%s != %s\" (%p == %p)", \
|
|
#_a, #_b, _val_a, _val_b); \
|
|
} \
|
|
} while(0)
|
|
|
|
#define TEST_ASSERT_STR_EQ(_a, _b) \
|
|
do { \
|
|
const char *_val_a = (_a); \
|
|
const char *_val_b = (_b); \
|
|
if (_val_a == NULL || _val_b == NULL || strcmp(_val_a, _val_b) != 0) { \
|
|
test_assert_fail(__FILE__, __FUNCTION__, __LINE__, \
|
|
"\"strcmp(%s, %s) == 0\" (strcmp(\"%s\", \"%s\"))", \
|
|
#_a, #_b, _val_a, _val_b); \
|
|
} \
|
|
} while (0)
|
|
|
|
|
|
#define TEST_ASSERT_STR_NEQ(_a, _b) \
|
|
do { \
|
|
const char *_val_a = (_a); \
|
|
const char *_val_b = (_b); \
|
|
if (strcmp(_val_a, _val_b) == 0) { \
|
|
test_assert_fail(__FILE__, __FUNCTION__, __LINE__, \
|
|
"\"strcmp(%s, %s) != 0\" (!strcmp(%s, %s))", \
|
|
#_a, #_b, _val_a, _val_b); \
|
|
} \
|
|
} while (0)
|
|
|
|
|
|
#define TEST_ASSERT_MEM_EQ(_a, _b, _size) \
|
|
do { \
|
|
const uint8_t *_val_a = (const uint8_t *)(_a); \
|
|
const uint8_t *_val_b = (const uint8_t *)(_b); \
|
|
unsigned long long _val_size = (_size); \
|
|
unsigned long long _i; \
|
|
for (_i = 0; _i < _val_size; _i++) { \
|
|
if (_val_a[_i] != _val_b[_i]) \
|
|
break; \
|
|
} \
|
|
if (_i != _val_size) { \
|
|
test_assert_fail(__FILE__, __FUNCTION__, __LINE__, \
|
|
"\"memcmp(%s, %s, %llu) == 0\" " \
|
|
"(differs at byte %llu, 0x%02x vs 0x%02x)", \
|
|
#_a, #_b, _val_size, _i, _val_a[_i], _val_b[_i]); \
|
|
} \
|
|
} while (0)
|
|
|
|
|
|
#define TEST_ASSERT_MEM_NEQ(_a, _b, _size) \
|
|
do { \
|
|
const void *_val_a = (const uint8_t *)(_a); \
|
|
const void *_val_b = (const uint8_t *)(_b); \
|
|
unsigned long long _val_size = (_size); \
|
|
if (memcmp(_val_a, _val_b, _val_size) == 0) { \
|
|
test_assert_fail(__FILE__, __FUNCTION__, __LINE__, \
|
|
"\"memcmp(%s, %s, %llu) != 0\"", \
|
|
#_a, #_b, _val_size); \
|
|
} \
|
|
} while (0)
|
|
|
|
#define TEST_ASSERT_NOT_NULL(_a) \
|
|
do { \
|
|
const void *_val = (const void *)_a; \
|
|
if (!(_val != NULL)) { \
|
|
test_assert_fail(__FILE__, __FUNCTION__, __LINE__, \
|
|
"\"%s != NULL\" (%p == NULL)", #_a, _val); \
|
|
} \
|
|
} while (0)
|
|
|
|
#define TEST_ASSERT_NULL(_a) \
|
|
do { \
|
|
const void *_val = (const void *)_a; \
|
|
if (!(_val == NULL)) { \
|
|
test_assert_fail(__FILE__, __FUNCTION__, __LINE__, \
|
|
"\"%s == NULL\" (%p == NULL)", #_a, _val); \
|
|
} \
|
|
} while (0)
|
|
|
|
|
|
#define TEST_EXPECT_PANIC() test_expect_panic(__FILE__, __FUNCTION__, __LINE__)
|
|
|
|
#define TEST_EXPECT_PANICKED() test_expect_panicked(__FILE__, __FUNCTION__, __LINE__)
|
|
|
|
#define TEST_FAIL(args...) test_assert_fail(__FILE__, __FUNCTION__, __LINE__, args)
|
|
|
|
// helper functions for the assertion macros above; don't call directly
|
|
void test_assert_fail(const char *file, const char *func, unsigned line, const char *fmt, ...) __noreturn;
|
|
void test_expect_panic(const char *file, const char *func, unsigned line);
|
|
void test_expect_panicked(const char *file, const char *func, unsigned line) __noreturn;
|
|
|
|
#endif
|