test: Add kernel tests
This commit is contained in:
@@ -50,6 +50,11 @@ SECTIONS
|
|||||||
KEEP(*(.initgraph.edges))
|
KEEP(*(.initgraph.edges))
|
||||||
__ld_initgraph_edges_end = .;
|
__ld_initgraph_edges_end = .;
|
||||||
|
|
||||||
|
. = ALIGN(8);
|
||||||
|
__ld_ktest_start = .;
|
||||||
|
KEEP(*(.ktest))
|
||||||
|
__ld_ktest_end = .;
|
||||||
|
|
||||||
*(.got .got.*)
|
*(.got .got.*)
|
||||||
|
|
||||||
__ld_rodata_end = .;
|
__ld_rodata_end = .;
|
||||||
|
|||||||
@@ -0,0 +1,107 @@
|
|||||||
|
// SPDX-License-Identifier: ISC
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <zinnia/compiler.h>
|
||||||
|
#include <zinnia/utils.h>
|
||||||
|
#include <stddef.h> // IWYU pragma: keep
|
||||||
|
#include <stdint.h> // IWYU pragma: keep
|
||||||
|
|
||||||
|
struct ktest {
|
||||||
|
const char* suite;
|
||||||
|
const char* name;
|
||||||
|
bool failed;
|
||||||
|
bool skipped;
|
||||||
|
const char* skip_reason;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ktest_case {
|
||||||
|
const char* suite;
|
||||||
|
const char* name;
|
||||||
|
void (*func)(struct ktest* test);
|
||||||
|
};
|
||||||
|
|
||||||
|
#define KTEST(suite_name, case_name) \
|
||||||
|
static void ktest_##suite_name##_##case_name(struct ktest* test); \
|
||||||
|
[[__used, __section(".ktest"), gnu::aligned(8)]] \
|
||||||
|
static const struct ktest_case ktest_##suite_name##_##case_name##_case = { \
|
||||||
|
.suite = #suite_name, \
|
||||||
|
.name = #case_name, \
|
||||||
|
.func = ktest_##suite_name##_##case_name, \
|
||||||
|
}; \
|
||||||
|
static void ktest_##suite_name##_##case_name(struct ktest* test)
|
||||||
|
|
||||||
|
[[gnu::format(printf, 4, 5)]]
|
||||||
|
void ktest_fail(struct ktest* test, const char* file, int line, const char* fmt, ...);
|
||||||
|
|
||||||
|
void ktest_main();
|
||||||
|
|
||||||
|
#define _KTEST_CHECK(cond, on_fail, ...) \
|
||||||
|
do { \
|
||||||
|
if (!(cond)) { \
|
||||||
|
ktest_fail(test, __FILE__, __LINE__, __VA_ARGS__); \
|
||||||
|
on_fail; \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define _KTEST_CMP(a, op, b, on_fail) \
|
||||||
|
do { \
|
||||||
|
const typeof(true ? (a) : (b)) _ktest_a = (a); \
|
||||||
|
const typeof(true ? (a) : (b)) _ktest_b = (b); \
|
||||||
|
_KTEST_CHECK( \
|
||||||
|
_ktest_a op _ktest_b, \
|
||||||
|
on_fail, \
|
||||||
|
"expected %s %s %s, got %#llx and %#llx", \
|
||||||
|
#a, \
|
||||||
|
#op, \
|
||||||
|
#b, \
|
||||||
|
(unsigned long long)_ktest_a, \
|
||||||
|
(unsigned long long)_ktest_b \
|
||||||
|
); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define _KTEST_STREQ(a, b, on_fail) \
|
||||||
|
do { \
|
||||||
|
const char* _ktest_a = (a); \
|
||||||
|
const char* _ktest_b = (b); \
|
||||||
|
_KTEST_CHECK( \
|
||||||
|
_ktest_a && _ktest_b && !strcmp(_ktest_a, _ktest_b), \
|
||||||
|
on_fail, \
|
||||||
|
"expected %s == %s, got \"%s\" and \"%s\"", \
|
||||||
|
#a, \
|
||||||
|
#b, \
|
||||||
|
_ktest_a ? _ktest_a : "(null)", \
|
||||||
|
_ktest_b ? _ktest_b : "(null)" \
|
||||||
|
); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define KTEST_EXPECT_TRUE(cond) _KTEST_CHECK(cond, (void)0, "expected %s", #cond)
|
||||||
|
#define KTEST_EXPECT_FALSE(cond) _KTEST_CHECK(!(cond), (void)0, "expected !(%s)", #cond)
|
||||||
|
#define KTEST_EXPECT_EQ(a, b) _KTEST_CMP(a, ==, b, (void)0)
|
||||||
|
#define KTEST_EXPECT_NE(a, b) _KTEST_CMP(a, !=, b, (void)0)
|
||||||
|
#define KTEST_EXPECT_LT(a, b) _KTEST_CMP(a, <, b, (void)0)
|
||||||
|
#define KTEST_EXPECT_LE(a, b) _KTEST_CMP(a, <=, b, (void)0)
|
||||||
|
#define KTEST_EXPECT_GT(a, b) _KTEST_CMP(a, >, b, (void)0)
|
||||||
|
#define KTEST_EXPECT_GE(a, b) _KTEST_CMP(a, >=, b, (void)0)
|
||||||
|
#define KTEST_EXPECT_STREQ(a, b) _KTEST_STREQ(a, b, (void)0)
|
||||||
|
#define KTEST_EXPECT_NULL(ptr) _KTEST_CHECK((ptr) == nullptr, (void)0, "expected %s to be null", #ptr)
|
||||||
|
#define KTEST_EXPECT_NOT_NULL(ptr) _KTEST_CHECK((ptr) != nullptr, (void)0, "expected %s to be non-null", #ptr)
|
||||||
|
|
||||||
|
#define KTEST_ASSERT_TRUE(cond) _KTEST_CHECK(cond, return, "expected %s", #cond)
|
||||||
|
#define KTEST_ASSERT_FALSE(cond) _KTEST_CHECK(!(cond), return, "expected !(%s)", #cond)
|
||||||
|
#define KTEST_ASSERT_EQ(a, b) _KTEST_CMP(a, ==, b, return)
|
||||||
|
#define KTEST_ASSERT_NE(a, b) _KTEST_CMP(a, !=, b, return)
|
||||||
|
#define KTEST_ASSERT_LT(a, b) _KTEST_CMP(a, <, b, return)
|
||||||
|
#define KTEST_ASSERT_LE(a, b) _KTEST_CMP(a, <=, b, return)
|
||||||
|
#define KTEST_ASSERT_GT(a, b) _KTEST_CMP(a, >, b, return)
|
||||||
|
#define KTEST_ASSERT_GE(a, b) _KTEST_CMP(a, >=, b, return)
|
||||||
|
#define KTEST_ASSERT_STREQ(a, b) _KTEST_STREQ(a, b, return)
|
||||||
|
#define KTEST_ASSERT_NULL(ptr) _KTEST_CHECK((ptr) == nullptr, return, "expected %s to be null", #ptr)
|
||||||
|
#define KTEST_ASSERT_NOT_NULL(ptr) _KTEST_CHECK((ptr) != nullptr, return, "expected %s to be non-null", #ptr)
|
||||||
|
|
||||||
|
#define KTEST_SKIP(reason) \
|
||||||
|
do { \
|
||||||
|
(test)->skipped = true; \
|
||||||
|
(test)->skip_reason = (reason); \
|
||||||
|
return; \
|
||||||
|
} while (0)
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
#include <zinnia/initgraph.h>
|
#include <zinnia/initgraph.h>
|
||||||
#include <zinnia/initramfs.h>
|
#include <zinnia/initramfs.h>
|
||||||
#include <zinnia/irq.h>
|
#include <zinnia/irq.h>
|
||||||
|
#include <zinnia/ktest.h>
|
||||||
#include <zinnia/module.h>
|
#include <zinnia/module.h>
|
||||||
#include <zinnia/percpu.h>
|
#include <zinnia/percpu.h>
|
||||||
#include <zinnia/print.h>
|
#include <zinnia/print.h>
|
||||||
@@ -48,6 +49,10 @@ static void kernel_main_task(uintptr_t arg0) {
|
|||||||
// Load all initramfs files.
|
// Load all initramfs files.
|
||||||
initramfs_load_boot_modules(info);
|
initramfs_load_boot_modules(info);
|
||||||
|
|
||||||
|
#if CONFIG_KTEST
|
||||||
|
ktest_main();
|
||||||
|
#endif
|
||||||
|
|
||||||
const char* init_argv[] = {init_path, nullptr};
|
const char* init_argv[] = {init_path, nullptr};
|
||||||
const char* init_envp[] = {"HOME=/", "TERM=console", nullptr};
|
const char* init_envp[] = {"HOME=/", "TERM=console", nullptr};
|
||||||
|
|
||||||
|
|||||||
@@ -105,6 +105,10 @@ subdir('syscalls')
|
|||||||
subdir('dev')
|
subdir('dev')
|
||||||
subdir('fs')
|
subdir('fs')
|
||||||
|
|
||||||
|
if kconfig.get('CONFIG_KTEST', 'n') == 'y'
|
||||||
|
subdir('tests/kernel')
|
||||||
|
endif
|
||||||
|
|
||||||
configure_file(
|
configure_file(
|
||||||
configuration: cfg,
|
configuration: cfg,
|
||||||
output: 'config.h',
|
output: 'config.h',
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
// SPDX-License-Identifier: ISC
|
||||||
|
|
||||||
|
#include <zinnia/ktest.h>
|
||||||
|
|
||||||
|
KTEST(example, arithmetic) {
|
||||||
|
KTEST_EXPECT_EQ(1 + 1, 2);
|
||||||
|
KTEST_EXPECT_LT(1, 2);
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
kernel_sources += files(
|
||||||
|
'example.c',
|
||||||
|
'runner.c',
|
||||||
|
)
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
// SPDX-License-Identifier: ISC
|
||||||
|
|
||||||
|
#include <zinnia/cmdline.h>
|
||||||
|
#include <zinnia/ktest.h>
|
||||||
|
#include <zinnia/print.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
|
||||||
|
extern const struct ktest_case __ld_ktest_start[];
|
||||||
|
extern const struct ktest_case __ld_ktest_end[];
|
||||||
|
|
||||||
|
static bool ktest_enabled = false;
|
||||||
|
|
||||||
|
static void ktest_option(bool value) {
|
||||||
|
ktest_enabled = value;
|
||||||
|
}
|
||||||
|
CMDLINE_OPTION("ktest", "Run all kernel tests", ktest_option);
|
||||||
|
|
||||||
|
void ktest_fail(struct ktest* test, const char* file, int line, const char* fmt, ...) {
|
||||||
|
char buf[256];
|
||||||
|
va_list args;
|
||||||
|
va_start(args, fmt);
|
||||||
|
kvsnprintf(buf, sizeof(buf), fmt, args);
|
||||||
|
va_end(args);
|
||||||
|
|
||||||
|
test->failed = true;
|
||||||
|
log("# %s.%s: %s:%d: %s\n", test->suite, test->name, file, line, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ktest_main() {
|
||||||
|
if (!ktest_enabled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
size_t index = 0;
|
||||||
|
for (const struct ktest_case* c = __ld_ktest_start; c < __ld_ktest_end; c++) {
|
||||||
|
struct ktest test = {.suite = c->suite, .name = c->name};
|
||||||
|
c->func(&test);
|
||||||
|
|
||||||
|
index++;
|
||||||
|
if (test.failed)
|
||||||
|
log("not ok %zu %s.%s\n", index, c->suite, c->name);
|
||||||
|
else if (test.skipped)
|
||||||
|
log("ok %zu %s.%s # SKIP %s\n", index, c->suite, c->name, test.skip_reason ? test.skip_reason : "");
|
||||||
|
else
|
||||||
|
log("ok %zu %s.%s\n", index, c->suite, c->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
log("1..%zu\n", index);
|
||||||
|
}
|
||||||
@@ -86,6 +86,10 @@ qemu = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
test_images = {
|
test_images = {
|
||||||
|
'kernel': {
|
||||||
|
'cmdline': 'ktest=1',
|
||||||
|
'files': [],
|
||||||
|
},
|
||||||
'user': {
|
'user': {
|
||||||
'cmdline': 'init=/init initrd=/initramfs.tar',
|
'cmdline': 'init=/init initrd=/initramfs.tar',
|
||||||
'files': [test_initramfs],
|
'files': [test_initramfs],
|
||||||
|
|||||||
Reference in New Issue
Block a user