struct test_group {
const char *name;
const enum test_group_flags flags;
struct cmdline_entry *ent;
struct test_verdict (*setup)(struct test_context *);
struct test_verdict (*teardown)(struct test_context *);
fx32_32_t default_intensity;
bool enabled;
bool incremental;
bool exit_on_fail;
struct test **tests[TEST_TIER_MAX];
size_t num_tests[TEST_TIER_MAX];
};
struct test {
const char *name;
struct test_verdict (*func)(struct test_context *ctx);
const struct test_group *group;
enum test_tier tier;
size_t msg_cap;
enum test_flags flags;
time_t duration_ms;
bool print_logs;
bool enabled;
bool keep_going;
fx32_32_t intensity;
uint64_t seed;
size_t run_times;
size_t inject_count;
struct cmdline_entry *base_entry;
struct cmdline_entry *cmdline_entries[TEST_CMDLINE_ENTRY_MAX];
struct inject_site *inject[];
};
struct test_context {
struct log_site *site;
struct log_handle handle;
uint32_t soft_fails;
fx32_32_t intensity;
time_t duration_ms;
uint64_t seed;
};
struct test_verdict {
enum test_result result;
enum test_skip_reason skip_reason;
const char *msg;
};
struct test_globals {
size_t results[TEST_TIER_MAX][TEST_RESULT_MAX];
size_t results_agg[TEST_RESULT_MAX];
struct test_context *current_test;
time_t total_time;
};
enum test_cmdline_entry {
TEST_CMDLINE_ENTRY_ENABLED,
TEST_CMDLINE_ENTRY_MAX,
};
enum test_tier {
TEST_TIER_SMOKE,
TEST_TIER_UNIT,
TEST_TIER_INTEGRATION,
TEST_TIER_MAX,
};
enum test_options {
TEST_OPTION_NONE = 0,
};
enum test_group_flags {
TEST_GROUP_FLAG_NONE = 0,
TEST_GROUP_FLAG_DEFAULT = 1,
};
enum test_result {
TEST_RESULT_OK,
TEST_RESULT_FAILED,
TEST_RESULT_SKIPPED,
TEST_RESULT_MAX,
};
enum test_flags {
TEST_FLAG_NONE = 0,
TEST_FLAG_HONORS_INTENSITY = 1,
};
enum test_skip_reason {
TEST_SKIP_NONE,
TEST_SKIP_RAM_LOW,
TEST_SKIP_UPSTREAM_FAILED,
TEST_SKIP_DISABLED,
};
typedef void (*test_fn_t)(void);
void tests_run(void);
void tests_hook_boot();
size_t test_current_message_count(void);
char test_tier_to_str(enum test_tier tier);
char test_result_to_str(enum test_result result);
char test_skip_reason_to_str(enum test_skip_reason reason);
extern struct test_globals test_global;
extern const char *large_test_string;
extern struct test_group test_group_orphan_parent;
extern struct cmdline_entry test_cmdline_default;
#define TEST_SEED_SENTINEL 0
#define TEST(id) __test_##id
#define TEST_DECLARE(id, ...) \
static struct test_verdict id(struct test_context *ctx); \
extern struct test __test_##id; \
CMDLINE_ENTRY_DECLARE(test_##id, .name = #id, \
.flags = CMDLINE_ENTRY_SYMBOLIC); \
CMDLINE_ENTRY_DECLARE_TYPED( \
test_##id##_enabled, __test_##id.enabled, .name = "enabled", \
.parent = CMDLINE_ENTRY(test_##id), .private = &__test_##id); \
CMDLINE_ENTRY_DECLARE_TYPED( \
test_##id##_intensity, __test_##id.intensity, .name = "intensity", \
.parent = CMDLINE_ENTRY(test_##id), .private = &__test_##id); \
CMDLINE_ENTRY_DECLARE_TYPED( \
test_##id##_run_times, __test_##id.run_times, .name = "run_times", \
.parent = CMDLINE_ENTRY(test_##id), .private = &__test_##id); \
CMDLINE_ENTRY_DECLARE_TYPED( \
test_##id##_seed, __test_##id.seed, .name = "seed", \
.parent = CMDLINE_ENTRY(test_##id), .private = &__test_##id); \
CMDLINE_ENTRY_DECLARE_TYPED( \
test_##id##_duration_ms, __test_##id.duration_ms, \
.name = "duration_ms", .parent = CMDLINE_ENTRY(test_##id), \
.private = &__test_##id); \
CMDLINE_ENTRY_DECLARE_TYPED( \
test_##id##_msg_cap, __test_##id.msg_cap, .name = "msg_cap", \
.parent = CMDLINE_ENTRY(test_##id), .private = &__test_##id); \
CMDLINE_ENTRY_DECLARE_TYPED( \
test_##id##_keep_going, __test_##id.keep_going, .name = "keep_going", \
.parent = CMDLINE_ENTRY(test_##id), .private = &__test_##id); \
CMDLINE_ENTRY_DECLARE_TYPED( \
test_##id##_print_logs, __test_##id.print_logs, .name = "print_logs", \
.parent = CMDLINE_ENTRY(test_##id), .private = &__test_##id); \
LINKER_SECTION_OBJECT(struct test, tests) \
__test_##id = {.name = #id, \
.func = id, \
.run_times = 1, \
.group = &test_group_orphan_parent, \
.enabled = true, \
.base_entry = CMDLINE_ENTRY(test_##id), \
.inject_count = 0, \
.msg_cap = 0, \
.seed = 0, \
.print_logs = false, \
.tier = TEST_TIER_UNIT, \
.cmdline_entries[TEST_CMDLINE_ENTRY_ENABLED] = \
CMDLINE_ENTRY(test_##id##_enabled), \
__VA_ARGS__}; \
\
static struct test_verdict id(struct test_context *ctx __unused)
#define TEST_CMDLINE_ENTRY_DECLARE(id, var, n) \
CMDLINE_ENTRY_DECLARE_TYPED(test_##id##_##n, var, .name = #n, \
.parent = CMDLINE_ENTRY(test_##id), \
.private = &__test_##id)
#define TEST_GROUP_DECLARE(n, ...) \
extern struct test_group __test_group_##n; \
CMDLINE_ENTRY_DECLARE(test_group_##n, .name = #n, \
.parent = CMDLINE_ENTRY(test_root), \
.flags = CMDLINE_ENTRY_SYMBOLIC) \
CMDLINE_ENTRY_DECLARE_TYPED(test_group_##n##_enabled, \
__test_group_##n.enabled, .name = "enabled", \
.parent = CMDLINE_ENTRY(test_group_##n), \
.private = &__test_group_##n); \
CMDLINE_ENTRY_DECLARE_TYPED( \
test_group_##n##_incremental, __test_group_##n.incremental, \
.name = "incremental", .parent = CMDLINE_ENTRY(test_group_##n), \
.private = &__test_group_##n); \
CMDLINE_ENTRY_DECLARE_TYPED( \
test_group_##n##_exit_on_fail, __test_group_##n.exit_on_fail, \
.name = "exit_on_fail", .parent = CMDLINE_ENTRY(test_group_##n), \
.private = &__test_group_##n); \
LINKER_SECTION_OBJECT(struct test_group, test_groups) \
__test_group_##n = {.name = #n, \
.incremental = false, \
.exit_on_fail = false, \
.enabled = true, \
__VA_ARGS__}
#define TEST_GROUP(name) __test_group_##name
#define TEST_SUCCESS ((struct test_verdict) {.result = TEST_RESULT_OK})
#define TEST_FAIL(m) \
((struct test_verdict) {.result = TEST_RESULT_FAILED, .msg = (m)})
#define TEST_SKIP(r) \
((struct test_verdict) {.result = TEST_RESULT_SKIPPED, .skip_reason = (r)})
#define TEST_ASSERT(x) \
do { \
if (!(x)) { \
printf(" assert \"%s\" failed at %s:%d ", #x, __FILE__, __LINE__); \
return TEST_FAIL(#x); \
} \
} while (0)
#define TEST_ASSERT_VOID(x) \
do { \
if (!(x)) { \
printf(" assert \"%s\" failed at %s:%d ", #x, __FILE__, __LINE__); \
return; \
} \
} while (0)
#define test_log(lvl, fmt, ...) \
log(test_global.current_test->site, &test_global.current_test->handle, \
lvl, fmt, ##__VA_ARGS__)
#define test_err(fmt, ...) test_log(LOG_ERROR, fmt, ##__VA_ARGS__)
#define test_warn(fmt, ...) test_log(LOG_WARN, fmt, ##__VA_ARGS__)
#define test_info(fmt, ...) test_log(LOG_INFO, fmt, ##__VA_ARGS__)
#define test_debug(fmt, ...) test_log(LOG_DEBUG, fmt, ##__VA_ARGS__)
#define test_trace(fmt, ...) test_log(LOG_TRACE, fmt, ##__VA_ARGS__)
#define FAIL_IF_FATAL(op) TEST_ASSERT(!ERR_IS_FATAL(op))
#define ABORT_IF_RAM_LOW() \
if (pmm_get_usable_ram() < 1024 * 1024 * 8) { \
test_info("RAM too low for test to continue!\n"); \
return TEST_SKIP(TEST_SKIP_RAM_LOW); \
}