test: Add userspace tests
This commit is contained in:
@@ -9,3 +9,4 @@ source "boot/Kconfig"
|
||||
source "mem/Kconfig"
|
||||
source "sched/Kconfig"
|
||||
source "dev/Kconfig"
|
||||
source "tests/Kconfig"
|
||||
|
||||
@@ -23,6 +23,7 @@ add_languages('c', native: false, required: true)
|
||||
|
||||
# Check if we're using a sane compiler.
|
||||
c_compiler = meson.get_compiler('c')
|
||||
python = import('python').find_installation('python3')
|
||||
|
||||
# We require a compiler that supports the GNU C dialect.
|
||||
assert(c_compiler.has_define('__GNUC__'))
|
||||
@@ -176,6 +177,10 @@ kernel = executable(
|
||||
|
||||
subdir('boot')
|
||||
|
||||
if kconfig.get('CONFIG_KTEST', 'n') == 'y'
|
||||
subdir('tests')
|
||||
endif
|
||||
|
||||
foreach name, v : modular_modules
|
||||
shared_library(
|
||||
name,
|
||||
|
||||
@@ -18,3 +18,10 @@ option(
|
||||
value: '.config',
|
||||
description: 'Relative path to the generated Kconfig file',
|
||||
)
|
||||
|
||||
option(
|
||||
'ovmf',
|
||||
type: 'string',
|
||||
value: '',
|
||||
description: 'OVMF firmware image used to run the QEMU tests',
|
||||
)
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
menu "Testing"
|
||||
|
||||
config KTEST
|
||||
bool "Kernel test suite"
|
||||
depends on BOOT_EFISTUB
|
||||
default n
|
||||
help
|
||||
Builds the kernel unit tests. To run them, add "ktest=1" to the cmdline.
|
||||
|
||||
endmenu
|
||||
@@ -0,0 +1 @@
|
||||
@CMDLINE@
|
||||
@@ -0,0 +1,121 @@
|
||||
fs = import('fs')
|
||||
|
||||
assert(
|
||||
kconfig.get('CONFIG_BOOT_EFISTUB', 'n') == 'y',
|
||||
'CONFIG_KTEST depends on CONFIG_BOOT_EFISTUB',
|
||||
)
|
||||
|
||||
assert(
|
||||
target_machine.cpu_family() == 'x86_64',
|
||||
'CONFIG_KTEST is only supported on x86_64',
|
||||
)
|
||||
|
||||
# All userspace tests are a single executable, so they have to be idempotent.
|
||||
utest_init = executable(
|
||||
'init',
|
||||
sources: files(
|
||||
'user/lib/crt0.S',
|
||||
'user/lib/utest.c',
|
||||
'user/memory.c',
|
||||
'user/system.c',
|
||||
),
|
||||
include_directories: include_directories('../include'),
|
||||
c_args: [
|
||||
'-ffreestanding',
|
||||
'-nostdinc',
|
||||
'-fno-pic',
|
||||
'-fno-stack-protector',
|
||||
'-masm=intel',
|
||||
],
|
||||
link_args: [
|
||||
'-nostdlib',
|
||||
'-static',
|
||||
'-Wl,--no-dynamic-linker',
|
||||
'-Wl,-z,max-page-size=0x1000',
|
||||
'-Wl,-z,noexecstack',
|
||||
'-Wl,--build-id=none',
|
||||
],
|
||||
pie: false,
|
||||
)
|
||||
|
||||
test_initramfs = custom_target(
|
||||
'initramfs.tar',
|
||||
input: utest_init,
|
||||
output: 'initramfs.tar',
|
||||
command: [
|
||||
find_program('tar'),
|
||||
'--format=ustar',
|
||||
'-cf', '@OUTPUT@',
|
||||
'-C', '@OUTDIR@',
|
||||
'init',
|
||||
],
|
||||
)
|
||||
|
||||
# Try to find an OVMF.
|
||||
ovmf = get_option('ovmf')
|
||||
if ovmf == ''
|
||||
foreach path : [
|
||||
'/usr/share/qemu/ovmf-x86_64-4m.bin',
|
||||
'/usr/share/qemu/ovmf-x86_64.bin',
|
||||
'/usr/share/ovmf/OVMF.fd',
|
||||
'/usr/share/edk2/x64/OVMF.4m.fd',
|
||||
'/usr/share/edk2/x64/OVMF.fd',
|
||||
'/usr/share/edk2/ovmf/OVMF.fd',
|
||||
]
|
||||
if ovmf == '' and fs.is_file(path)
|
||||
ovmf = path
|
||||
endif
|
||||
endforeach
|
||||
endif
|
||||
assert(ovmf != '', 'CONFIG_KTEST needs an OVMF image, set it with -Dovmf=')
|
||||
|
||||
qemu = [
|
||||
find_program('qemu-system-x86_64'),
|
||||
'-M', 'q35',
|
||||
'-m', '512M',
|
||||
'-smp', '4',
|
||||
'-accel', 'kvm',
|
||||
'-accel', 'tcg',
|
||||
'-cpu', 'max',
|
||||
'-nic', 'none',
|
||||
'-display', 'none',
|
||||
'-monitor', 'none',
|
||||
'-serial', 'stdio',
|
||||
'-no-reboot',
|
||||
'-bios', ovmf,
|
||||
]
|
||||
|
||||
test_images = {
|
||||
'user': {
|
||||
'cmdline': 'init=/init initrd=/initramfs.tar',
|
||||
'files': [test_initramfs],
|
||||
},
|
||||
}
|
||||
|
||||
# Build an ESP with the cmdline file and an initramfs.
|
||||
foreach name, image : test_images
|
||||
conf = configure_file(
|
||||
input: 'cmdline.in',
|
||||
output: name + '.cmdline',
|
||||
configuration: {'CMDLINE': image['cmdline']},
|
||||
)
|
||||
|
||||
esp = [
|
||||
zinnia_efi.full_path() + '=EFI/BOOT/BOOTX64.EFI',
|
||||
conf.full_path() + '=EFI/zinnia/cmdline',
|
||||
]
|
||||
|
||||
foreach file : image['files']
|
||||
esp += file.full_path() + '=' + fs.name(file.full_path())
|
||||
endforeach
|
||||
|
||||
test(
|
||||
name,
|
||||
python,
|
||||
args: [files('run.py'), esp, '--', qemu],
|
||||
depends: [zinnia_efi, image['files']],
|
||||
protocol: 'tap',
|
||||
suite: 'qemu',
|
||||
timeout: 60,
|
||||
)
|
||||
endforeach
|
||||
@@ -0,0 +1,37 @@
|
||||
# SPDX-License-Identifier: ISC
|
||||
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
NOISE_RE = re.compile(r"\x1b\[[0-9;]*m|\r|^\[[ \d.]+\] (?:syslog\(\w+\): )?")
|
||||
PLAN_RE = re.compile(r"^1\.\.\d+")
|
||||
TAP_RE = re.compile(r"^(?:ok|not ok|#|1\.\.\d+|Bail out!)")
|
||||
|
||||
sep = sys.argv.index("--")
|
||||
with tempfile.TemporaryDirectory(prefix="zinnia-esp-") as esp:
|
||||
for spec in sys.argv[1:sep]:
|
||||
src, dest = spec.split("=", 1)
|
||||
path = os.path.join(esp, dest)
|
||||
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||||
shutil.copyfile(src, path)
|
||||
|
||||
proc = subprocess.Popen(
|
||||
sys.argv[sep + 1 :] + ["-drive", f"format=raw,file=fat:{esp},if=virtio,readonly=on"],
|
||||
stdin=subprocess.DEVNULL,
|
||||
stdout=subprocess.PIPE,
|
||||
text=True,
|
||||
errors="replace",
|
||||
)
|
||||
for line in proc.stdout:
|
||||
line = NOISE_RE.sub("", line).rstrip()
|
||||
print(line if TAP_RE.match(line) else f"# {line}", flush=True)
|
||||
if PLAN_RE.match(line):
|
||||
proc.kill()
|
||||
proc.wait()
|
||||
sys.exit(0)
|
||||
proc.wait()
|
||||
sys.exit(1)
|
||||
@@ -0,0 +1,18 @@
|
||||
// SPDX-License-Identifier: ISC
|
||||
|
||||
.intel_syntax noprefix
|
||||
.text
|
||||
|
||||
.globl _start
|
||||
.type _start, @function
|
||||
_start:
|
||||
xor ebp, ebp
|
||||
and rsp, -16
|
||||
call main
|
||||
mov edi, eax
|
||||
xor eax, eax
|
||||
syscall
|
||||
ud2
|
||||
.size _start, . - _start
|
||||
|
||||
.section .note.GNU-stack, "", @progbits
|
||||
@@ -0,0 +1,229 @@
|
||||
// SPDX-License-Identifier: ISC
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
enum syscall_number : uintptr_t {
|
||||
#define SYSCALL_DECLARE(number, name, num_args) SYS_##name = number,
|
||||
#include <zinnia/syscall_list.inc>
|
||||
#undef SYSCALL_DECLARE
|
||||
};
|
||||
|
||||
enum syslog_kind : uintptr_t {
|
||||
SYSLOG_ERR = 3,
|
||||
SYSLOG_INFO = 6,
|
||||
};
|
||||
|
||||
struct sys_ret {
|
||||
uintptr_t value;
|
||||
uintptr_t error;
|
||||
};
|
||||
|
||||
static inline struct sys_ret syscall6(
|
||||
enum syscall_number num,
|
||||
uintptr_t a0,
|
||||
uintptr_t a1,
|
||||
uintptr_t a2,
|
||||
uintptr_t a3,
|
||||
uintptr_t a4,
|
||||
uintptr_t a5
|
||||
) {
|
||||
register uintptr_t r10 asm("r10") = a3;
|
||||
register uintptr_t r8 asm("r8") = a4;
|
||||
register uintptr_t r9 asm("r9") = a5;
|
||||
uintptr_t value;
|
||||
uintptr_t error;
|
||||
asm volatile("syscall"
|
||||
: "=a"(value), "=d"(error)
|
||||
: "a"(num), "D"(a0), "S"(a1), "d"(a2), "r"(r10), "r"(r8), "r"(r9)
|
||||
: "rcx", "r11", "memory");
|
||||
return (struct sys_ret){value, error};
|
||||
}
|
||||
|
||||
static inline struct sys_ret syscall3(enum syscall_number num, uintptr_t a0, uintptr_t a1, uintptr_t a2) {
|
||||
return syscall6(num, a0, a1, a2, 0, 0, 0);
|
||||
}
|
||||
|
||||
static inline struct sys_ret syscall4(enum syscall_number num, uintptr_t a0, uintptr_t a1, uintptr_t a2, uintptr_t a3) {
|
||||
return syscall6(num, a0, a1, a2, a3, 0, 0);
|
||||
}
|
||||
|
||||
static inline struct sys_ret syscall5(
|
||||
enum syscall_number num,
|
||||
uintptr_t a0,
|
||||
uintptr_t a1,
|
||||
uintptr_t a2,
|
||||
uintptr_t a3,
|
||||
uintptr_t a4
|
||||
) {
|
||||
return syscall6(num, a0, a1, a2, a3, a4, 0);
|
||||
}
|
||||
|
||||
[[noreturn]]
|
||||
static inline void sys_exit(int status) {
|
||||
syscall3(SYS_exit, (uintptr_t)status, 0, 0);
|
||||
__builtin_unreachable();
|
||||
}
|
||||
|
||||
static inline struct sys_ret sys_syslog(enum syslog_kind kind, const char* msg, size_t len) {
|
||||
return syscall3(SYS_syslog, kind, (uintptr_t)msg, len);
|
||||
}
|
||||
|
||||
static inline struct sys_ret sys_mmap(void* addr, size_t len, uint32_t prot, uint32_t flags, int fd, int64_t off) {
|
||||
return syscall6(SYS_mmap, (uintptr_t)addr, len, prot, flags, (uintptr_t)fd, (uintptr_t)off);
|
||||
}
|
||||
|
||||
static inline struct sys_ret sys_munmap(void* addr, size_t len) {
|
||||
return syscall3(SYS_munmap, (uintptr_t)addr, len, 0);
|
||||
}
|
||||
|
||||
static inline struct sys_ret sys_openat(int dirfd, const char* path, uint32_t flags, uint32_t mode) {
|
||||
return syscall4(SYS_openat, (uintptr_t)dirfd, (uintptr_t)path, flags, mode);
|
||||
}
|
||||
|
||||
static inline struct sys_ret sys_close(int fd) {
|
||||
return syscall3(SYS_close, (uintptr_t)fd, 0, 0);
|
||||
}
|
||||
|
||||
static inline struct sys_ret sys_pread(int fd, void* buf, size_t len, uint64_t off) {
|
||||
return syscall4(SYS_pread, (uintptr_t)fd, (uintptr_t)buf, len, off);
|
||||
}
|
||||
|
||||
static inline struct sys_ret sys_fstat(int fd, void* buf) {
|
||||
return syscall3(SYS_fstat, (uintptr_t)fd, (uintptr_t)buf, 0);
|
||||
}
|
||||
|
||||
static inline struct sys_ret sys_getuname(void* buf) {
|
||||
return syscall3(SYS_getuname, (uintptr_t)buf, 0, 0);
|
||||
}
|
||||
|
||||
static inline struct sys_ret sys_setuname(const void* buf) {
|
||||
return syscall3(SYS_setuname, (uintptr_t)buf, 0, 0);
|
||||
}
|
||||
|
||||
static inline struct sys_ret sys_mprotect(void* addr, size_t len, uint32_t prot) {
|
||||
return syscall3(SYS_mprotect, (uintptr_t)addr, len, prot);
|
||||
}
|
||||
|
||||
static inline struct sys_ret sys_madvise(void* addr, size_t len, int advice) {
|
||||
return syscall3(SYS_madvise, (uintptr_t)addr, len, (uintptr_t)advice);
|
||||
}
|
||||
|
||||
static inline struct sys_ret sys_msync(void* addr, size_t len, int flags) {
|
||||
return syscall3(SYS_msync, (uintptr_t)addr, len, (uintptr_t)flags);
|
||||
}
|
||||
|
||||
static inline struct sys_ret sys_pwrite(int fd, const void* buf, size_t len, uint64_t off) {
|
||||
return syscall4(SYS_pwrite, (uintptr_t)fd, (uintptr_t)buf, len, off);
|
||||
}
|
||||
|
||||
static inline struct sys_ret sys_readv(int fd, const void* iov, size_t count) {
|
||||
return syscall3(SYS_readv, (uintptr_t)fd, (uintptr_t)iov, count);
|
||||
}
|
||||
|
||||
static inline struct sys_ret sys_writev(int fd, const void* iov, size_t count) {
|
||||
return syscall3(SYS_writev, (uintptr_t)fd, (uintptr_t)iov, count);
|
||||
}
|
||||
|
||||
static inline struct sys_ret sys_seek(int fd, int64_t off, int whence) {
|
||||
return syscall3(SYS_seek, (uintptr_t)fd, (uintptr_t)off, (uintptr_t)whence);
|
||||
}
|
||||
|
||||
static inline struct sys_ret sys_fstatat(int dirfd, const char* path, void* buf, uint32_t flags) {
|
||||
return syscall4(SYS_fstatat, (uintptr_t)dirfd, (uintptr_t)path, (uintptr_t)buf, flags);
|
||||
}
|
||||
|
||||
static inline struct sys_ret sys_faccessat(int dirfd, const char* path, uint32_t mode, uint32_t flags) {
|
||||
return syscall4(SYS_faccessat, (uintptr_t)dirfd, (uintptr_t)path, mode, flags);
|
||||
}
|
||||
|
||||
static inline struct sys_ret sys_fcntl(int fd, int cmd, uintptr_t arg) {
|
||||
return syscall3(SYS_fcntl, (uintptr_t)fd, (uintptr_t)cmd, arg);
|
||||
}
|
||||
|
||||
static inline struct sys_ret sys_ftruncate(int fd, int64_t len) {
|
||||
return syscall3(SYS_ftruncate, (uintptr_t)fd, (uintptr_t)len, 0);
|
||||
}
|
||||
|
||||
static inline struct sys_ret sys_truncate(const char* path, int64_t len) {
|
||||
return syscall3(SYS_truncate, (uintptr_t)path, (uintptr_t)len, 0);
|
||||
}
|
||||
|
||||
static inline struct sys_ret sys_getcwd(char* buf, size_t len) {
|
||||
return syscall3(SYS_getcwd, (uintptr_t)buf, len, 0);
|
||||
}
|
||||
|
||||
static inline struct sys_ret sys_chdir(const char* path) {
|
||||
return syscall3(SYS_chdir, (uintptr_t)path, 0, 0);
|
||||
}
|
||||
|
||||
static inline struct sys_ret sys_fchdir(int fd) {
|
||||
return syscall3(SYS_fchdir, (uintptr_t)fd, 0, 0);
|
||||
}
|
||||
|
||||
static inline struct sys_ret sys_mkdirat(int dirfd, const char* path, uint32_t mode) {
|
||||
return syscall3(SYS_mkdirat, (uintptr_t)dirfd, (uintptr_t)path, mode);
|
||||
}
|
||||
|
||||
static inline struct sys_ret sys_rmdirat(int dirfd, const char* path) {
|
||||
return syscall3(SYS_rmdirat, (uintptr_t)dirfd, (uintptr_t)path, 0);
|
||||
}
|
||||
|
||||
static inline struct sys_ret sys_unlinkat(int dirfd, const char* path, uint32_t flags) {
|
||||
return syscall3(SYS_unlinkat, (uintptr_t)dirfd, (uintptr_t)path, flags);
|
||||
}
|
||||
|
||||
static inline struct sys_ret sys_getdents(int fd, void* buf, size_t len) {
|
||||
return syscall3(SYS_getdents, (uintptr_t)fd, (uintptr_t)buf, len);
|
||||
}
|
||||
|
||||
static inline struct sys_ret sys_renameat(int old_dirfd, const char* old_path, int new_dirfd, const char* new_path) {
|
||||
return syscall4(SYS_renameat, (uintptr_t)old_dirfd, (uintptr_t)old_path, (uintptr_t)new_dirfd, (uintptr_t)new_path);
|
||||
}
|
||||
|
||||
static inline struct sys_ret sys_linkat(
|
||||
int old_dirfd,
|
||||
const char* old_path,
|
||||
int new_dirfd,
|
||||
const char* new_path,
|
||||
uint32_t flags
|
||||
) {
|
||||
return syscall5(
|
||||
SYS_linkat,
|
||||
(uintptr_t)old_dirfd,
|
||||
(uintptr_t)old_path,
|
||||
(uintptr_t)new_dirfd,
|
||||
(uintptr_t)new_path,
|
||||
flags
|
||||
);
|
||||
}
|
||||
|
||||
static inline struct sys_ret sys_symlinkat(const char* target, int dirfd, const char* path) {
|
||||
return syscall3(SYS_symlinkat, (uintptr_t)target, (uintptr_t)dirfd, (uintptr_t)path);
|
||||
}
|
||||
|
||||
static inline struct sys_ret sys_readlinkat(int dirfd, const char* path, char* buf, size_t len) {
|
||||
return syscall4(SYS_readlinkat, (uintptr_t)dirfd, (uintptr_t)path, (uintptr_t)buf, len);
|
||||
}
|
||||
|
||||
static inline struct sys_ret sys_dup(int fd) {
|
||||
return syscall3(SYS_dup, (uintptr_t)fd, 0, 0);
|
||||
}
|
||||
|
||||
static inline struct sys_ret sys_dup3(int old_fd, int new_fd, uint32_t flags) {
|
||||
return syscall3(SYS_dup3, (uintptr_t)old_fd, (uintptr_t)new_fd, flags);
|
||||
}
|
||||
|
||||
static inline struct sys_ret sys_umask(uint32_t mask) {
|
||||
return syscall3(SYS_umask, mask, 0, 0);
|
||||
}
|
||||
|
||||
static inline struct sys_ret sys_pipe(int* fds, uint32_t flags) {
|
||||
return syscall3(SYS_pipe, (uintptr_t)fds, flags, 0);
|
||||
}
|
||||
|
||||
static inline struct sys_ret sys_ppoll(void* fds, size_t nfds, const void* timeout, const void* sigmask) {
|
||||
return syscall4(SYS_ppoll, (uintptr_t)fds, nfds, (uintptr_t)timeout, (uintptr_t)sigmask);
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
// SPDX-License-Identifier: ISC
|
||||
|
||||
#include "utest.h"
|
||||
#include <stdarg.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include "syscall.h"
|
||||
|
||||
constexpr size_t LINE_MAX = 240;
|
||||
|
||||
extern const struct utest_case __start_utest_cases[];
|
||||
extern const struct utest_case __stop_utest_cases[];
|
||||
|
||||
size_t strlen(const char* str) {
|
||||
size_t len = 0;
|
||||
while (str[len])
|
||||
len++;
|
||||
return len;
|
||||
}
|
||||
|
||||
int memcmp(const void* s1, const void* s2, size_t size) {
|
||||
const uint8_t* a = s1;
|
||||
const uint8_t* b = s2;
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
if (a[i] != b[i])
|
||||
return a[i] < b[i] ? -1 : 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void* memcpy(void* restrict dest, const void* restrict src, size_t n) {
|
||||
uint8_t* d = dest;
|
||||
const uint8_t* s = src;
|
||||
for (size_t i = 0; i < n; i++)
|
||||
d[i] = s[i];
|
||||
return dest;
|
||||
}
|
||||
|
||||
void* memset(void* dest, int value, size_t n) {
|
||||
uint8_t* d = dest;
|
||||
for (size_t i = 0; i < n; i++)
|
||||
d[i] = (uint8_t)value;
|
||||
return dest;
|
||||
}
|
||||
|
||||
bool utest_streq(const char* a, const char* b) {
|
||||
if (!a || !b)
|
||||
return a == b;
|
||||
while (*a && *a == *b) {
|
||||
a++;
|
||||
b++;
|
||||
}
|
||||
return *a == *b;
|
||||
}
|
||||
|
||||
struct line {
|
||||
char buf[LINE_MAX];
|
||||
size_t len;
|
||||
};
|
||||
|
||||
static void put(struct line* line, char c) {
|
||||
if (line->len < sizeof(line->buf) - 1)
|
||||
line->buf[line->len++] = c;
|
||||
}
|
||||
|
||||
static void put_str(struct line* line, const char* str) {
|
||||
for (; *str; str++)
|
||||
put(line, *str);
|
||||
}
|
||||
|
||||
static void put_num(struct line* line, unsigned long long value, unsigned base, bool negative, bool prefix) {
|
||||
char digits[24];
|
||||
size_t n = 0;
|
||||
do {
|
||||
digits[n++] = "0123456789abcdef"[value % base];
|
||||
value /= base;
|
||||
} while (value);
|
||||
if (negative)
|
||||
put(line, '-');
|
||||
if (prefix)
|
||||
put_str(line, "0x");
|
||||
while (n)
|
||||
put(line, digits[--n]);
|
||||
}
|
||||
|
||||
static void format(struct line* line, const char* fmt, va_list args) {
|
||||
for (; *fmt; fmt++) {
|
||||
if (*fmt != '%') {
|
||||
put(line, *fmt);
|
||||
continue;
|
||||
}
|
||||
fmt++;
|
||||
bool alt = false;
|
||||
if (*fmt == '#') {
|
||||
alt = true;
|
||||
fmt++;
|
||||
}
|
||||
int longs = 0;
|
||||
bool size = false;
|
||||
for (;; fmt++) {
|
||||
if (*fmt == 'l')
|
||||
longs++;
|
||||
else if (*fmt == 'z')
|
||||
size = true;
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
switch (*fmt) {
|
||||
case 'd':
|
||||
case 'i': {
|
||||
long long v = size ? (long long)va_arg(args, ptrdiff_t)
|
||||
: longs ? va_arg(args, long long)
|
||||
: va_arg(args, int);
|
||||
put_num(line, v < 0 ? -(unsigned long long)v : (unsigned long long)v, 10, v < 0, false);
|
||||
break;
|
||||
}
|
||||
case 'u':
|
||||
case 'x': {
|
||||
unsigned long long v = size ? va_arg(args, size_t)
|
||||
: longs ? va_arg(args, unsigned long long)
|
||||
: va_arg(args, unsigned int);
|
||||
put_num(line, v, *fmt == 'x' ? 16 : 10, false, alt && *fmt == 'x');
|
||||
break;
|
||||
}
|
||||
case 'p':
|
||||
put_num(line, (uintptr_t)va_arg(args, void*), 16, false, true);
|
||||
break;
|
||||
case 's': {
|
||||
const char* s = va_arg(args, const char*);
|
||||
put_str(line, s ? s : "(null)");
|
||||
break;
|
||||
}
|
||||
case 'c':
|
||||
put(line, (char)va_arg(args, int));
|
||||
break;
|
||||
case '%':
|
||||
put(line, '%');
|
||||
break;
|
||||
default:
|
||||
put(line, '?');
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void emit(struct line* line) {
|
||||
line->buf[line->len] = 0;
|
||||
sys_syslog(SYSLOG_INFO, line->buf, line->len);
|
||||
}
|
||||
|
||||
void utest_log(const char* fmt, ...) {
|
||||
struct line line = {};
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
format(&line, fmt, args);
|
||||
va_end(args);
|
||||
emit(&line);
|
||||
}
|
||||
|
||||
void utest_fail(struct utest* test, const char* file, int line_nr, const char* fmt, ...) {
|
||||
struct line line = {};
|
||||
put_str(&line, "# ");
|
||||
put_str(&line, test->suite);
|
||||
put(&line, '.');
|
||||
put_str(&line, test->name);
|
||||
put_str(&line, ": ");
|
||||
put_str(&line, file);
|
||||
put(&line, ':');
|
||||
put_num(&line, (unsigned)line_nr, 10, false, false);
|
||||
put_str(&line, ": ");
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
format(&line, fmt, args);
|
||||
va_end(args);
|
||||
emit(&line);
|
||||
test->failed = true;
|
||||
}
|
||||
|
||||
void utest_run_all() {
|
||||
size_t index = 0;
|
||||
for (const struct utest_case* c = __start_utest_cases; c < __stop_utest_cases; c++) {
|
||||
struct utest test = {.suite = c->suite, .name = c->name};
|
||||
c->func(&test);
|
||||
|
||||
index++;
|
||||
if (test.skipped)
|
||||
utest_log("ok %zu %s.%s # SKIP %s", index, c->suite, c->name, test.skip_reason ? test.skip_reason : "");
|
||||
else if (c->xfail_reason)
|
||||
utest_log(
|
||||
"%s %zu %s.%s # TODO %s",
|
||||
test.failed ? "not ok" : "ok",
|
||||
index,
|
||||
c->suite,
|
||||
c->name,
|
||||
c->xfail_reason
|
||||
);
|
||||
else
|
||||
utest_log("%s %zu %s.%s", test.failed ? "not ok" : "ok", index, c->suite, c->name);
|
||||
}
|
||||
|
||||
utest_log("1..%zu", index);
|
||||
}
|
||||
|
||||
int main() {
|
||||
utest_run_all();
|
||||
for (;;)
|
||||
;
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
// SPDX-License-Identifier: ISC
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h> // IWYU pragma: keep
|
||||
#include <stdint.h> // IWYU pragma: keep
|
||||
|
||||
struct utest {
|
||||
const char* suite;
|
||||
const char* name;
|
||||
bool failed;
|
||||
bool skipped;
|
||||
const char* skip_reason;
|
||||
};
|
||||
|
||||
struct utest_case {
|
||||
const char* suite;
|
||||
const char* name;
|
||||
const char* xfail_reason;
|
||||
void (*func)(struct utest* test);
|
||||
};
|
||||
|
||||
#define _UTEST(suite_name, case_name, reason) \
|
||||
static void suite_name##_##case_name(struct utest* test); \
|
||||
[[gnu::used, gnu::retain, gnu::section("utest_cases"), gnu::aligned(8)]] \
|
||||
static const struct utest_case suite_name##_##case_name##_case = { \
|
||||
.suite = #suite_name, \
|
||||
.name = #case_name, \
|
||||
.xfail_reason = (reason), \
|
||||
.func = suite_name##_##case_name, \
|
||||
}; \
|
||||
static void suite_name##_##case_name(struct utest* test)
|
||||
|
||||
// Expected success
|
||||
#define UTEST(suite_name, case_name) _UTEST(suite_name, case_name, nullptr)
|
||||
// Expected failure
|
||||
#define UTEST_XFAIL(suite_name, case_name, reason) _UTEST(suite_name, case_name, reason)
|
||||
|
||||
[[gnu::format(printf, 1, 2)]]
|
||||
void utest_log(const char* fmt, ...);
|
||||
|
||||
[[gnu::format(printf, 4, 5)]]
|
||||
void utest_fail(struct utest* test, const char* file, int line, const char* fmt, ...);
|
||||
|
||||
void utest_run_all();
|
||||
|
||||
bool utest_streq(const char* a, const char* b);
|
||||
|
||||
#define _UTEST_CHECK(cond, on_fail, ...) \
|
||||
do { \
|
||||
if (!(cond)) { \
|
||||
utest_fail(test, __FILE__, __LINE__, __VA_ARGS__); \
|
||||
on_fail; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define _UTEST_CMP(a, op, b, on_fail) \
|
||||
do { \
|
||||
const typeof(true ? (a) : (b)) _utest_a = (a); \
|
||||
const typeof(true ? (a) : (b)) _utest_b = (b); \
|
||||
_UTEST_CHECK( \
|
||||
_utest_a op _utest_b, \
|
||||
on_fail, \
|
||||
"expected %s %s %s, got %#llx and %#llx", \
|
||||
#a, \
|
||||
#op, \
|
||||
#b, \
|
||||
(unsigned long long)_utest_a, \
|
||||
(unsigned long long)_utest_b \
|
||||
); \
|
||||
} while (0)
|
||||
|
||||
#define EXPECT_TRUE(cond) _UTEST_CHECK(cond, (void)0, "expected %s", #cond)
|
||||
#define EXPECT_FALSE(cond) _UTEST_CHECK(!(cond), (void)0, "expected !(%s)", #cond)
|
||||
#define EXPECT_EQ(a, b) _UTEST_CMP(a, ==, b, (void)0)
|
||||
#define EXPECT_NE(a, b) _UTEST_CMP(a, !=, b, (void)0)
|
||||
#define EXPECT_GT(a, b) _UTEST_CMP(a, >, b, (void)0)
|
||||
#define EXPECT_GE(a, b) _UTEST_CMP(a, >=, b, (void)0)
|
||||
#define EXPECT_STREQ(a, b) _UTEST_CHECK(utest_streq((a), (b)), (void)0, "expected %s == \"%s\"", #a, (b))
|
||||
|
||||
#define ASSERT_TRUE(cond) _UTEST_CHECK(cond, return, "expected %s", #cond)
|
||||
#define ASSERT_EQ(a, b) _UTEST_CMP(a, ==, b, return)
|
||||
#define ASSERT_NE(a, b) _UTEST_CMP(a, !=, b, return)
|
||||
|
||||
#define SKIP(reason) \
|
||||
do { \
|
||||
test->skipped = true; \
|
||||
test->skip_reason = (reason); \
|
||||
return; \
|
||||
} while (0)
|
||||
@@ -0,0 +1,137 @@
|
||||
// SPDX-License-Identifier: ISC
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <uapi/errno.h>
|
||||
#include <uapi/fcntl.h>
|
||||
#include <uapi/mman.h>
|
||||
#include "lib/syscall.h"
|
||||
#include "lib/utest.h"
|
||||
|
||||
constexpr size_t PAGE_SIZE = 0x1000;
|
||||
constexpr uint32_t PROT_RW = __PROT_READ | __PROT_WRITE;
|
||||
constexpr uint32_t MAP_ANON_PRIVATE = __MAP_PRIVATE | __MAP_ANON;
|
||||
|
||||
static uint8_t* map_anon(size_t len) {
|
||||
struct sys_ret ret = sys_mmap(nullptr, len, PROT_RW, MAP_ANON_PRIVATE, -1, 0);
|
||||
return ret.error ? nullptr : (uint8_t*)ret.value;
|
||||
}
|
||||
|
||||
UTEST(mman, anonymous_mapping) {
|
||||
const size_t len = 4 * PAGE_SIZE;
|
||||
struct sys_ret ret = sys_mmap(nullptr, len, __PROT_READ | __PROT_WRITE, __MAP_PRIVATE | __MAP_ANON, -1, 0);
|
||||
ASSERT_EQ(ret.error, 0);
|
||||
|
||||
uint8_t* mem = (uint8_t*)ret.value;
|
||||
EXPECT_EQ((uintptr_t)mem % PAGE_SIZE, 0);
|
||||
for (size_t i = 0; i < len; i += PAGE_SIZE)
|
||||
EXPECT_EQ(mem[i], 0);
|
||||
for (size_t i = 0; i < len; i++)
|
||||
mem[i] = (uint8_t)i;
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
if (mem[i] != (uint8_t)i) {
|
||||
utest_fail(test, __FILE__, __LINE__, "byte %zu reads back as %u", i, mem[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
EXPECT_EQ(sys_munmap(mem, len).error, 0);
|
||||
}
|
||||
|
||||
UTEST(mman, rejects_zero_length) {
|
||||
struct sys_ret ret = sys_mmap(nullptr, 0, __PROT_READ, __MAP_PRIVATE | __MAP_ANON, -1, 0);
|
||||
EXPECT_EQ(ret.error, __EINVAL);
|
||||
}
|
||||
|
||||
UTEST(mman, shared_anonymous_mapping) {
|
||||
struct sys_ret ret = sys_mmap(nullptr, PAGE_SIZE, PROT_RW, __MAP_SHARED | __MAP_ANON, -1, 0);
|
||||
ASSERT_EQ(ret.error, 0);
|
||||
uint8_t* mem = (uint8_t*)ret.value;
|
||||
EXPECT_EQ(mem[0], 0);
|
||||
mem[0] = 0x5a;
|
||||
mem[PAGE_SIZE - 1] = 0xa5;
|
||||
EXPECT_EQ(mem[0], 0x5a);
|
||||
EXPECT_EQ(mem[PAGE_SIZE - 1], 0xa5);
|
||||
EXPECT_EQ(sys_munmap(mem, PAGE_SIZE).error, 0);
|
||||
}
|
||||
|
||||
UTEST(mman, file_mapping) {
|
||||
struct sys_ret ret = sys_openat(__AT_FDCWD, "/init", __O_RDONLY, 0);
|
||||
ASSERT_EQ(ret.error, 0);
|
||||
const int fd = (int)ret.value;
|
||||
|
||||
ret = sys_mmap(nullptr, PAGE_SIZE, __PROT_READ, __MAP_PRIVATE, fd, 0);
|
||||
EXPECT_EQ(ret.error, 0);
|
||||
if (!ret.error) {
|
||||
const uint8_t elf_header[] = {0x7f, 'E', 'L', 'F'};
|
||||
EXPECT_EQ(memcmp((const void*)ret.value, elf_header, sizeof(elf_header)), 0);
|
||||
EXPECT_EQ(sys_munmap((void*)ret.value, PAGE_SIZE).error, 0);
|
||||
}
|
||||
EXPECT_EQ(sys_close(fd).error, 0);
|
||||
}
|
||||
|
||||
UTEST(mman, fixed_mapping_replaces) {
|
||||
uint8_t* mem = map_anon(3 * PAGE_SIZE);
|
||||
ASSERT_TRUE(mem != nullptr);
|
||||
memset(mem, 0xcc, 3 * PAGE_SIZE);
|
||||
|
||||
struct sys_ret ret = sys_mmap(mem + PAGE_SIZE, PAGE_SIZE, PROT_RW, MAP_ANON_PRIVATE | __MAP_FIXED, -1, 0);
|
||||
EXPECT_EQ(ret.error, 0);
|
||||
EXPECT_EQ(ret.value, (uintptr_t)(mem + PAGE_SIZE));
|
||||
EXPECT_EQ(mem[0], 0xcc);
|
||||
EXPECT_EQ(mem[PAGE_SIZE], 0);
|
||||
EXPECT_EQ(mem[2 * PAGE_SIZE], 0xcc);
|
||||
EXPECT_EQ(sys_munmap(mem, 3 * PAGE_SIZE).error, 0);
|
||||
}
|
||||
|
||||
UTEST(mman, partial_unmap) {
|
||||
uint8_t* mem = map_anon(3 * PAGE_SIZE);
|
||||
ASSERT_TRUE(mem != nullptr);
|
||||
EXPECT_EQ(sys_munmap(mem + PAGE_SIZE, PAGE_SIZE).error, 0);
|
||||
mem[0] = 1;
|
||||
mem[2 * PAGE_SIZE] = 2;
|
||||
EXPECT_EQ(mem[0], 1);
|
||||
EXPECT_EQ(mem[2 * PAGE_SIZE], 2);
|
||||
EXPECT_EQ(sys_munmap(mem, 3 * PAGE_SIZE).error, 0);
|
||||
}
|
||||
|
||||
UTEST(mman, rejects_invalid_arguments) {
|
||||
EXPECT_EQ(sys_mmap(nullptr, PAGE_SIZE, 0x80, MAP_ANON_PRIVATE, -1, 0).error, __EINVAL);
|
||||
EXPECT_EQ(sys_mmap(nullptr, PAGE_SIZE, PROT_RW, __MAP_ANON, -1, 0).error, __EINVAL);
|
||||
EXPECT_EQ(sys_mmap(nullptr, PAGE_SIZE, PROT_RW, MAP_ANON_PRIVATE, -1, 1).error, __EINVAL);
|
||||
EXPECT_EQ(sys_mmap((void*)0x1001, PAGE_SIZE, PROT_RW, MAP_ANON_PRIVATE | __MAP_FIXED, -1, 0).error, __EINVAL);
|
||||
EXPECT_EQ(sys_mmap(nullptr, PAGE_SIZE, __PROT_READ, __MAP_PRIVATE, 1234, 0).error, __EBADF);
|
||||
}
|
||||
|
||||
UTEST(mman, mprotect) {
|
||||
uint8_t* mem = map_anon(2 * PAGE_SIZE);
|
||||
ASSERT_TRUE(mem != nullptr);
|
||||
mem[0] = 7;
|
||||
EXPECT_EQ(sys_mprotect(mem, PAGE_SIZE, __PROT_READ).error, 0);
|
||||
EXPECT_EQ(mem[0], 7);
|
||||
EXPECT_EQ(sys_mprotect(mem, PAGE_SIZE, PROT_RW).error, 0);
|
||||
mem[0] = 8;
|
||||
EXPECT_EQ(mem[0], 8);
|
||||
EXPECT_EQ(sys_mprotect(mem, PAGE_SIZE, 0x80).error, __EINVAL);
|
||||
EXPECT_EQ(sys_mprotect(mem, 0, __PROT_READ).error, __EINVAL);
|
||||
EXPECT_EQ(sys_munmap(mem, 2 * PAGE_SIZE).error, 0);
|
||||
}
|
||||
|
||||
UTEST(mman, msync) {
|
||||
uint8_t* mem = map_anon(PAGE_SIZE);
|
||||
ASSERT_TRUE(mem != nullptr);
|
||||
EXPECT_EQ(sys_msync(mem, PAGE_SIZE, __MS_SYNC).error, 0);
|
||||
EXPECT_EQ(sys_msync(mem, PAGE_SIZE, __MS_ASYNC).error, 0);
|
||||
EXPECT_EQ(sys_msync(mem, PAGE_SIZE, __MS_ASYNC | __MS_SYNC).error, __EINVAL);
|
||||
EXPECT_EQ(sys_msync(mem + 1, PAGE_SIZE, __MS_SYNC).error, __EINVAL);
|
||||
EXPECT_EQ(sys_munmap(mem, PAGE_SIZE).error, 0);
|
||||
EXPECT_EQ(sys_msync(mem, PAGE_SIZE, __MS_ASYNC).error, __ENOMEM);
|
||||
}
|
||||
|
||||
UTEST(mman, madvise) {
|
||||
uint8_t* mem = map_anon(PAGE_SIZE);
|
||||
ASSERT_TRUE(mem != nullptr);
|
||||
EXPECT_EQ(sys_madvise(mem, PAGE_SIZE, 0).error, 0);
|
||||
EXPECT_EQ(sys_madvise(mem + 1, PAGE_SIZE, 0).error, __EINVAL);
|
||||
EXPECT_EQ(sys_munmap(mem, PAGE_SIZE).error, 0);
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
// SPDX-License-Identifier: ISC
|
||||
|
||||
#include <string.h>
|
||||
#include <uapi/errno.h>
|
||||
#include <uapi/utsname.h>
|
||||
#include "lib/syscall.h"
|
||||
#include "lib/utest.h"
|
||||
|
||||
UTEST(syslog, writes_message) {
|
||||
const char msg[] = "utest: syslog test";
|
||||
struct sys_ret ret = sys_syslog(SYSLOG_INFO, msg, sizeof(msg) - 1);
|
||||
EXPECT_EQ(ret.error, 0);
|
||||
}
|
||||
|
||||
UTEST(syslog, rejects_bad_pointer) {
|
||||
struct sys_ret ret = sys_syslog(SYSLOG_INFO, (const char*)0x10, 8);
|
||||
EXPECT_EQ(ret.error, __EFAULT);
|
||||
}
|
||||
|
||||
UTEST(system, uname) {
|
||||
struct __utsname uts = {};
|
||||
struct sys_ret ret = sys_getuname(&uts);
|
||||
ASSERT_EQ(ret.error, 0);
|
||||
EXPECT_STREQ(uts.sysname, "Zinnia");
|
||||
EXPECT_STREQ(uts.machine, "x86_64");
|
||||
}
|
||||
|
||||
UTEST(syslog, long_message) {
|
||||
char msg[1024];
|
||||
memset(msg, 'x', sizeof(msg));
|
||||
EXPECT_EQ(sys_syslog(SYSLOG_INFO, msg, sizeof(msg)).error, 0);
|
||||
}
|
||||
|
||||
UTEST(system, uname_rejects_null) {
|
||||
EXPECT_EQ(sys_getuname(nullptr).error, __EINVAL);
|
||||
EXPECT_EQ(sys_setuname(nullptr).error, __EINVAL);
|
||||
}
|
||||
|
||||
UTEST(system, uname_rejects_bad_pointer) {
|
||||
EXPECT_EQ(sys_getuname((void*)0x10).error, __EFAULT);
|
||||
EXPECT_EQ(sys_setuname((const void*)0x10).error, __EFAULT);
|
||||
}
|
||||
|
||||
UTEST(system, setuname) {
|
||||
struct __utsname original = {};
|
||||
ASSERT_EQ(sys_getuname(&original).error, 0);
|
||||
|
||||
struct __utsname changed = original;
|
||||
memset(changed.nodename, 0, sizeof(changed.nodename));
|
||||
memcpy(changed.nodename, "foobar", sizeof("foobar"));
|
||||
ASSERT_EQ(sys_setuname(&changed).error, 0);
|
||||
|
||||
struct __utsname now = {};
|
||||
EXPECT_EQ(sys_getuname(&now).error, 0);
|
||||
EXPECT_STREQ(now.nodename, "foobar");
|
||||
EXPECT_STREQ(now.sysname, original.sysname);
|
||||
|
||||
EXPECT_EQ(sys_setuname(&original).error, 0);
|
||||
EXPECT_EQ(sys_getuname(&now).error, 0);
|
||||
EXPECT_STREQ(now.nodename, original.nodename);
|
||||
}
|
||||
Reference in New Issue
Block a user