summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/Build.mk12
-rw-r--r--test/selftest.cc83
-rw-r--r--test/test_atomic.cc102
-rw-r--r--test/test_dcache.cc232
-rw-r--r--test/test_driver_model.cc214
-rw-r--r--test/test_exec.cc447
-rw-r--r--test/test_fdt.cc50
-rw-r--r--test/test_klog.cc175
-rw-r--r--test/test_mutex.cc183
-rw-r--r--test/test_namei.cc173
-rw-r--r--test/test_page_cache.cc180
-rw-r--r--test/test_pid.cc106
-rw-r--r--test/test_pipe_devfs.cc420
-rw-r--r--test/test_pmm.cc283
-rw-r--r--test/test_random.cc49
-rw-r--r--test/test_rbtree.cc119
-rw-r--r--test/test_sched.cc257
-rw-r--r--test/test_signal.cc193
-rw-r--r--test/test_slab.cc255
-rw-r--r--test/test_smp.cc136
-rw-r--r--test/test_spinlock.cc135
-rw-r--r--test/test_sys_fs_extra.cc238
-rw-r--r--test/test_sys_mm.cc178
-rw-r--r--test/test_sys_process.cc93
-rw-r--r--test/test_trap.cc197
-rw-r--r--test/test_uaccess.cc97
-rw-r--r--test/test_vfs_io.cc345
-rw-r--r--test/test_vma.cc282
-rw-r--r--test/test_vmm.cc58
-rw-r--r--test/test_waitqueue.cc91
30 files changed, 0 insertions, 5383 deletions
diff --git a/test/Build.mk b/test/Build.mk
deleted file mode 100644
index cb8f200..0000000
--- a/test/Build.mk
+++ /dev/null
@@ -1,12 +0,0 @@
-targets += test/kernel
-target.test/kernel.kind := kernel-test
-target.test/kernel.sources := \
- test/selftest.cc test/test_pmm.cc test/test_slab.cc test/test_spinlock.cc \
- test/test_mutex.cc test/test_driver_model.cc test/test_rbtree.cc \
- test/test_vmm.cc test/test_vma.cc test/test_atomic.cc test/test_fdt.cc \
- test/test_trap.cc test/test_sched.cc test/test_klog.cc test/test_random.cc \
- test/test_waitqueue.cc test/test_uaccess.cc test/test_sys_mm.cc \
- test/test_smp.cc test/test_dcache.cc test/test_namei.cc \
- test/test_vfs_io.cc test/test_exec.cc test/test_pipe_devfs.cc \
- test/test_sys_process.cc test/test_signal.cc test/test_pid.cc \
- test/test_sys_fs_extra.cc test/test_page_cache.cc
diff --git a/test/selftest.cc b/test/selftest.cc
deleted file mode 100644
index e1bb2b4..0000000
--- a/test/selftest.cc
+++ /dev/null
@@ -1,83 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-/*
- * Boot Self-Test Runner
- *
- * Entry point that invokes all registered self-test suites.
- * Called from kernel_main() after all subsystem initialization.
- */
-
-#include <lume/selftest.h>
-
-/* Individual test suite entry points */
-void selftest_pmm();
-void selftest_slab();
-void selftest_spinlock();
-void selftest_mutex();
-void selftest_driver_model();
-void selftest_rbtree();
-void selftest_vmm();
-void selftest_fdt();
-void selftest_vma();
-void selftest_atomic();
-void selftest_trap();
-void selftest_sched();
-void selftest_klog();
-void selftest_random();
-void selftest_waitqueue();
-void selftest_uaccess();
-void selftest_sys_mm();
-void selftest_dcache();
-void selftest_namei();
-void selftest_vfs_io();
-void selftest_exec();
-void selftest_devfs();
-void selftest_pipe();
-void selftest_fcntl();
-void selftest_sys_process();
-void selftest_signal();
-void selftest_pid();
-void selftest_sys_fs_extra();
-void selftest_page_cache();
-
-void selftest_run_all()
-{
- kprintf("\n========================================\n");
- kprintf(" Boot Self-Test Suite\n");
- kprintf("========================================\n");
-
- selftest_spinlock();
- selftest_mutex();
- selftest_driver_model();
- selftest_pmm();
- selftest_slab();
- selftest_rbtree();
- selftest_vmm();
- selftest_vma();
- selftest_atomic();
- selftest_fdt();
- selftest_trap();
- selftest_sched();
- selftest_klog();
- selftest_random();
- selftest_waitqueue();
- selftest_uaccess();
- selftest_sys_mm();
- selftest_dcache();
- selftest_namei();
- selftest_vfs_io();
- selftest_devfs();
- selftest_pipe();
- selftest_fcntl();
- selftest_sys_process();
- selftest_signal();
- selftest_pid();
- selftest_sys_fs_extra();
- selftest_page_cache();
- selftest_exec();
-
- kprintf("========================================\n");
- kprintf(" All self-tests PASSED\n");
- kprintf("========================================\n\n");
-}
diff --git a/test/test_atomic.cc b/test/test_atomic.cc
deleted file mode 100644
index 9851e7b..0000000
--- a/test/test_atomic.cc
+++ /dev/null
@@ -1,102 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-/*
- * Boot Self-Test: lume::atomic<T>
- *
- * Tests:
- * 1. Default construction (zero-initialized)
- * 2. Explicit construction
- * 3. store / load round-trip with different memory orders
- * 4. exchange returns old value
- * 5. fetch_add / fetch_sub arithmetic
- * 6. compare_exchange success and failure paths
- */
-
-#include <lume/selftest.h>
-#include <lume/atomic.h>
-
-void selftest_atomic() {
- st_begin("atomic: default construction is zero");
- {
- lume::atomic<uint32> a;
- ST_ASSERT_EQ(a.load(), 0U);
- }
- st_pass();
-
- st_begin("atomic: explicit construction");
- {
- lume::atomic<uint64> a(42);
- ST_ASSERT_EQ(a.load(), 42ULL);
- }
- st_pass();
-
- st_begin("atomic: store/load round-trip");
- {
- lume::atomic<uint32> a;
- a.store(0xDEAD, __ATOMIC_RELEASE);
- ST_ASSERT_EQ(a.load(__ATOMIC_ACQUIRE), 0xDEADU);
-
- a.store(0, __ATOMIC_SEQ_CST);
- ST_ASSERT_EQ(a.load(__ATOMIC_SEQ_CST), 0U);
- }
- st_pass();
-
- st_begin("atomic: exchange returns old value");
- {
- lume::atomic<uint32> a(10);
- uint32 old = a.exchange(20);
- ST_ASSERT_EQ(old, 10U);
- ST_ASSERT_EQ(a.load(), 20U);
- }
- st_pass();
-
- st_begin("atomic: fetch_add / fetch_sub");
- {
- lume::atomic<uint32> a(100);
-
- uint32 old = a.fetch_add(5);
- ST_ASSERT_EQ(old, 100U);
- ST_ASSERT_EQ(a.load(), 105U);
-
- old = a.fetch_sub(10);
- ST_ASSERT_EQ(old, 105U);
- ST_ASSERT_EQ(a.load(), 95U);
-
- // Underflow wraps (unsigned)
- lume::atomic<uint32> b(0);
- old = b.fetch_sub(1);
- ST_ASSERT_EQ(old, 0U);
- ST_ASSERT_EQ(b.load(), 0xFFFFFFFFU);
- }
- st_pass();
-
- st_begin("atomic: compare_exchange success and failure");
- {
- lume::atomic<uint32> a(42);
-
- // Success path: expected matches, swap happens
- uint32 expected = 42;
- bool ok = a.compare_exchange(expected, 99);
- ST_ASSERT(ok);
- ST_ASSERT_EQ(a.load(), 99U);
- ST_ASSERT_EQ(expected, 42U); // expected unchanged on success
-
- // Failure path: expected doesn't match, expected gets updated
- expected = 0; // wrong
- ok = a.compare_exchange(expected, 200);
- ST_ASSERT(!ok);
- ST_ASSERT_EQ(a.load(), 99U); // value unchanged
- ST_ASSERT_EQ(expected, 99U); // expected updated to actual value
- }
- st_pass();
-
- st_begin("atomic: 64-bit fetch_add correctness");
- {
- lume::atomic<uint64> a(0xFFFFFFFF00000000ULL);
- uint64 old = a.fetch_add(1);
- ST_ASSERT_EQ(old, 0xFFFFFFFF00000000ULL);
- ST_ASSERT_EQ(a.load(), 0xFFFFFFFF00000001ULL);
- }
- st_pass();
-}
diff --git a/test/test_dcache.cc b/test/test_dcache.cc
deleted file mode 100644
index 9e0eec5..0000000
--- a/test/test_dcache.cc
+++ /dev/null
@@ -1,232 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-/*
- * test_dcache.cc — Self-tests for the dentry cache layer
- */
-
-#include <lume/selftest.h>
-#include <lume/dcache.h>
-#include <lume/fs.h>
-#include <lume/mount.h>
-#include <lume/errno.h>
-#include <lume/slab.h>
-
-/* Root dentry/mount from VFS init */
-extern dentry* g_root_dentry;
-extern vfsmount* g_root_mnt;
-
-void selftest_dcache()
-{
- /* ---- d_alloc: basic allocation under root ---- */
- st_begin("dcache: d_alloc basic");
- {
- qstr name{};
- name.name = "test_child";
- name.len = 10;
- name.hash = full_name_hash(name.name, name.len);
-
- dentry* child = d_alloc(g_root_dentry, &name);
- ST_ASSERT(child != nullptr);
- ST_ASSERT_EQ(child->d_name.len, 10u);
- ST_ASSERT(child->d_parent == g_root_dentry);
- ST_ASSERT(child->d_inode == nullptr); /* not instantiated yet */
- ST_ASSERT(child->d_sb == g_root_dentry->d_sb);
-
- /* Name was copied into inline buffer */
- bool match = true;
- for (uint32 i = 0; i < name.len; i++) {
- if (child->d_name.name[i] != name.name[i]) { match = false; break; }
- }
- ST_ASSERT(match);
-
- d_drop(child);
- dput(child);
- }
- st_pass();
-
- /* ---- d_lookup: find existing child ---- */
- st_begin("dcache: d_lookup finds child");
- {
- qstr name{};
- name.name = "lookup_me";
- name.len = 9;
- name.hash = full_name_hash(name.name, name.len);
-
- dentry* child = d_alloc(g_root_dentry, &name);
- ST_ASSERT(child != nullptr);
-
- /* d_lookup should find it */
- dentry* found = d_lookup(g_root_dentry, &name);
- ST_ASSERT(found != nullptr);
- ST_ASSERT(found == child);
- dput(found); /* drop d_lookup ref */
-
- d_drop(child);
- dput(child);
- }
- st_pass();
-
- /* ---- d_lookup: miss returns nullptr ---- */
- st_begin("dcache: d_lookup miss returns nullptr");
- {
- qstr name{};
- name.name = "nonexistent_xyz";
- name.len = 15;
- name.hash = full_name_hash(name.name, name.len);
-
- dentry* found = d_lookup(g_root_dentry, &name);
- ST_ASSERT(found == nullptr);
- }
- st_pass();
-
- /* ---- d_instantiate: bind inode to dentry ---- */
- st_begin("dcache: d_instantiate binds inode");
- {
- qstr name{};
- name.name = "has_inode";
- name.len = 9;
- name.hash = full_name_hash(name.name, name.len);
-
- dentry* child = d_alloc(g_root_dentry, &name);
- ST_ASSERT(child != nullptr);
- ST_ASSERT(child->d_inode == nullptr);
-
- /* Create a dummy inode */
- inode* ip = new_inode(g_root_dentry->d_sb);
- ST_ASSERT(ip != nullptr);
- ip->i_mode = S_IFREG | 0644;
-
- d_instantiate(child, ip);
- ST_ASSERT(child->d_inode == ip);
- ST_ASSERT(child->d_flags & DCACHE_REGULAR_TYPE);
-
- d_drop(child);
- dput(child);
- }
- st_pass();
-
- /* ---- dget/dput: refcount ---- */
- st_begin("dcache: dget/dput refcount");
- {
- qstr name{};
- name.name = "reftest";
- name.len = 7;
- name.hash = full_name_hash(name.name, name.len);
-
- dentry* child = d_alloc(g_root_dentry, &name);
- ST_ASSERT(child != nullptr);
- /* d_alloc sets refcount = 1 */
- int rc1 = child->d_lockref.load();
- ST_ASSERT(rc1 >= 1);
-
- dget(child);
- int rc2 = child->d_lockref.load();
- ST_ASSERT_EQ(rc2, rc1 + 1);
-
- dput(child);
- int rc3 = child->d_lockref.load();
- ST_ASSERT_EQ(rc3, rc1);
-
- d_drop(child);
- dput(child);
- }
- st_pass();
-
- /* ---- d_drop: removes from parent children list ---- */
- st_begin("dcache: d_drop removes from parent");
- {
- qstr name{};
- name.name = "dropme";
- name.len = 6;
- name.hash = full_name_hash(name.name, name.len);
-
- dentry* child = d_alloc(g_root_dentry, &name);
- ST_ASSERT(child != nullptr);
-
- /* Should be findable */
- dentry* f1 = d_lookup(g_root_dentry, &name);
- ST_ASSERT(f1 != nullptr);
- dput(f1);
-
- /* Drop it */
- d_drop(child);
-
- /* Should no longer be findable */
- dentry* f2 = d_lookup(g_root_dentry, &name);
- ST_ASSERT(f2 == nullptr);
-
- dput(child);
- }
- st_pass();
-
- /* ---- d_path: root returns "/" ---- */
- st_begin("dcache: d_path root returns /");
- {
- path p{};
- p.de = g_root_dentry;
- p.mnt = g_root_mnt;
-
- char buf[64];
- char* result = d_path(&p, buf, sizeof(buf));
- ST_ASSERT(result != nullptr);
- ST_ASSERT(result[0] == '/');
- ST_ASSERT(result[1] == '\0');
- }
- st_pass();
-
- /* ---- d_path: child path ---- */
- st_begin("dcache: d_path child returns /name");
- {
- qstr name{};
- name.name = "pathtest";
- name.len = 8;
- name.hash = full_name_hash(name.name, name.len);
-
- dentry* child = d_alloc(g_root_dentry, &name);
- ST_ASSERT(child != nullptr);
-
- path p{};
- p.de = child;
- p.mnt = g_root_mnt;
-
- char buf[64];
- char* result = d_path(&p, buf, sizeof(buf));
- ST_ASSERT(result != nullptr);
-
- /* Should be "/pathtest" */
- const char* expected = "/pathtest";
- bool eq = true;
- for (int i = 0; expected[i]; i++) {
- if (result[i] != expected[i]) { eq = false; break; }
- }
- ST_ASSERT(eq);
-
- d_drop(child);
- dput(child);
- }
- st_pass();
-
- /* ---- d_alloc_anon: root-like dentry ---- */
- st_begin("dcache: d_alloc_anon");
- {
- dentry* anon = d_alloc_anon(g_root_dentry->d_sb);
- ST_ASSERT(anon != nullptr);
- ST_ASSERT(anon->d_parent == anon); /* root: parent is self */
- ST_ASSERT(anon->d_sb == g_root_dentry->d_sb);
- dput(anon);
- }
- st_pass();
-
- /* ---- full_name_hash: deterministic ---- */
- st_begin("dcache: full_name_hash deterministic");
- {
- uint32 h1 = full_name_hash("hello", 5);
- uint32 h2 = full_name_hash("hello", 5);
- ST_ASSERT_EQ(h1, h2);
-
- uint32 h3 = full_name_hash("world", 5);
- ST_ASSERT_NE(h1, h3);
- }
- st_pass();
-}
diff --git a/test/test_driver_model.cc b/test/test_driver_model.cc
deleted file mode 100644
index 074a84e..0000000
--- a/test/test_driver_model.cc
+++ /dev/null
@@ -1,214 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-/*
- * test_driver_model — exercises kref, kobject, and the bus/device/driver
- * core. Uses a dummy bus + dummy driver + dummy device so the test is
- * self-contained and doesn't depend on FDT/platform_device.
- */
-
-#include <lume/selftest.h>
-#include <lume/kref.h>
-#include <lume/kobject.h>
-#include <lume/device.h>
-#include <lume/irq.h>
-
-/* ------------------------------------------------------------------ */
-/* Test 1: kref get/put + release fires exactly once */
-/* ------------------------------------------------------------------ */
-
-namespace {
-
-struct krefable {
- kref ref;
- int release_count;
-};
-
-void krefable_release(kref* r)
-{
- auto* k = container_of(r, krefable, ref);
- k->release_count++;
-}
-
-void test_kref()
-{
- st_begin("kref: init/get/put");
-
- krefable k = {};
- kref_init(&k.ref);
-
- ST_ASSERT_EQ(kref_read(&k.ref), 1);
-
- kref_get(&k.ref);
- kref_get(&k.ref);
- ST_ASSERT_EQ(kref_read(&k.ref), 3);
-
- ST_ASSERT_EQ(kref_put(&k.ref, krefable_release), 0);
- ST_ASSERT_EQ(kref_put(&k.ref, krefable_release), 0);
- ST_ASSERT_EQ(k.release_count, 0);
-
- ST_ASSERT_EQ(kref_put(&k.ref, krefable_release), 1);
- ST_ASSERT_EQ(k.release_count, 1);
-
- st_pass();
-}
-
-/* ------------------------------------------------------------------ */
-/* Test 2: kobject parent/child + release on final put */
-/* ------------------------------------------------------------------ */
-
-int g_child_released = 0;
-int g_parent_released = 0;
-
-void child_release(kobject*) { g_child_released++; }
-void parent_release(kobject*) { g_parent_released++; }
-
-const kobj_type child_ktype = { .release = child_release };
-const kobj_type parent_ktype = { .release = parent_release };
-
-void test_kobject_hierarchy()
-{
- st_begin("kobject: parent/child + ref counting");
-
- g_child_released = 0;
- g_parent_released = 0;
-
- kobject parent;
- kobject_init(&parent, &parent_ktype);
-
- kobject child;
- kobject_init(&child, &child_ktype);
- kobject_add(&child, &parent, "child");
-
- /* Adding child took a ref on parent. */
- ST_ASSERT_EQ(kref_read(&parent.refcount), 2);
- ST_ASSERT(child.parent == &parent);
-
- /* Drop child's "added" reference — release should NOT fire because
- * we (the test) still hold the original kobject_init ref. */
- kobject_del(&child);
- ST_ASSERT(child.parent == nullptr);
- ST_ASSERT_EQ(kref_read(&parent.refcount), 1);
- ST_ASSERT_EQ(g_child_released, 0);
- ST_ASSERT_EQ(g_parent_released, 0);
-
- /* Drop final refs. */
- kobject_put(&child);
- ST_ASSERT_EQ(g_child_released, 1);
-
- kobject_put(&parent);
- ST_ASSERT_EQ(g_parent_released, 1);
-
- st_pass();
-}
-
-/* ------------------------------------------------------------------ */
-/* Test 3: bus / device / driver bind via match + probe */
-/* ------------------------------------------------------------------ */
-
-struct dummy_dev { device dev; int probed; };
-struct dummy_drv { device_driver drv; const char* want; };
-
-int g_probes = 0;
-
-int dummy_match(device* dev, device_driver* drv)
-{
- auto* dd = container_of(drv, dummy_drv, drv);
- return (dev->init_name != nullptr &&
- dd->want != nullptr &&
- __builtin_strcmp(dev->init_name, dd->want) == 0) ? 1 : 0;
-}
-
-int dummy_probe(device* dev)
-{
- auto* dd = container_of(dev, dummy_dev, dev);
- dd->probed++;
- g_probes++;
- return 0;
-}
-
-bus_type dummy_bus = {
- .name = "test_dummy",
- .match = dummy_match,
- .probe = nullptr, /* fall through to drv->probe */
- .devices = {},
- .drivers = {},
- .lock = {},
- .registered = false,
-};
-
-void test_bus_bind()
-{
- st_begin("driver model: bus / driver / device bind");
-
- g_probes = 0;
- ST_ASSERT_EQ(bus_register(&dummy_bus), 0);
-
- /* Register driver before any matching device exists. */
- dummy_drv drv_a = {};
- drv_a.drv.name = "drv_a";
- drv_a.drv.bus = &dummy_bus;
- drv_a.drv.probe = dummy_probe;
- drv_a.want = "compat_a";
- ST_ASSERT_EQ(driver_register(&drv_a.drv), 0);
-
- /* Register a device that should bind to drv_a immediately. */
- dummy_dev dev_a = {};
- dev_a.dev.bus = &dummy_bus;
- dev_a.dev.init_name = "compat_a";
- ST_ASSERT_EQ(device_register(&dev_a.dev), 0);
- ST_ASSERT_EQ(dev_a.probed, 1);
- ST_ASSERT(dev_a.dev.driver == &drv_a.drv);
-
- /* Register a device first, then a driver — should also bind. */
- dummy_dev dev_b = {};
- dev_b.dev.bus = &dummy_bus;
- dev_b.dev.init_name = "compat_b";
- ST_ASSERT_EQ(device_register(&dev_b.dev), 0);
- ST_ASSERT_EQ(dev_b.probed, 0);
-
- dummy_drv drv_b = {};
- drv_b.drv.name = "drv_b";
- drv_b.drv.bus = &dummy_bus;
- drv_b.drv.probe = dummy_probe;
- drv_b.want = "compat_b";
- ST_ASSERT_EQ(driver_register(&drv_b.drv), 0);
- ST_ASSERT_EQ(dev_b.probed, 1);
- ST_ASSERT(dev_b.dev.driver == &drv_b.drv);
-
- /* Non-matching device sits unbound. */
- dummy_dev dev_c = {};
- dev_c.dev.bus = &dummy_bus;
- dev_c.dev.init_name = "compat_unknown";
- ST_ASSERT_EQ(device_register(&dev_c.dev), 0);
- ST_ASSERT_EQ(dev_c.probed, 0);
- ST_ASSERT(dev_c.dev.driver == nullptr);
-
- ST_ASSERT_EQ(g_probes, 2);
-
- /* Cleanup. */
- device_unregister(&dev_a.dev);
- device_unregister(&dev_b.dev);
- device_unregister(&dev_c.dev);
- driver_unregister(&drv_a.drv);
- driver_unregister(&drv_b.drv);
-
- st_pass();
-}
-
-} // namespace
-
-/* ------------------------------------------------------------------ */
-/* Entry */
-/* ------------------------------------------------------------------ */
-
-void selftest_driver_model()
-{
- test_kref();
- test_kobject_hierarchy();
- test_bus_bind();
-
- st_begin("driver model: platform IRQ chip probed");
- ST_ASSERT(g_irq_manager.get_chip() != nullptr);
- st_pass();
-}
diff --git a/test/test_exec.cc b/test/test_exec.cc
deleted file mode 100644
index bbfa3b6..0000000
--- a/test/test_exec.cc
+++ /dev/null
@@ -1,447 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-/*
- * test_exec.cc — Self-tests for execve / ELF loader
- *
- * Tests the ELF validation, binary loading, and user stack construction
- * by creating synthetic ELF binaries in RamFS and invoking do_execve
- * in a controlled environment.
- *
- * We cannot test the actual user-space transition (that requires
- * sret to U-mode), but we CAN test everything up to and including
- * the trapframe setup.
- */
-
-#include <lume/selftest.h>
-#include <lume/elf.h>
-#include <lume/task.h>
-#include <lume/vmm.h>
-#include <lume/pmm.h>
-#include <lume/addr.h>
-#include <lume/errno.h>
-#include <lume/fdtable.h>
-#include <lume/fs.h>
-#include <lume/fs_struct.h>
-#include <lume/dcache.h>
-#include <lume/namei.h>
-#include <lume/file.h>
-#include <lume/mm_layout.h>
-#include <arch/trapframe.h>
-#include <arch/pmap.h>
-
-/* Extern: do_execve (kernel/exec.cc) */
-int do_execve(const char* pathname, const char* const* argv,
- const char* const* envp);
-
-/* Extern: VFS file creation syscalls */
-int64 sys_openat(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_write(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_close(uint64, uint64, uint64, uint64, uint64, uint64);
-
-/* ================================================================== */
-/* Helper: build a minimal valid RISC-V ELF64 in a buffer */
-/* ================================================================== */
-
-struct TestElf {
- Elf64_Ehdr ehdr;
- Elf64_Phdr phdr;
- uint8 code[16]; /* Tiny "program" — just a few NOPs */
-};
-
-/*
- * Build a minimal static ELF64 for RISC-V.
- * Single PT_LOAD segment mapping the tiny code at 0x10000.
- * Entry point = 0x10000 + offsetof(code).
- */
-static void build_minimal_elf(TestElf* elf)
-{
- /* Zero everything */
- for (uint64 i = 0; i < sizeof(*elf); i++)
- reinterpret_cast<uint8*>(elf)[i] = 0;
-
- /* ELF header */
- Elf64_Ehdr* e = &elf->ehdr;
- e->e_ident[EI_MAG0] = ELFMAG0;
- e->e_ident[EI_MAG1] = ELFMAG1;
- e->e_ident[EI_MAG2] = ELFMAG2;
- e->e_ident[EI_MAG3] = ELFMAG3;
- e->e_ident[EI_CLASS] = ELFCLASS64;
- e->e_ident[EI_DATA] = ELFDATA2LSB;
- e->e_ident[EI_VERSION] = 1;
- e->e_type = ET_EXEC;
- e->e_machine = EM_RISCV;
- e->e_version = 1;
- e->e_phoff = sizeof(Elf64_Ehdr);
- e->e_phentsize = sizeof(Elf64_Phdr);
- e->e_phnum = 1;
- e->e_ehsize = sizeof(Elf64_Ehdr);
-
- /* Code section offset within the file */
- uint64 code_offset = sizeof(Elf64_Ehdr) + sizeof(Elf64_Phdr);
-
- /* Entry point = vaddr of code */
- uint64 load_vaddr = 0x10000;
- e->e_entry = load_vaddr + code_offset;
-
- /* Program header — single PT_LOAD covering entire file */
- Elf64_Phdr* p = &elf->phdr;
- p->p_type = PT_LOAD;
- p->p_flags = PF_R | PF_X;
- p->p_offset = 0;
- p->p_vaddr = load_vaddr;
- p->p_paddr = load_vaddr;
- p->p_filesz = sizeof(TestElf);
- p->p_memsz = sizeof(TestElf);
- p->p_align = 0x1000;
-
- /* Code: RISC-V "unimp" (all zeros = illegal instruction, but good enough for testing) */
- /* We just need the loader to succeed; we won't actually execute this. */
- elf->code[0] = 0x13; /* nop = addi x0, x0, 0 */
- elf->code[4] = 0x13;
- elf->code[8] = 0x73; /* ebreak */
-}
-
-/* ================================================================== */
-/* Helper: write a buffer to a file in RamFS */
-/* ================================================================== */
-
-static int create_test_file(const char* path, const void* data, uint64 size)
-{
- /* Open with O_CREAT | O_WRONLY | O_TRUNC */
- int64 fd = sys_openat(
- static_cast<uint64>(-100), /* AT_FDCWD */
- reinterpret_cast<uint64>(path),
- O_CREAT | O_WRONLY | O_TRUNC,
- 0755, 0, 0);
- if (fd < 0) return static_cast<int>(fd);
-
- int64 written = sys_write(
- static_cast<uint64>(fd),
- reinterpret_cast<uint64>(data),
- size, 0, 0, 0);
-
- sys_close(static_cast<uint64>(fd), 0, 0, 0, 0, 0);
-
- if (written < 0) return static_cast<int>(written);
- if (static_cast<uint64>(written) != size) return -EIO;
- return 0;
-}
-
-/* ================================================================== */
-/* Helper: ensure current task has VFS context for file ops */
-/* ================================================================== */
-
-struct ExecTestCtx {
- TaskControlBlock* curr;
- VmSpace* old_mm;
- arch::TrapFrame fake_tf;
- arch::TrapFrame* old_tf;
- uint64 saved_satp;
-};
-
-static void exec_test_setup(ExecTestCtx* ctx)
-{
- ctx->curr = current_task();
-
- /* Save kernel SATP so we can restore it in teardown */
- asm volatile("csrr %0, satp" : "=r"(ctx->saved_satp));
-
- /* Save and replace mm with a fresh one */
- ctx->old_mm = ctx->curr->mm;
- ctx->curr->mm = VmSpace::create();
-
- /* Set up a fake trapframe so do_execve can modify it */
- for (uint64 i = 0; i < sizeof(ctx->fake_tf); i++)
- reinterpret_cast<uint8*>(&ctx->fake_tf)[i] = 0;
- ctx->old_tf = ctx->curr->trapframe;
- ctx->curr->trapframe = &ctx->fake_tf;
-}
-
-static void exec_test_teardown(ExecTestCtx* ctx)
-{
- /* Restore trapframe */
- ctx->curr->trapframe = ctx->old_tf;
-
- /* Destroy the test mm (may have been replaced by do_execve) */
- VmSpace* test_mm = ctx->curr->mm;
- ctx->curr->mm = ctx->old_mm;
- if (test_mm && test_mm != ctx->old_mm) {
- /* If do_execve succeeded, it activated new_mm's page table.
- * Switch back to the kernel page table before destroying. */
- asm volatile("csrw satp, %0" :: "r"(ctx->saved_satp));
- asm volatile("sfence.vma");
- test_mm->put();
- }
-}
-
-/* ================================================================== */
-/* Test cases */
-/* ================================================================== */
-
-void selftest_exec()
-{
- /* ---- 1a. Non-existent path under missing directory ---- */
- st_begin("exec: missing path returns error");
- {
- const char* argv[] = { "/no_such_dir/no_file", nullptr };
- const char* envp[] = { nullptr };
- int ret = do_execve("/no_such_dir/no_file", argv, envp);
- ST_ASSERT(ret < 0); /* -ENOENT from filename_lookup */
- }
- st_pass();
-
- /* ---- 1b. Directory is not executable ---- */
- st_begin("exec: directory returns -EACCES");
- {
- /* "/" exists and is a directory */
- const char* argv[] = { "/dev", nullptr };
- const char* envp[] = { nullptr };
- int ret = do_execve("/dev", argv, envp);
- ST_ASSERT_EQ(ret, -EACCES);
- }
- st_pass();
-
- /* ---- 2. Bad ELF magic ---- */
- st_begin("exec: bad ELF magic returns -ENOEXEC");
- {
- uint8 garbage[128];
- for (int i = 0; i < 128; i++) garbage[i] = 0xAA;
- int err = create_test_file("/test_bad_magic", garbage, 128);
- ST_ASSERT_EQ(err, 0);
-
- ExecTestCtx ctx;
- exec_test_setup(&ctx);
-
- const char* argv[] = { "/test_bad_magic", nullptr };
- const char* envp[] = { nullptr };
- int ret = do_execve("/test_bad_magic", argv, envp);
- ST_ASSERT_EQ(ret, -ENOEXEC);
-
- exec_test_teardown(&ctx);
- }
- st_pass();
-
- /* ---- 3. Wrong ELF class (32-bit) ---- */
- st_begin("exec: 32-bit ELF returns -ENOEXEC");
- {
- TestElf elf;
- build_minimal_elf(&elf);
- elf.ehdr.e_ident[EI_CLASS] = 1; /* ELFCLASS32 */
- int err = create_test_file("/test_elf32", &elf, sizeof(elf));
- ST_ASSERT_EQ(err, 0);
-
- ExecTestCtx ctx;
- exec_test_setup(&ctx);
-
- const char* argv[] = { "/test_elf32", nullptr };
- const char* envp[] = { nullptr };
- int ret = do_execve("/test_elf32", argv, envp);
- ST_ASSERT_EQ(ret, -ENOEXEC);
-
- exec_test_teardown(&ctx);
- }
- st_pass();
-
- /* ---- 4. Wrong endianness (big-endian) ---- */
- st_begin("exec: big-endian ELF returns -ENOEXEC");
- {
- TestElf elf;
- build_minimal_elf(&elf);
- elf.ehdr.e_ident[EI_DATA] = 2; /* ELFDATA2MSB */
- int err = create_test_file("/test_be", &elf, sizeof(elf));
- ST_ASSERT_EQ(err, 0);
-
- ExecTestCtx ctx;
- exec_test_setup(&ctx);
-
- const char* argv[] = { "/test_be", nullptr };
- const char* envp[] = { nullptr };
- int ret = do_execve("/test_be", argv, envp);
- ST_ASSERT_EQ(ret, -ENOEXEC);
-
- exec_test_teardown(&ctx);
- }
- st_pass();
-
- /* ---- 5. Wrong machine (x86_64 instead of RISC-V) ---- */
- st_begin("exec: wrong machine type returns -ENOEXEC");
- {
- TestElf elf;
- build_minimal_elf(&elf);
- elf.ehdr.e_machine = 62; /* EM_X86_64 */
- int err = create_test_file("/test_x86", &elf, sizeof(elf));
- ST_ASSERT_EQ(err, 0);
-
- ExecTestCtx ctx;
- exec_test_setup(&ctx);
-
- const char* argv[] = { "/test_x86", nullptr };
- const char* envp[] = { nullptr };
- int ret = do_execve("/test_x86", argv, envp);
- ST_ASSERT_EQ(ret, -ENOEXEC);
-
- exec_test_teardown(&ctx);
- }
- st_pass();
-
- /* ---- 6. Truncated ELF (too small for header) ---- */
- st_begin("exec: truncated ELF returns -ENOEXEC");
- {
- /* Valid magic but file is too short for full header */
- uint8 tiny[8] = { 0x7f, 'E', 'L', 'F', 2, 1, 1, 0 };
- int err = create_test_file("/test_tiny", tiny, sizeof(tiny));
- ST_ASSERT_EQ(err, 0);
-
- ExecTestCtx ctx;
- exec_test_setup(&ctx);
-
- const char* argv[] = { "/test_tiny", nullptr };
- const char* envp[] = { nullptr };
- int ret = do_execve("/test_tiny", argv, envp);
- ST_ASSERT_EQ(ret, -ENOEXEC);
-
- exec_test_teardown(&ctx);
- }
- st_pass();
-
- /* ---- 7. Zero program headers ---- */
- st_begin("exec: zero phnum returns -ENOEXEC");
- {
- TestElf elf;
- build_minimal_elf(&elf);
- elf.ehdr.e_phnum = 0;
- int err = create_test_file("/test_nophdr", &elf, sizeof(elf));
- ST_ASSERT_EQ(err, 0);
-
- ExecTestCtx ctx;
- exec_test_setup(&ctx);
-
- const char* argv[] = { "/test_nophdr", nullptr };
- const char* envp[] = { nullptr };
- int ret = do_execve("/test_nophdr", argv, envp);
- ST_ASSERT_EQ(ret, -ENOEXEC);
-
- exec_test_teardown(&ctx);
- }
- st_pass();
-
- /* ---- 8. Valid ELF: successful load and trapframe setup ---- */
- st_begin("exec: valid ELF loads and sets up trapframe");
- {
- TestElf elf;
- build_minimal_elf(&elf);
- int err = create_test_file("/test_valid", &elf, sizeof(elf));
- ST_ASSERT_EQ(err, 0);
-
- ExecTestCtx ctx;
- exec_test_setup(&ctx);
-
- const char* argv[] = { "/test_valid", nullptr };
- const char* envp[] = { "HOME=/", nullptr };
- int ret = do_execve("/test_valid", argv, envp);
- ST_ASSERT_EQ(ret, 0);
-
- /* Verify trapframe was set up correctly */
- arch::TrapFrame* tf = &ctx.fake_tf;
-
- /* Entry point should match what we set in the ELF */
- uint64 code_offset = sizeof(Elf64_Ehdr) + sizeof(Elf64_Phdr);
- uint64 expected_entry = 0x10000 + code_offset;
- ST_ASSERT_EQ(tf->epc, expected_entry);
-
- /* Stack pointer should be in user space, below the stack top */
- ST_ASSERT(tf->sp > 0);
- ST_ASSERT(tf->sp < kUserStackTop);
- ST_ASSERT(tf->sp >= kUserStackBase);
-
- /* sstatus: SPP=0 (U-mode), SPIE=1 (interrupts enabled on sret) */
- uint64 spp_bit = (1ULL << 8);
- uint64 spie_bit = (1ULL << 5);
- ST_ASSERT_EQ(tf->sstatus & spp_bit, 0ULL); /* Not S-mode */
- ST_ASSERT_EQ(tf->sstatus & spie_bit, spie_bit); /* SPIE set */
-
- /* General registers should be zeroed */
- ST_ASSERT_EQ(tf->ra, 0ULL);
- ST_ASSERT_EQ(tf->gp, 0ULL);
- ST_ASSERT_EQ(tf->tp, 0ULL);
- for (int i = 0; i < 7; i++)
- ST_ASSERT_EQ(tf->t[i], 0ULL);
- for (int i = 0; i < 12; i++)
- ST_ASSERT_EQ(tf->s[i], 0ULL);
- for (int i = 0; i < 8; i++)
- ST_ASSERT_EQ(tf->a[i], 0ULL);
-
- exec_test_teardown(&ctx);
- }
- st_pass();
-
- /* ---- 9. Verify user stack layout (argc/argv) ---- */
- st_begin("exec: user stack has correct argc/argv layout");
- {
- TestElf elf;
- build_minimal_elf(&elf);
- int err = create_test_file("/test_stack", &elf, sizeof(elf));
- ST_ASSERT_EQ(err, 0);
-
- ExecTestCtx ctx;
- exec_test_setup(&ctx);
-
- const char* argv[] = { "/test_stack", "arg1", nullptr };
- const char* envp[] = { "A=B", nullptr };
- int ret = do_execve("/test_stack", argv, envp);
- ST_ASSERT_EQ(ret, 0);
-
- /* The stack was set up in the new mm.
- * Read argc from the top of the stack using pmap lookup. */
- VmSpace* mm = ctx.curr->mm;
- uint64 sp = ctx.fake_tf.sp;
-
- /* Look up the physical page for sp */
- uint64 pa = 0;
- bool mapped = pmap::lookup(mm->root_pa, sp & ~(kPageSize - 1), &pa);
- ST_ASSERT(mapped);
-
- /* Read argc (first uint64 on stack) */
- uint64 page_offset = sp & (kPageSize - 1);
- uint8* kva = reinterpret_cast<uint8*>(pa_to_va(PhysAddr{pa}).raw);
- uint64 argc = *reinterpret_cast<uint64*>(kva + page_offset);
- ST_ASSERT_EQ(argc, 2ULL); /* "/test_stack" and "arg1" */
-
- exec_test_teardown(&ctx);
- }
- st_pass();
-
- /* ---- 10. Multiple consecutive execve replaces mm correctly ---- */
- st_begin("exec: consecutive execve replaces address space");
- {
- TestElf elf;
- build_minimal_elf(&elf);
- int err = create_test_file("/test_reexec", &elf, sizeof(elf));
- ST_ASSERT_EQ(err, 0);
-
- ExecTestCtx ctx;
- exec_test_setup(&ctx);
-
- /* First exec */
- const char* argv[] = { "/test_reexec", nullptr };
- const char* envp[] = { nullptr };
- int ret = do_execve("/test_reexec", argv, envp);
- ST_ASSERT_EQ(ret, 0);
-
- VmSpace* first_mm = ctx.curr->mm;
- ST_ASSERT(first_mm != nullptr);
-
- /* Second exec — should replace the first mm */
- ret = do_execve("/test_reexec", argv, envp);
- ST_ASSERT_EQ(ret, 0);
-
- /* mm should be different (new address space) */
- VmSpace* second_mm = ctx.curr->mm;
- ST_ASSERT(second_mm != nullptr);
- ST_ASSERT_NE(first_mm, second_mm);
-
- exec_test_teardown(&ctx);
- }
- st_pass();
-}
diff --git a/test/test_fdt.cc b/test/test_fdt.cc
deleted file mode 100644
index cf09029..0000000
--- a/test/test_fdt.cc
+++ /dev/null
@@ -1,50 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-/*
- * Boot Self-Test: FDT (Flattened Device Tree)
- */
-
-#include <lume/selftest.h>
-#include <lume/fdt.h>
-
-// Manually declare what we need to avoid header conflicts with toolchain libc
-extern "C" int strcmp(const char *s1, const char *s2);
-
-void selftest_fdt() {
- st_begin("fdt: tree root access");
- {
- DeviceNode* root = fdt_get_node_by_path("/");
- ST_ASSERT(root != nullptr);
- ST_ASSERT_EQ(strcmp(root->get_name(), ""), 0);
- }
- st_pass();
-
- st_begin("fdt: find existing compatible node (UART)");
- {
- // QEMU virt RISC-V platform always has a ns16550a UART
- DeviceNode* uart = fdt_find_compatible("ns16550a");
- ST_ASSERT(uart != nullptr);
-
- // Use get_prop_string which is convenience helper
- const char* compat = uart->get_prop_string("compatible");
- ST_ASSERT(compat != nullptr);
-
- // Check raw property access
- const FdtProperty* reg = uart->get_prop("reg");
- ST_ASSERT(reg != nullptr);
- ST_ASSERT(reg->value != nullptr);
- ST_ASSERT(reg->len > 0);
- }
- st_pass();
-
- st_begin("fdt: non-existent node/compatible queries");
- {
- DeviceNode* missing1 = fdt_get_node_by_path("/definitely_does_not_exist_node_12345");
- ST_ASSERT(missing1 == nullptr);
-
- DeviceNode* missing2 = fdt_find_compatible("invalid_compatible_string_xyz");
- ST_ASSERT(missing2 == nullptr);
- }
- st_pass();
-}
diff --git a/test/test_klog.cc b/test/test_klog.cc
deleted file mode 100644
index 896e7ed..0000000
--- a/test/test_klog.cc
+++ /dev/null
@@ -1,175 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-/*
- * Boot Self-Test: klog
- *
- * Tests:
- * 1. read vs read_consume semantics
- * 2. subsystem mask filtering
- * 3. ratelimit bookkeeping
- * 4. flush advances console tail when console filtering is active
- * 5. seq counter increments on writes
- */
-
-#include <lume/selftest.h>
-#include <lume/klog.h>
-#include <string.h>
-
-static void drain_klog()
-{
- static char buf[512];
- while (true) {
- int n = klog_read_consume(buf, sizeof(buf));
- if (n <= 0) {
- break;
- }
- }
-}
-
-static bool test_read_peek_and_consume()
-{
- drain_klog();
-
- klog_ex(KLOG_INFO, KLOG_SUBSYS_TEST, "klog_test_a");
- klog_ex(KLOG_INFO, KLOG_SUBSYS_TEST, "klog_test_b");
-
- KlogStats s0;
- klog_stats(&s0);
-
- char buf[1024];
- int n = klog_read(buf, sizeof(buf) - 1);
- if (n <= 0) {
- return false;
- }
- buf[n] = '\0';
- if (!strstr(buf, "klog_test_a") || !strstr(buf, "klog_test_b")) {
- return false;
- }
-
- KlogStats s1;
- klog_stats(&s1);
- if (s1.tail != s0.tail) {
- return false;
- }
-
- int n2 = klog_read_consume(buf, sizeof(buf) - 1);
- if (n2 <= 0) {
- return false;
- }
- buf[n2] = '\0';
- if (!strstr(buf, "klog_test_a") || !strstr(buf, "klog_test_b")) {
- return false;
- }
-
- KlogStats s2;
- klog_stats(&s2);
- return s2.tail != s0.tail;
-}
-
-static bool test_subsys_mask()
-{
- drain_klog();
-
- const uint64 all_mask = (1ULL << KLOG_SUBSYS_MAX) - 1;
- klog_set_subsys_mask(1ULL << KLOG_SUBSYS_TEST);
-
- klog_ex(KLOG_INFO, KLOG_SUBSYS_TEST, "klog_subsys_ok");
- klog_ex(KLOG_INFO, KLOG_SUBSYS_GENERIC, "klog_subsys_drop");
-
- char buf[1024];
- int n = klog_read_consume(buf, sizeof(buf) - 1);
- klog_set_subsys_mask(all_mask);
-
- if (n <= 0) {
- return false;
- }
- buf[n] = '\0';
- return strstr(buf, "klog_subsys_ok") && !strstr(buf, "klog_subsys_drop");
-}
-
-static bool test_ratelimit()
-{
- KlogRateLimit rl = {};
- rl.interval = 100;
- rl.burst = 2;
-
- bool a = klog_ratelimit(&rl, 10);
- bool b = klog_ratelimit(&rl, 20);
- bool c = klog_ratelimit(&rl, 30);
-
- if (!a || !b || c) {
- return false;
- }
- if (rl.printed != 2 || rl.suppressed != 1) {
- return false;
- }
-
- bool d = klog_ratelimit(&rl, 200);
- if (!d) {
- return false;
- }
- return rl.printed == 1 && rl.suppressed == 0;
-}
-
-static bool test_flush_advances_console_tail()
-{
- drain_klog();
-
- klog_set_level(KLOG_ERR);
- klog_ex(KLOG_INFO, KLOG_SUBSYS_TEST, "klog_flush_msg");
-
- KlogStats s1;
- klog_stats(&s1);
- if (s1.console_tail == s1.head) {
- klog_set_level(static_cast<KlogLevel>(KLOG_DEFAULT_LEVEL));
- return false;
- }
-
- klog_flush();
-
- KlogStats s2;
- klog_stats(&s2);
-
- klog_set_level(static_cast<KlogLevel>(KLOG_DEFAULT_LEVEL));
- return s2.console_tail == s2.head;
-}
-
-static bool test_seq_increments()
-{
- drain_klog();
-
- KlogStats s0;
- klog_stats(&s0);
-
- klog_ex(KLOG_INFO, KLOG_SUBSYS_TEST, "klog_seq_1");
- klog_ex(KLOG_INFO, KLOG_SUBSYS_TEST, "klog_seq_2");
-
- KlogStats s1;
- klog_stats(&s1);
-
- return s1.seq >= s0.seq + 2;
-}
-
-void selftest_klog()
-{
- st_begin("klog: read vs read_consume");
- ST_ASSERT(test_read_peek_and_consume());
- st_pass();
-
- st_begin("klog: subsys mask filters");
- ST_ASSERT(test_subsys_mask());
- st_pass();
-
- st_begin("klog: ratelimit bookkeeping");
- ST_ASSERT(test_ratelimit());
- st_pass();
-
- st_begin("klog: flush advances console tail");
- ST_ASSERT(test_flush_advances_console_tail());
- st_pass();
-
- st_begin("klog: seq increments on write");
- ST_ASSERT(test_seq_increments());
- st_pass();
-}
diff --git a/test/test_mutex.cc b/test/test_mutex.cc
deleted file mode 100644
index e86c984..0000000
--- a/test/test_mutex.cc
+++ /dev/null
@@ -1,183 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-/*
- * test_mutex — self-tests for the Mutex sleeping lock.
- *
- * Coverage:
- * - basic lock/unlock single-thread
- * - try_lock when free and when held
- * - mutual exclusion across kthreads (no lost increments)
- * - FIFO wake order (A acquires, B/C queue; release lets B in then C)
- */
-
-#include <lume/selftest.h>
-#include <lume/mutex.h>
-#include <lume/task.h>
-#include <lume/sched.h>
-#include <lume/kprintf.h>
-
-extern TaskControlBlock* kthread_create(const char* name,
- void (*entry)(void*), void* arg,
- uint8 priority);
-extern void task_exit(int code);
-
-/* ------------------------------------------------------------------ */
-/* Test 1: basic lock/unlock */
-/* ------------------------------------------------------------------ */
-
-static void test_basic()
-{
- st_begin("mutex: basic lock/unlock");
-
- Mutex m;
- m.init("test_basic");
-
- ST_ASSERT(!m.is_locked());
- m.lock();
- ST_ASSERT(m.is_locked());
- ST_ASSERT(m.is_owned_by_current());
- m.unlock();
- ST_ASSERT(!m.is_locked());
-
- st_pass();
-}
-
-/* ------------------------------------------------------------------ */
-/* Test 2: try_lock */
-/* ------------------------------------------------------------------ */
-
-static void test_trylock()
-{
- st_begin("mutex: try_lock");
-
- Mutex m;
- m.init("test_trylock");
-
- ST_ASSERT(m.try_lock() == true);
- ST_ASSERT(m.is_locked());
-
- /* A second try_lock from the same task: since owner == current,
- * we still return false (try_lock is non-recursive). */
- ST_ASSERT(m.try_lock() == false);
-
- m.unlock();
- ST_ASSERT(!m.is_locked());
- ST_ASSERT(m.try_lock() == true);
- m.unlock();
-
- st_pass();
-}
-
-/* ------------------------------------------------------------------ */
-/* Test 3: mutual exclusion across kthreads */
-/* ------------------------------------------------------------------ */
-
-static Mutex g_mut;
-static volatile int32 g_counter = 0;
-static volatile int32 g_done = 0;
-static constexpr int32 kIters = 200;
-
-static void mutex_worker(void*)
-{
- for (int i = 0; i < kIters; i++) {
- g_mut.lock();
- int32 v = g_counter;
- /* Yield inside the critical section to expose missing mutex.
- * If the mutex were broken we'd see lost increments. */
- sched_yield();
- g_counter = v + 1;
- g_mut.unlock();
- }
- __sync_fetch_and_add(&g_done, 1);
- task_exit(0);
-}
-
-static void test_mutual_exclusion()
-{
- st_begin("mutex: mutual exclusion (2 workers)");
-
- g_mut.init("test_mutex_mx");
- g_counter = 0;
- g_done = 0;
-
- kthread_create("mut_w1", mutex_worker, nullptr, 0);
- kthread_create("mut_w2", mutex_worker, nullptr, 0);
-
- /* Spin-yield until both workers finish. */
- while (g_done < 2) {
- sched_yield();
- }
-
- ST_ASSERT_EQ(g_counter, 2 * kIters);
- ST_ASSERT(!g_mut.is_locked());
-
- st_pass();
-}
-
-/* ------------------------------------------------------------------ */
-/* Test 4: FIFO wake order */
-/* ------------------------------------------------------------------ */
-
-static Mutex g_fifo_mut;
-static volatile int32 g_fifo_seq = 0; // monotonically increases
-static volatile int32 g_fifo_order[3]; // order each worker observed
-static volatile int32 g_fifo_done = 0;
-
-static void fifo_worker(void* arg)
-{
- int slot = static_cast<int>(reinterpret_cast<uint64>(arg));
- g_fifo_mut.lock();
- g_fifo_order[slot] = __sync_fetch_and_add(&g_fifo_seq, 1);
- g_fifo_mut.unlock();
- __sync_fetch_and_add(&g_fifo_done, 1);
- task_exit(0);
-}
-
-static void test_fifo_order()
-{
- st_begin("mutex: FIFO wake order (3 waiters)");
-
- g_fifo_mut.init("test_mutex_fifo");
- g_fifo_seq = 0;
- g_fifo_done = 0;
- g_fifo_order[0] = g_fifo_order[1] = g_fifo_order[2] = -1;
-
- /* Main grabs the lock first so workers will queue up behind. */
- g_fifo_mut.lock();
-
- /* Spawn workers in deterministic order. After each create() yield
- * once so the new thread reaches mutex.lock() and enqueues itself
- * before we spawn the next one. */
- kthread_create("fifo_A", fifo_worker, reinterpret_cast<void*>(0), 0);
- sched_yield();
- kthread_create("fifo_B", fifo_worker, reinterpret_cast<void*>(1), 0);
- sched_yield();
- kthread_create("fifo_C", fifo_worker, reinterpret_cast<void*>(2), 0);
- sched_yield();
-
- /* Release: A should fire next, then B, then C. */
- g_fifo_mut.unlock();
-
- while (g_fifo_done < 3) {
- sched_yield();
- }
-
- ST_ASSERT_EQ(g_fifo_order[0], 0);
- ST_ASSERT_EQ(g_fifo_order[1], 1);
- ST_ASSERT_EQ(g_fifo_order[2], 2);
-
- st_pass();
-}
-
-/* ------------------------------------------------------------------ */
-/* Entry */
-/* ------------------------------------------------------------------ */
-
-void selftest_mutex()
-{
- test_basic();
- test_trylock();
- test_mutual_exclusion();
- test_fifo_order();
-}
diff --git a/test/test_namei.cc b/test/test_namei.cc
deleted file mode 100644
index 8e4ecb0..0000000
--- a/test/test_namei.cc
+++ /dev/null
@@ -1,173 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-/*
- * test_namei.cc — Self-tests for pathname resolution (namei.cc)
- */
-
-#include <lume/selftest.h>
-#include <lume/namei.h>
-#include <lume/dcache.h>
-#include <lume/fs.h>
-#include <lume/file.h>
-#include <lume/mount.h>
-#include <lume/task.h>
-#include <lume/errno.h>
-
-extern dentry* g_root_dentry;
-extern vfsmount* g_root_mnt;
-
-/* sys_mkdirat from kernel/sys_fs.cc */
-int64 sys_mkdirat(uint64, uint64, uint64, uint64, uint64, uint64);
-
-void selftest_namei()
-{
- /* ---- lookup root "/" ---- */
- st_begin("namei: lookup root /");
- {
- path p{};
- int err = filename_lookup(AT_FDCWD, "/", 0, &p);
- ST_ASSERT_EQ(err, 0);
- ST_ASSERT(p.de == g_root_dentry);
- ST_ASSERT(p.de->d_inode != nullptr);
- ST_ASSERT(S_ISDIR(p.de->d_inode->i_mode));
- }
- st_pass();
-
- /* ---- lookup /dev (exists from boot) ---- */
- st_begin("namei: lookup /dev");
- {
- path p{};
- int err = filename_lookup(AT_FDCWD, "/dev", 0, &p);
- ST_ASSERT_EQ(err, 0);
- ST_ASSERT(p.de != nullptr);
- ST_ASSERT(p.de->d_inode != nullptr);
- ST_ASSERT(S_ISDIR(p.de->d_inode->i_mode));
- }
- st_pass();
-
- /* ---- lookup nonexistent path returns -ENOENT ---- */
- st_begin("namei: lookup nonexistent returns -ENOENT");
- {
- path p{};
- int err = filename_lookup(AT_FDCWD, "/this_does_not_exist", 0, &p);
- ST_ASSERT_EQ(err, -ENOENT);
- }
- st_pass();
-
- /* ---- lookup /dev/console (chardev) ---- */
- st_begin("namei: lookup /dev/console");
- {
- path p{};
- int err = filename_lookup(AT_FDCWD, "/dev/console", 0, &p);
- ST_ASSERT_EQ(err, 0);
- ST_ASSERT(p.de->d_inode != nullptr);
- ST_ASSERT(S_ISCHR(p.de->d_inode->i_mode));
- }
- st_pass();
-
- /* ---- lookup with LOOKUP_DIRECTORY on non-dir returns -ENOTDIR ---- */
- st_begin("namei: LOOKUP_DIRECTORY on file returns -ENOTDIR");
- {
- path p{};
- int err = filename_lookup(AT_FDCWD, "/dev/console", LOOKUP_DIRECTORY, &p);
- ST_ASSERT_EQ(err, -ENOTDIR);
- }
- st_pass();
-
- /* ---- lookup with LOOKUP_DIRECTORY on dir succeeds ---- */
- st_begin("namei: LOOKUP_DIRECTORY on dir succeeds");
- {
- path p{};
- int err = filename_lookup(AT_FDCWD, "/dev", LOOKUP_DIRECTORY, &p);
- ST_ASSERT_EQ(err, 0);
- ST_ASSERT(S_ISDIR(p.de->d_inode->i_mode));
- }
- st_pass();
-
- /* ---- kern_path shortcut ---- */
- st_begin("namei: kern_path works like filename_lookup");
- {
- path p{};
- int err = kern_path("/dev", 0, &p);
- ST_ASSERT_EQ(err, 0);
- ST_ASSERT(p.de->d_inode != nullptr);
- }
- st_pass();
-
- /* ---- dot path: /dev/. resolves to /dev ---- */
- st_begin("namei: /dev/. resolves to /dev");
- {
- path p1{}, p2{};
- int e1 = filename_lookup(AT_FDCWD, "/dev", 0, &p1);
- int e2 = filename_lookup(AT_FDCWD, "/dev/.", 0, &p2);
- ST_ASSERT_EQ(e1, 0);
- ST_ASSERT_EQ(e2, 0);
- ST_ASSERT(p1.de == p2.de);
- }
- st_pass();
-
- /* ---- dotdot path: /dev/.. resolves to root ---- */
- st_begin("namei: /dev/.. resolves to root");
- {
- path p{};
- int err = filename_lookup(AT_FDCWD, "/dev/..", 0, &p);
- ST_ASSERT_EQ(err, 0);
- ST_ASSERT(p.de == g_root_dentry);
- }
- st_pass();
-
- /* ---- lookup into non-dir returns -ENOTDIR ---- */
- st_begin("namei: path through non-dir returns -ENOTDIR");
- {
- path p{};
- int err = filename_lookup(AT_FDCWD, "/dev/console/child", 0, &p);
- ST_ASSERT_EQ(err, -ENOTDIR);
- }
- st_pass();
-
- /* ---- null/empty paths ---- */
- st_begin("namei: null name returns -EINVAL");
- {
- path p{};
- int err = filename_lookup(AT_FDCWD, nullptr, 0, &p);
- ST_ASSERT_EQ(err, -EINVAL);
- }
- st_pass();
-
- /* ---- LOOKUP_PARENT: returns parent dentry ---- */
- st_begin("namei: LOOKUP_PARENT returns parent");
- {
- path p{};
- int err = filename_lookup(AT_FDCWD, "/dev/console", LOOKUP_PARENT, &p);
- ST_ASSERT_EQ(err, 0);
- /* Should return /dev, not /dev/console */
- path dev_path{};
- int e2 = filename_lookup(AT_FDCWD, "/dev", 0, &dev_path);
- ST_ASSERT_EQ(e2, 0);
- ST_ASSERT(p.de == dev_path.de);
- }
- st_pass();
-
- /* ---- mkdir + lookup round-trip ---- */
- st_begin("namei: mkdir then lookup succeeds");
- {
- /* Create /test_namei_dir via sys_mkdirat */
- int64 ret = sys_mkdirat(
- static_cast<uint64>(-100),
- reinterpret_cast<uint64>("/test_namei_dir"),
- 0755, 0, 0, 0);
- ST_ASSERT_EQ(ret, (int64)0);
-
- /* Now lookup should find it */
- path p{};
- int err = filename_lookup(AT_FDCWD, "/test_namei_dir", 0, &p);
- ST_ASSERT_EQ(err, 0);
- ST_ASSERT(p.de->d_inode != nullptr);
- ST_ASSERT(S_ISDIR(p.de->d_inode->i_mode));
- }
- st_pass();
-
- /* NOTE: trailing slash "/dev/" not tested — Phase 1 namei
- * doesn't handle empty trailing components. */
-}
diff --git a/test/test_page_cache.cc b/test/test_page_cache.cc
deleted file mode 100644
index ee0e0e2..0000000
--- a/test/test_page_cache.cc
+++ /dev/null
@@ -1,180 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-/*
- * test_page_cache.cc — Self-tests for the page cache hash table
- *
- * Tests: insert, get, remove, duplicate insert, invalidate,
- * get_or_alloc, null args.
- */
-
-#include <lume/selftest.h>
-#include <lume/page_cache.h>
-#include <lume/page.h>
-#include <lume/pmm.h>
-#include <lume/errno.h>
-
-void selftest_page_cache()
-{
- /* Use sentinel pointers as fake "mapping" identifiers.
- * These are never dereferenced — only used as hash keys. */
- int mapping_a_tag = 0;
- int mapping_b_tag = 0;
- void* mapping_a = &mapping_a_tag;
- void* mapping_b = &mapping_b_tag;
-
- /* ---- insert and get ---- */
- st_begin("page_cache: insert then get returns same page");
- {
- Page* page = pmm_alloc_page();
- ST_ASSERT(page != nullptr);
-
- int ret = page_cache_insert(mapping_a, 0, page);
- ST_ASSERT_EQ(ret, 0);
-
- Page* got = page_cache_get(mapping_a, 0);
- ST_ASSERT_EQ(got, page);
-
- /* put the reference from get */
- page_cache_put(got);
-
- /* Clean up */
- page_cache_remove(mapping_a, 0);
- pmm_free_page(page);
- }
- st_pass();
-
- /* ---- get nonexistent returns nullptr ---- */
- st_begin("page_cache: get nonexistent returns nullptr");
- {
- Page* got = page_cache_get(mapping_a, 99999);
- ST_ASSERT(got == nullptr);
- }
- st_pass();
-
- /* ---- duplicate insert returns -EEXIST ---- */
- st_begin("page_cache: duplicate insert returns -EEXIST");
- {
- Page* page = pmm_alloc_page();
- ST_ASSERT(page != nullptr);
-
- int ret1 = page_cache_insert(mapping_a, 1, page);
- ST_ASSERT_EQ(ret1, 0);
-
- Page* page2 = pmm_alloc_page();
- ST_ASSERT(page2 != nullptr);
-
- int ret2 = page_cache_insert(mapping_a, 1, page2);
- ST_ASSERT_EQ(ret2, -EEXIST);
-
- /* Clean up */
- page_cache_remove(mapping_a, 1);
- pmm_free_page(page);
- pmm_free_page(page2);
- }
- st_pass();
-
- /* ---- remove returns page ---- */
- st_begin("page_cache: remove returns page and deletes entry");
- {
- Page* page = pmm_alloc_page();
- ST_ASSERT(page != nullptr);
-
- page_cache_insert(mapping_a, 2, page);
-
- Page* removed = page_cache_remove(mapping_a, 2);
- ST_ASSERT_EQ(removed, page);
-
- /* Should be gone now */
- Page* got = page_cache_get(mapping_a, 2);
- ST_ASSERT(got == nullptr);
-
- pmm_free_page(page);
- }
- st_pass();
-
- /* ---- remove nonexistent returns nullptr ---- */
- st_begin("page_cache: remove nonexistent returns nullptr");
- {
- Page* got = page_cache_remove(mapping_a, 77777);
- ST_ASSERT(got == nullptr);
- }
- st_pass();
-
- /* ---- different mappings are independent ---- */
- st_begin("page_cache: different mappings are independent");
- {
- Page* pa = pmm_alloc_page();
- Page* pb = pmm_alloc_page();
- ST_ASSERT(pa != nullptr);
- ST_ASSERT(pb != nullptr);
-
- page_cache_insert(mapping_a, 5, pa);
- page_cache_insert(mapping_b, 5, pb);
-
- Page* got_a = page_cache_get(mapping_a, 5);
- Page* got_b = page_cache_get(mapping_b, 5);
- ST_ASSERT_EQ(got_a, pa);
- ST_ASSERT_EQ(got_b, pb);
- ST_ASSERT_NE(got_a, got_b);
-
- page_cache_put(got_a);
- page_cache_put(got_b);
- page_cache_remove(mapping_a, 5);
- page_cache_remove(mapping_b, 5);
- pmm_free_page(pa);
- pmm_free_page(pb);
- }
- st_pass();
-
- /* ---- invalidate removes all entries for a mapping ---- */
- st_begin("page_cache: invalidate removes all entries for mapping");
- {
- Page* pages[4];
- for (int i = 0; i < 4; i++) {
- pages[i] = pmm_alloc_page();
- ST_ASSERT(pages[i] != nullptr);
- page_cache_insert(mapping_a, 10 + i, pages[i]);
- }
-
- /* Also insert one for mapping_b */
- Page* pb = pmm_alloc_page();
- page_cache_insert(mapping_b, 10, pb);
-
- /* Invalidate mapping_a */
- page_cache_invalidate(mapping_a);
-
- /* All mapping_a entries should be gone */
- for (int i = 0; i < 4; i++) {
- Page* got = page_cache_get(mapping_a, 10 + i);
- ST_ASSERT(got == nullptr);
- pmm_free_page(pages[i]);
- }
-
- /* mapping_b should still be there */
- Page* got_b = page_cache_get(mapping_b, 10);
- ST_ASSERT_EQ(got_b, pb);
- page_cache_put(got_b);
- page_cache_remove(mapping_b, 10);
- pmm_free_page(pb);
- }
- st_pass();
-
- /* ---- insert null mapping returns -EINVAL ---- */
- st_begin("page_cache: insert null mapping returns -EINVAL");
- {
- Page* page = pmm_alloc_page();
- int ret = page_cache_insert(nullptr, 0, page);
- ST_ASSERT_EQ(ret, -EINVAL);
- pmm_free_page(page);
- }
- st_pass();
-
- /* ---- insert null page returns -EINVAL ---- */
- st_begin("page_cache: insert null page returns -EINVAL");
- {
- int ret = page_cache_insert(mapping_a, 0, nullptr);
- ST_ASSERT_EQ(ret, -EINVAL);
- }
- st_pass();
-}
diff --git a/test/test_pid.cc b/test/test_pid.cc
deleted file mode 100644
index e79eb1e..0000000
--- a/test/test_pid.cc
+++ /dev/null
@@ -1,106 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-/*
- * test_pid.cc — Self-tests for PID allocator
- *
- * Tests: alloc, free, re-use, exhaustion guard, reserved PIDs.
- */
-
-#include <lume/selftest.h>
-#include <lume/pid.h>
-
-void selftest_pid()
-{
- /* Create a local PidAllocator for isolated testing */
- PidAllocator pa;
- pa.init();
-
- /* ---- PID 0 is reserved (idle/swapper) ---- */
- st_begin("pid: PID 0 is reserved");
- {
- /* First alloc should return 1 (PID 0 reserved for idle) */
- int32 first = pa.alloc();
- ST_ASSERT(first >= 1);
- }
- st_pass();
-
- /* ---- Sequential allocation returns unique PIDs ---- */
- st_begin("pid: sequential allocation returns unique PIDs");
- {
- PidAllocator pa2;
- pa2.init();
-
- int32 p1 = pa2.alloc();
- int32 p2 = pa2.alloc();
- int32 p3 = pa2.alloc();
- ST_ASSERT(p1 >= 1);
- ST_ASSERT(p2 >= 1);
- ST_ASSERT(p3 >= 1);
- ST_ASSERT_NE(p1, p2);
- ST_ASSERT_NE(p2, p3);
- ST_ASSERT_NE(p1, p3);
- }
- st_pass();
-
- /* ---- Freed PID can be reused ---- */
- st_begin("pid: freed PID can be reused");
- {
- PidAllocator pa3;
- pa3.init();
-
- /* Allocate all PIDs from 2..N */
- constexpr int kAllocCount = 60;
- int32 pids[kAllocCount];
- for (int i = 0; i < kAllocCount; i++) {
- pids[i] = pa3.alloc();
- ST_ASSERT(pids[i] >= 1);
- }
-
- /* Free one in the middle */
- int32 freed_pid = pids[kAllocCount / 2];
- pa3.free(freed_pid);
-
- /* Allocate again — should eventually get the freed PID */
- bool found = false;
- for (int i = 0; i < kAllocCount; i++) {
- int32 p = pa3.alloc();
- if (p == freed_pid) { found = true; break; }
- pa3.free(p);
- }
- ST_ASSERT(found);
- }
- st_pass();
-
- /* ---- free invalid PID is safe ---- */
- st_begin("pid: free invalid PID is no-op");
- {
- PidAllocator pa4;
- pa4.init();
-
- /* These should not crash */
- pa4.free(-1);
- pa4.free(kMaxPid);
- pa4.free(kMaxPid + 100);
-
- /* Allocator should still work — PID 0 still reserved */
- int32 p = pa4.alloc();
- ST_ASSERT(p >= 1);
- }
- st_pass();
-
- /* ---- Monotonic allocation before wrap ---- */
- st_begin("pid: monotonic allocation");
- {
- PidAllocator pa5;
- pa5.init();
-
- int32 prev = pa5.alloc();
- for (int i = 0; i < 10; i++) {
- int32 next = pa5.alloc();
- ST_ASSERT(next > prev);
- prev = next;
- }
- }
- st_pass();
-}
diff --git a/test/test_pipe_devfs.cc b/test/test_pipe_devfs.cc
deleted file mode 100644
index ed375ed..0000000
--- a/test/test_pipe_devfs.cc
+++ /dev/null
@@ -1,420 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-/*
- * test_pipe_devfs.cc — Self-tests for pipe, /dev/null, /dev/zero, fcntl
- */
-
-#include <lume/selftest.h>
-#include <lume/errno.h>
-#include <lume/file.h>
-#include <lume/fs.h>
-#include <lume/fs_types.h>
-#include <lume/task.h>
-#include <lume/fdtable.h>
-#include <lume/pipe.h>
-
-/* Syscall entry points */
-int64 sys_openat(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_close(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_read(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_write(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_pipe2(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_fcntl(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_fstat_wrapper(uint64, uint64, uint64, uint64, uint64, uint64);
-
-/* ================================================================== */
-/* Helpers */
-/* ================================================================== */
-
-static int64 open_dev(const char* path, int flags)
-{
- return sys_openat(
- static_cast<uint64>(-100), /* AT_FDCWD */
- reinterpret_cast<uint64>(path),
- static_cast<uint64>(flags),
- 0, 0, 0);
-}
-
-static int64 do_close(int64 fd)
-{
- return sys_close(static_cast<uint64>(fd), 0, 0, 0, 0, 0);
-}
-
-static int64 do_read(int64 fd, void* buf, uint64 count)
-{
- return sys_read(static_cast<uint64>(fd),
- reinterpret_cast<uint64>(buf), count, 0, 0, 0);
-}
-
-static int64 do_write(int64 fd, const void* buf, uint64 count)
-{
- return sys_write(static_cast<uint64>(fd),
- reinterpret_cast<uint64>(buf), count, 0, 0, 0);
-}
-
-/* fcntl command constants — must match sys_fs.cc */
-inline constexpr int F_DUPFD = 0;
-inline constexpr int F_GETFD = 1;
-inline constexpr int F_SETFD = 2;
-inline constexpr int F_GETFL = 3;
-inline constexpr int F_SETFL = 4;
-inline constexpr int F_DUPFD_CLOEXEC = 1030;
-inline constexpr int FD_CLOEXEC = 1;
-
-/* ================================================================== */
-/* /dev/null tests */
-/* ================================================================== */
-
-void selftest_devfs()
-{
- /* ---- /dev/null: read returns EOF ---- */
- st_begin("devfs: /dev/null read returns 0 (EOF)");
- {
- int64 fd = open_dev("/dev/null", O_RDONLY);
- ST_ASSERT(fd >= 0);
-
- char buf[16];
- int64 n = do_read(fd, buf, sizeof(buf));
- ST_ASSERT_EQ(n, (int64)0);
- do_close(fd);
- }
- st_pass();
-
- /* ---- /dev/null: write discards data ---- */
- st_begin("devfs: /dev/null write discards data");
- {
- int64 fd = open_dev("/dev/null", O_WRONLY);
- ST_ASSERT(fd >= 0);
-
- const char* msg = "discarded";
- int64 n = do_write(fd, msg, 9);
- ST_ASSERT_EQ(n, (int64)9);
- do_close(fd);
- }
- st_pass();
-
- /* ---- /dev/null: fstat reports S_IFCHR ---- */
- st_begin("devfs: /dev/null fstat is S_IFCHR");
- {
- int64 fd = open_dev("/dev/null", O_RDONLY);
- ST_ASSERT(fd >= 0);
-
- kstat st{};
- int64 ret = sys_fstat_wrapper(
- static_cast<uint64>(fd),
- reinterpret_cast<uint64>(&st), 0, 0, 0, 0);
- ST_ASSERT_EQ(ret, (int64)0);
- ST_ASSERT(S_ISCHR(st.st_mode));
- do_close(fd);
- }
- st_pass();
-
- /* ---- /dev/zero: read returns zeros ---- */
- st_begin("devfs: /dev/zero read returns zeros");
- {
- int64 fd = open_dev("/dev/zero", O_RDONLY);
- ST_ASSERT(fd >= 0);
-
- char buf[32];
- /* Pre-fill with non-zero */
- for (int i = 0; i < 32; i++) buf[i] = 0xFF;
-
- int64 n = do_read(fd, buf, 32);
- ST_ASSERT_EQ(n, (int64)32);
-
- bool all_zero = true;
- for (int i = 0; i < 32; i++) {
- if (buf[i] != 0) { all_zero = false; break; }
- }
- ST_ASSERT(all_zero);
- do_close(fd);
- }
- st_pass();
-
- /* ---- /dev/zero: write discards data ---- */
- st_begin("devfs: /dev/zero write discards data");
- {
- int64 fd = open_dev("/dev/zero", O_WRONLY);
- ST_ASSERT(fd >= 0);
-
- char buf[16] = {1, 2, 3};
- int64 n = do_write(fd, buf, 16);
- ST_ASSERT_EQ(n, (int64)16);
- do_close(fd);
- }
- st_pass();
-}
-
-/* ================================================================== */
-/* Pipe tests */
-/* ================================================================== */
-
-void selftest_pipe()
-{
- /* ---- pipe2: basic creation ---- */
- st_begin("pipe: pipe2 creates two valid fds");
- {
- int fds[2] = {-1, -1};
- int64 ret = sys_pipe2(
- reinterpret_cast<uint64>(fds), 0, 0, 0, 0, 0);
- ST_ASSERT_EQ(ret, (int64)0);
- ST_ASSERT(fds[0] >= 0);
- ST_ASSERT(fds[1] >= 0);
- ST_ASSERT_NE(fds[0], fds[1]);
-
- do_close(fds[0]);
- do_close(fds[1]);
- }
- st_pass();
-
- /* ---- pipe: write then read ---- */
- st_begin("pipe: write then read returns correct data");
- {
- int fds[2];
- sys_pipe2(reinterpret_cast<uint64>(fds), 0, 0, 0, 0, 0);
-
- const char* msg = "pipe_test";
- int64 w = do_write(fds[1], msg, 9);
- ST_ASSERT_EQ(w, (int64)9);
-
- char buf[16] = {};
- int64 r = do_read(fds[0], buf, 16);
- ST_ASSERT_EQ(r, (int64)9);
-
- bool match = true;
- for (int i = 0; i < 9; i++) {
- if (buf[i] != msg[i]) { match = false; break; }
- }
- ST_ASSERT(match);
-
- do_close(fds[0]);
- do_close(fds[1]);
- }
- st_pass();
-
- /* ---- pipe: read from closed write end returns EOF ---- */
- st_begin("pipe: read after writer close returns EOF");
- {
- int fds[2];
- sys_pipe2(reinterpret_cast<uint64>(fds), 0, 0, 0, 0, 0);
-
- /* Write some data, then close write end */
- const char* msg = "eof";
- do_write(fds[1], msg, 3);
- do_close(fds[1]);
-
- /* Read the data */
- char buf[16];
- int64 r1 = do_read(fds[0], buf, 16);
- ST_ASSERT_EQ(r1, (int64)3);
-
- /* Next read should return 0 (EOF) */
- int64 r2 = do_read(fds[0], buf, 16);
- ST_ASSERT_EQ(r2, (int64)0);
-
- do_close(fds[0]);
- }
- st_pass();
-
- /* ---- pipe: write to closed read end returns -EPIPE ---- */
- st_begin("pipe: write after reader close returns -EPIPE");
- {
- int fds[2];
- sys_pipe2(reinterpret_cast<uint64>(fds), 0, 0, 0, 0, 0);
-
- /* Close read end */
- do_close(fds[0]);
-
- /* Write should fail */
- const char* msg = "broken";
- int64 w = do_write(fds[1], msg, 6);
- ST_ASSERT_EQ(w, (int64)-EPIPE);
-
- do_close(fds[1]);
- }
- st_pass();
-
- /* ---- pipe: multiple writes accumulate ---- */
- st_begin("pipe: multiple writes accumulate in buffer");
- {
- int fds[2];
- sys_pipe2(reinterpret_cast<uint64>(fds), 0, 0, 0, 0, 0);
-
- do_write(fds[1], "AAA", 3);
- do_write(fds[1], "BBB", 3);
-
- char buf[16] = {};
- int64 r = do_read(fds[0], buf, 16);
- ST_ASSERT_EQ(r, (int64)6);
- ST_ASSERT_EQ(buf[0], 'A');
- ST_ASSERT_EQ(buf[3], 'B');
-
- do_close(fds[0]);
- do_close(fds[1]);
- }
- st_pass();
-
- /* ---- pipe: read partial ---- */
- st_begin("pipe: partial read leaves remainder");
- {
- int fds[2];
- sys_pipe2(reinterpret_cast<uint64>(fds), 0, 0, 0, 0, 0);
-
- do_write(fds[1], "ABCDEF", 6);
-
- char buf[3] = {};
- int64 r1 = do_read(fds[0], buf, 3);
- ST_ASSERT_EQ(r1, (int64)3);
- ST_ASSERT_EQ(buf[0], 'A');
- ST_ASSERT_EQ(buf[2], 'C');
-
- int64 r2 = do_read(fds[0], buf, 3);
- ST_ASSERT_EQ(r2, (int64)3);
- ST_ASSERT_EQ(buf[0], 'D');
- ST_ASSERT_EQ(buf[2], 'F');
-
- do_close(fds[0]);
- do_close(fds[1]);
- }
- st_pass();
-}
-
-/* ================================================================== */
-/* fcntl tests */
-/* ================================================================== */
-
-void selftest_fcntl()
-{
- /* ---- fcntl F_DUPFD ---- */
- st_begin("fcntl: F_DUPFD duplicates fd");
- {
- int64 fd = open_dev("/dev/null", O_RDONLY);
- ST_ASSERT(fd >= 0);
-
- int64 fd2 = sys_fcntl(static_cast<uint64>(fd), F_DUPFD, 0, 0, 0, 0);
- ST_ASSERT(fd2 >= 0);
- ST_ASSERT_NE(fd, fd2);
-
- /* Both should be valid reads */
- char buf[4];
- int64 r = do_read(fd2, buf, 4);
- ST_ASSERT_EQ(r, (int64)0); /* /dev/null → EOF */
-
- do_close(fd);
- do_close(fd2);
- }
- st_pass();
-
- /* ---- fcntl F_DUPFD with min_fd ---- */
- st_begin("fcntl: F_DUPFD respects min_fd");
- {
- int64 fd = open_dev("/dev/null", O_RDONLY);
- ST_ASSERT(fd >= 0);
-
- int64 fd2 = sys_fcntl(static_cast<uint64>(fd), F_DUPFD, 100, 0, 0, 0);
- ST_ASSERT(fd2 >= 100);
-
- do_close(fd);
- do_close(fd2);
- }
- st_pass();
-
- /* ---- fcntl F_GETFD / F_SETFD ---- */
- st_begin("fcntl: F_GETFD/F_SETFD cloexec");
- {
- int64 fd = open_dev("/dev/null", O_RDONLY);
- ST_ASSERT(fd >= 0);
-
- /* Initially no CLOEXEC */
- int64 flags = sys_fcntl(static_cast<uint64>(fd), F_GETFD, 0, 0, 0, 0);
- ST_ASSERT_EQ(flags, (int64)0);
-
- /* Set CLOEXEC */
- int64 ret = sys_fcntl(static_cast<uint64>(fd), F_SETFD, FD_CLOEXEC, 0, 0, 0);
- ST_ASSERT_EQ(ret, (int64)0);
-
- /* Verify it was set */
- flags = sys_fcntl(static_cast<uint64>(fd), F_GETFD, 0, 0, 0, 0);
- ST_ASSERT_EQ(flags, (int64)FD_CLOEXEC);
-
- /* Clear it */
- sys_fcntl(static_cast<uint64>(fd), F_SETFD, 0, 0, 0, 0);
- flags = sys_fcntl(static_cast<uint64>(fd), F_GETFD, 0, 0, 0, 0);
- ST_ASSERT_EQ(flags, (int64)0);
-
- do_close(fd);
- }
- st_pass();
-
- /* ---- fcntl F_GETFL ---- */
- st_begin("fcntl: F_GETFL returns open flags");
- {
- int64 fd = open_dev("/dev/null", O_RDONLY);
- ST_ASSERT(fd >= 0);
-
- int64 fl = sys_fcntl(static_cast<uint64>(fd), F_GETFL, 0, 0, 0, 0);
- /* O_RDONLY == 0, so result should have no write bits */
- ST_ASSERT_EQ(fl & O_WRONLY, (int64)0);
-
- do_close(fd);
- }
- st_pass();
-
- /* ---- fcntl F_SETFL O_NONBLOCK ---- */
- st_begin("fcntl: F_SETFL toggles O_NONBLOCK");
- {
- int64 fd = open_dev("/dev/null", O_RDONLY);
- ST_ASSERT(fd >= 0);
-
- /* Set O_NONBLOCK */
- sys_fcntl(static_cast<uint64>(fd), F_SETFL, O_NONBLOCK, 0, 0, 0);
- int64 fl = sys_fcntl(static_cast<uint64>(fd), F_GETFL, 0, 0, 0, 0);
- ST_ASSERT(fl & O_NONBLOCK);
-
- /* Clear it */
- sys_fcntl(static_cast<uint64>(fd), F_SETFL, 0, 0, 0, 0);
- fl = sys_fcntl(static_cast<uint64>(fd), F_GETFL, 0, 0, 0, 0);
- ST_ASSERT_EQ(fl & O_NONBLOCK, (int64)0);
-
- do_close(fd);
- }
- st_pass();
-
- /* ---- fcntl F_DUPFD_CLOEXEC ---- */
- st_begin("fcntl: F_DUPFD_CLOEXEC sets cloexec");
- {
- int64 fd = open_dev("/dev/null", O_RDONLY);
- ST_ASSERT(fd >= 0);
-
- int64 fd2 = sys_fcntl(static_cast<uint64>(fd), F_DUPFD_CLOEXEC, 0, 0, 0, 0);
- ST_ASSERT(fd2 >= 0);
-
- int64 flags = sys_fcntl(static_cast<uint64>(fd2), F_GETFD, 0, 0, 0, 0);
- ST_ASSERT_EQ(flags, (int64)FD_CLOEXEC);
-
- do_close(fd);
- do_close(fd2);
- }
- st_pass();
-
- /* ---- fcntl bad fd ---- */
- st_begin("fcntl: bad fd returns -EBADF");
- {
- int64 ret = sys_fcntl(999, F_GETFD, 0, 0, 0, 0);
- ST_ASSERT_EQ(ret, (int64)-EBADF);
- }
- st_pass();
-
- /* ---- fcntl unknown cmd ---- */
- st_begin("fcntl: unknown cmd returns -EINVAL");
- {
- int64 fd = open_dev("/dev/null", O_RDONLY);
- ST_ASSERT(fd >= 0);
-
- int64 ret = sys_fcntl(static_cast<uint64>(fd), 9999, 0, 0, 0, 0);
- ST_ASSERT_EQ(ret, (int64)-EINVAL);
- do_close(fd);
- }
- st_pass();
-}
diff --git a/test/test_pmm.cc b/test/test_pmm.cc
deleted file mode 100644
index 3330866..0000000
--- a/test/test_pmm.cc
+++ /dev/null
@@ -1,283 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-/*
- * Boot Self-Test: Physical Memory Manager (PMM)
- *
- * Tests:
- * 1. Single page alloc/free round-trip
- * 2. Page address alignment (4KB)
- * 3. No duplicate pages from consecutive allocs
- * 4. Free then re-alloc returns the same page (PCP cache)
- * 5. Multi-page (order > 0) alloc/free
- * 6. Bulk alloc stress test (alloc many, free all)
- * 7. Reference counting (incref/decref)
- * 8. page_to_pa / pa_to_page round-trip
- */
-
-#include <lume/selftest.h>
-#include <lume/pmm.h>
-#include <lume/addr.h>
-
-void selftest_pmm()
-{
- st_begin("pmm: single page alloc/free");
- {
- Page* page = pmm_alloc_page();
- ST_ASSERT(page != nullptr);
- ST_ASSERT(page->type == PageType::Allocated);
- ST_ASSERT(page->order == 0);
-
- uint64 pa = page_to_pa(page).raw;
- ST_ASSERT_EQ(pa % kPageSize, 0ULL); // 4KB aligned
-
- uint64 va = page_to_va(page).raw;
- ST_ASSERT_EQ(va, pa_to_va(phys_addr(pa)).raw);
-
- pmm_free_page(page);
- }
- st_pass();
-
- st_begin("pmm: no duplicate pages");
- {
- Page* p1 = pmm_alloc_page();
- Page* p2 = pmm_alloc_page();
- ST_ASSERT(p1 != nullptr);
- ST_ASSERT(p2 != nullptr);
- ST_ASSERT_NE(p1, p2);
- ST_ASSERT_NE(page_to_pa(p1).raw, page_to_pa(p2).raw);
- pmm_free_page(p2);
- pmm_free_page(p1);
- }
- st_pass();
-
- st_begin("pmm: free then re-alloc (PCP)");
- {
- Page* p1 = pmm_alloc_page();
- uint64 pa1 = page_to_pa(p1).raw;
- pmm_free_page(p1);
-
- // PCP should return the most recently freed page
- Page* p2 = pmm_alloc_page();
- uint64 pa2 = page_to_pa(p2).raw;
- ST_ASSERT_EQ(pa1, pa2);
- pmm_free_page(p2);
- }
- st_pass();
-
- st_begin("pmm: multi-page alloc (order 2 = 4 pages)");
- {
- Page* page = pmm_alloc_pages(2);
- ST_ASSERT(page != nullptr);
- ST_ASSERT(page->order == 2);
-
- uint64 pa = page_to_pa(page).raw;
- ST_ASSERT_EQ(pa % (kPageSize * 4), 0ULL); // Naturally aligned
-
- pmm_free_pages(page, 2);
- }
- st_pass();
-
- st_begin("pmm: bulk alloc/free stress (256 pages)");
- {
- constexpr int N = 256;
- Page* pages[N];
-
- // Allocate all
- for (int i = 0; i < N; i++) {
- pages[i] = pmm_alloc_page();
- ST_ASSERT(pages[i] != nullptr);
- }
-
- // Verify no duplicates (spot check: consecutive pairs)
- for (int i = 1; i < N; i++) {
- ST_ASSERT_NE(pages[i], pages[i - 1]);
- }
-
- // Free all
- for (int i = 0; i < N; i++) {
- pmm_free_page(pages[i]);
- }
- }
- st_pass();
-
- st_begin("pmm: page_to_pa / pa_to_page round-trip");
- {
- Page* page = pmm_alloc_page();
- ST_ASSERT(page != nullptr);
- uint64 pa = page_to_pa(page).raw;
- Page* page2 = pa_to_page(phys_addr(pa));
- ST_ASSERT_EQ(page, page2);
- pmm_free_page(page);
- }
- st_pass();
-
- st_begin("pmm: refcount incref/decref");
- {
- Page* page = pmm_alloc_page();
- ST_ASSERT(page != nullptr);
- ST_ASSERT(page->refcount.load() == 1);
-
- page_incref(page);
- ST_ASSERT(page->refcount.load() == 2);
-
- page_decref(page); // 2 -> 1, should NOT free
- ST_ASSERT(page->refcount.load() == 1);
- ST_ASSERT(page->type == PageType::Allocated);
-
- page_decref(page); // 1 -> 0, should free to pool
- // After decref to 0, page is returned to pool.
- // We can't safely check page->type because it might be reused.
- }
- st_pass();
- st_begin("pmm: pmm_alloc_pages natural alignment (order-3)");
- {
- Page* page = pmm_alloc_pages(3);
- ST_ASSERT(page != nullptr);
- uint64 pa = page_to_pa(page).raw;
- ST_ASSERT_EQ(pa % (kPageSize * 8), 0ULL);
- pmm_free_pages(page, 3);
- }
- st_pass();
-
- st_begin("pmm: page_decref to 0 and reuse (order-0)");
- {
- Page* page = pmm_alloc_page();
- uint64 pa = page_to_pa(page).raw;
- page_decref(page); // drops 1 -> 0, freeing it
-
- // Next alloc from PCP should give back the same page
- Page* page2 = pmm_alloc_page();
- ST_ASSERT_EQ(page_to_pa(page2).raw, pa);
- pmm_free_page(page2);
- }
- st_pass();
-
- st_begin("pmm: page_decref to 0 on multi-page blocks (order-3)");
- {
- Page* page = pmm_alloc_pages(3);
- uint64 pa = page_to_pa(page).raw;
- page_decref(page); // Should internally route to pmm_free_pages(page, 3)
-
- // Re-allocate order-3 and see if we get it back (buddy coalescing was respected)
- Page* page2 = pmm_alloc_pages(3);
- ST_ASSERT_EQ(page_to_pa(page2).raw, pa);
- pmm_free_pages(page2, 3);
- }
- st_pass();
-
- st_begin("pmm: buddy split and block coalescing correctness");
- {
- // Force split: allocate order-1, free it, allocate two order-0
- Page* page_ord1 = pmm_alloc_pages(1);
- uint64 pa1 = page_to_pa(page_ord1).raw;
- pmm_free_pages(page_ord1, 1);
-
- Page* p0_a = pmm_alloc_page();
- Page* p0_b = pmm_alloc_page();
-
- // Free them to trigger coalesce
- pmm_free_page(p0_a);
- pmm_free_page(p0_b);
-
- // Allocate order-1 again to verify successful coalescing
- Page* page_ord1_again = pmm_alloc_pages(1);
- ST_ASSERT_EQ(page_to_pa(page_ord1_again).raw, pa1);
- pmm_free_pages(page_ord1_again, 1);
- }
- st_pass();
-
- st_begin("pmm: mixed order cross allocation");
- {
- Page* p0 = pmm_alloc_page();
- Page* p2 = pmm_alloc_pages(2);
- Page* p0b = pmm_alloc_page();
-
- ST_ASSERT(p0 != nullptr);
- ST_ASSERT(p2 != nullptr);
- ST_ASSERT(p0b != nullptr);
- ST_ASSERT_NE(page_to_pa(p0).raw, page_to_pa(p2).raw);
-
- pmm_free_page(p0);
- pmm_free_pages(p2, 2);
- pmm_free_page(p0b);
- }
- st_pass();
-
- st_begin("pmm: PCP drain/refill boundaries");
- {
- // kPcpHighWatermark = 64. Allocating and freeing 65 blocks triggers drain.
- Page* pages[65];
- for (int i=0; i<65; i++) pages[i] = pmm_alloc_page();
- for (int i=0; i<65; i++) pmm_free_page(pages[i]);
-
- // The system shouldn't crash and PCP logic should handle this.
- Page* verify_page = pmm_alloc_page();
- ST_ASSERT(verify_page != nullptr);
- pmm_free_page(verify_page);
- }
- st_pass();
-
- st_begin("pmm: OOM exhaustion stability");
- {
- Page* head = nullptr;
- // First allocate large chunks to be fast
- while (true) {
- Page* page = pmm_alloc_pages(5);
- if (!page) break;
- uint64* ptr = reinterpret_cast<uint64*>(page_to_va(page).raw);
- *ptr = reinterpret_cast<uint64>(head);
- // encode order in the second word
- *(ptr + 1) = 5;
- head = page;
- }
- // Then allocate single frames until absolute zero
- while (true) {
- Page* page = pmm_alloc_page();
- if (!page) break;
- uint64* ptr = reinterpret_cast<uint64*>(page_to_va(page).raw);
- *ptr = reinterpret_cast<uint64>(head);
- *(ptr + 1) = 0;
- head = page;
- }
-
- // We hit nullptr. System is completely exhausted.
- ST_ASSERT(pmm_alloc_page() == nullptr);
-
- // Free everything
- while (head) {
- uint64* ptr = reinterpret_cast<uint64*>(page_to_va(head).raw);
- Page* nxt = reinterpret_cast<Page*>(*ptr);
- uint64 order = *(ptr + 1);
- if (order == 5) pmm_free_pages(head, 5);
- else pmm_free_page(head);
- head = nxt;
- }
- }
- st_pass();
-
- st_begin("pmm: out of bounds order allocation gracefully fails");
- {
- // kMaxOrder is 11, so passing 11 or higher should safely return nullptr.
- Page* page = pmm_alloc_pages(11);
- ST_ASSERT(page == nullptr);
- page = pmm_alloc_pages(255);
- ST_ASSERT(page == nullptr);
- }
- st_pass();
-
- st_begin("pmm: pmm_get_mem_base / pmm_get_mem_size sanity");
- {
- uint64 base = pmm_get_mem_base().raw;
- uint64 size = pmm_get_mem_size();
-
- // We no longer assert on QEMU specific memory address
- // ST_ASSERT(base >= 0x80000000ULL);
- ST_ASSERT(size > 0);
- // Size must be page-aligned
- ST_ASSERT_EQ(size % kPageSize, 0ULL);
- // Base must be page-aligned
- ST_ASSERT_EQ(base % kPageSize, 0ULL);
- }
- st_pass();
-}
diff --git a/test/test_random.cc b/test/test_random.cc
deleted file mode 100644
index d546410..0000000
--- a/test/test_random.cc
+++ /dev/null
@@ -1,49 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-#include <lume/selftest.h>
-#include <lume/random.h>
-#include <lume/kprintf.h>
-
-void selftest_random()
-{
- /* Test 1: Basic 64-bit generation */
- kprintf("[test] random: basic 64-bit generation ... ");
- uint64 v1 = get_random_u64();
- uint64 v2 = get_random_u64();
- if (v1 == v2) {
- kernel_panic("random: two consecutive generations were identical!");
- }
- kprintf("PASS\n");
-
- /* Test 2: Basic 32-bit generation */
- kprintf("[test] random: basic 32-bit generation ... ");
- uint32 v3 = get_random_u32();
- uint32 v4 = get_random_u32();
- if (v3 == v4) {
- kernel_panic("random: two consecutive 32-bit generations were identical!");
- }
- kprintf("PASS\n");
-
- /* Test 3: Byte generation */
- kprintf("[test] random: byte generation ... ");
- uint8 buf1[16];
- uint8 buf2[16];
- get_random_bytes(buf1, sizeof(buf1));
- get_random_bytes(buf2, sizeof(buf2));
-
- bool all_zero1 = true;
- bool all_zero2 = true;
- bool same = true;
-
- for (int i = 0; i < 16; i++) {
- if (buf1[i] != 0) all_zero1 = false;
- if (buf2[i] != 0) all_zero2 = false;
- if (buf1[i] != buf2[i]) same = false;
- }
-
- if (all_zero1 || all_zero2 || same) {
- kernel_panic("random: byte generation statistical failure!");
- }
- kprintf("PASS\n");
-}
diff --git a/test/test_rbtree.cc b/test/test_rbtree.cc
deleted file mode 100644
index 27f049a..0000000
--- a/test/test_rbtree.cc
+++ /dev/null
@@ -1,119 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-#include <lume/selftest.h>
-#include <lume/rbtree.h>
-
-struct TestNode {
- int key;
- struct rb_node rb;
-};
-
-static bool node_less(struct rb_node *node, const struct rb_node *parent) {
- TestNode *n = rb_entry(node, TestNode, rb);
- const TestNode *p = reinterpret_cast<const TestNode*>(
- reinterpret_cast<const char*>(parent) - __builtin_offsetof(TestNode, rb));
- return n->key < p->key;
-}
-
-static int node_cmp(const void *key, const struct rb_node *node) {
- int k = *static_cast<const int*>(key);
- const TestNode *n = reinterpret_cast<const TestNode*>(
- reinterpret_cast<const char*>(node) - __builtin_offsetof(TestNode, rb));
- if (k < n->key) return -1;
- if (k > n->key) return 1;
- return 0;
-}
-
-void selftest_rbtree() {
- st_begin("rbtree: basic insert, find, erase");
- {
- struct rb_root tree = RB_ROOT;
- TestNode n1{10, {}}, n2{20, {}}, n3{5, {}};
-
- rb_add(&n1.rb, &tree, node_less);
- rb_add(&n2.rb, &tree, node_less);
- rb_add(&n3.rb, &tree, node_less);
-
- int search_key = 20;
- struct rb_node *found = rb_find(&search_key, &tree, node_cmp);
- ST_ASSERT(found == &n2.rb);
-
- search_key = 99;
- found = rb_find(&search_key, &tree, node_cmp);
- ST_ASSERT(found == nullptr);
-
- rb_erase(&n2.rb, &tree);
- // Verify erased
- search_key = 20;
- found = rb_find(&search_key, &tree, node_cmp);
- ST_ASSERT(found == nullptr);
- }
- st_pass();
-
- st_begin("rbtree: ordered iteration");
- {
- struct rb_root tree = RB_ROOT;
- TestNode nodes[5] = { {50, {}}, {10, {}}, {40, {}}, {20, {}}, {30, {}} };
- for (int i=0; i<5; i++) {
- rb_add(&nodes[i].rb, &tree, node_less);
- }
-
- int expected[] = {10, 20, 30, 40, 50};
- int idx = 0;
- struct rb_node *node = rb_first(&tree);
- while (node) {
- TestNode *n = rb_entry(node, TestNode, rb);
- ST_ASSERT_EQ(n->key, expected[idx]);
- idx++;
- node = rb_next(node);
- }
- ST_ASSERT_EQ(idx, 5);
- }
- st_pass();
-
- st_begin("rbtree: large scale insert/erase balance");
- {
- struct rb_root tree = RB_ROOT;
- constexpr int N = 200;
- static TestNode nodes[N]; // Static to avoid blowing up the BSP stack
- for (int i=0; i<N; i++) {
- nodes[i].key = (i * 17) % N; // Pseudo-random insertions
- rb_add(&nodes[i].rb, &tree, node_less);
- }
-
- int c = 0;
- for (struct rb_node *n = rb_first(&tree); n; n = rb_next(n)) c++;
- ST_ASSERT_EQ(c, N);
-
- for (int i=0; i<N; i+=2) {
- rb_erase(&nodes[i].rb, &tree);
- }
- c = 0;
- for (struct rb_node *n = rb_first(&tree); n; n = rb_next(n)) c++;
- ST_ASSERT_EQ(c, N / 2);
- }
- st_pass();
-
- st_begin("rbtree: empty tree operations");
- {
- struct rb_root tree = RB_ROOT;
- ST_ASSERT(RB_EMPTY_ROOT(&tree));
- ST_ASSERT(rb_first(&tree) == nullptr);
- ST_ASSERT(rb_last(&tree) == nullptr);
- }
- st_pass();
-
- st_begin("rbtree: rb_erase_init after effects");
- {
- struct rb_root tree = RB_ROOT;
- TestNode n1{100, {}};
- rb_add(&n1.rb, &tree, node_less);
- ST_ASSERT(!RB_EMPTY_NODE(&n1.rb));
-
- rb_erase_init(&n1.rb, &tree);
- ST_ASSERT(RB_EMPTY_NODE(&n1.rb));
- ST_ASSERT(RB_EMPTY_ROOT(&tree));
- }
- st_pass();
-}
diff --git a/test/test_sched.cc b/test/test_sched.cc
deleted file mode 100644
index d90c30a..0000000
--- a/test/test_sched.cc
+++ /dev/null
@@ -1,257 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-/*
- * Boot self-tests for the Scheduler subsystem.
- *
- * Tests RunQueue bitmap operations, priority selection,
- * round-robin ordering, current_task(), and time slice policy.
- */
-
-#include <lume/types.h>
-#include <lume/task.h>
-#include <lume/sched.h>
-#include <lume/sched_class.h>
-#include <lume/config.h>
-#include <lume/selftest.h>
-#include <lume/pmm.h>
-#include <lume/addr.h>
-#include <lume/new.h>
-#include <kernel/sched_internal.h>
-
-/* ========================================================================
- * Helper: create a minimal test TCB (no real kernel stack, just metadata)
- * ======================================================================== */
-
-static TaskControlBlock g_test_tcbs[8];
-
-static TaskControlBlock* make_test_tcb(int index, const char* name,
- uint8 priority, uint8 cpu)
-{
- TaskControlBlock* t = &g_test_tcbs[index];
- t->context = {};
- t->wait_chld.init("test_wq");
- t->name = name;
- t->priority = priority;
- t->cpu_id = cpu;
- t->pid = 100 + index;
- t->tgid = 100 + index;
- t->state = TaskState::Runnable;
- t->time_slice = priority_to_timeslice(priority);
- t->run_link.init();
- t->all_link.init();
- t->children.init();
- t->sibling.init();
- t->mm = nullptr;
- t->trapframe = nullptr;
- t->kstack_pa = 0;
- t->kstack_va = 0;
- t->flags = TF_KTHREAD;
- t->sched_class = &g_sched_class_o1;
- t->sched_entity = &t->run_link;
- return t;
-}
-
-/* ========================================================================
- * Test cases
- * ======================================================================== */
-
-static bool test_current_task()
-{
- /* After sched_init, current_task() should be the BSP boot TCB */
- TaskControlBlock* curr = current_task();
- return curr != nullptr && curr->state == TaskState::Running;
-}
-
-static bool test_priority_to_timeslice()
-{
- /* Higher priority (lower number) should get longer time slice */
- int32 ts_high = priority_to_timeslice(0);
- int32 ts_mid = priority_to_timeslice(15);
- int32 ts_low = priority_to_timeslice(31);
-
- return ts_high > ts_mid && ts_mid > ts_low && ts_low >= 1;
-}
-
-static bool test_o1_pick_next_priority()
-{
- RunQueue rq;
- rq.init(0);
- g_sched_class_o1.init_rq(&rq);
-
- TaskControlBlock* t_lo = make_test_tcb(0, "test_p5", 5, 0);
- TaskControlBlock* t_hi = make_test_tcb(1, "test_p2", 2, 0);
-
- g_sched_class_o1.enqueue_task(&rq, t_lo);
- g_sched_class_o1.enqueue_task(&rq, t_hi);
-
- TaskControlBlock* next = g_sched_class_o1.pick_next_task(&rq);
- return next == t_hi;
-}
-
-static bool test_o1_fifo_order()
-{
- RunQueue rq;
- rq.init(0);
- g_sched_class_o1.init_rq(&rq);
-
- TaskControlBlock* t0 = make_test_tcb(0, "test_q0", 10, 0);
- TaskControlBlock* t1 = make_test_tcb(1, "test_q1", 10, 0);
- TaskControlBlock* t2 = make_test_tcb(2, "test_q2", 10, 0);
-
- g_sched_class_o1.enqueue_task(&rq, t0);
- g_sched_class_o1.enqueue_task(&rq, t1);
- g_sched_class_o1.enqueue_task(&rq, t2);
-
- TaskControlBlock* next = g_sched_class_o1.pick_next_task(&rq);
- if (next != t0) return false;
- g_sched_class_o1.dequeue_task(&rq, t0);
-
- next = g_sched_class_o1.pick_next_task(&rq);
- if (next != t1) return false;
- g_sched_class_o1.dequeue_task(&rq, t1);
-
- next = g_sched_class_o1.pick_next_task(&rq);
- if (next != t2) return false;
-
- return true;
-}
-
-static bool test_o1_nr_running()
-{
- RunQueue rq;
- rq.init(0);
- g_sched_class_o1.init_rq(&rq);
-
- TaskControlBlock* t0 = make_test_tcb(0, "test_n0", 8, 0);
- TaskControlBlock* t1 = make_test_tcb(1, "test_n1", 9, 0);
-
- if (rq.nr_running() != 0 || rq.o1().nr_running() != 0) return false;
-
- g_sched_class_o1.enqueue_task(&rq, t0);
- if (rq.nr_running() != 1 || rq.o1().nr_running() != 1) return false;
-
- g_sched_class_o1.enqueue_task(&rq, t1);
- if (rq.nr_running() != 2 || rq.o1().nr_running() != 2) return false;
-
- g_sched_class_o1.dequeue_task(&rq, t0);
- if (rq.nr_running() != 1 || rq.o1().nr_running() != 1) return false;
-
- g_sched_class_o1.dequeue_task(&rq, t1);
- return rq.nr_running() == 0 && rq.o1().nr_running() == 0;
-}
-
-static bool test_o1_pick_next_empty()
-{
- RunQueue rq;
- rq.init(0);
- g_sched_class_o1.init_rq(&rq);
-
- return g_sched_class_o1.pick_next_task(&rq) == nullptr;
-}
-
-static bool test_priority_to_timeslice_bounds()
-{
- /* Boundary values */
- int32 ts0 = priority_to_timeslice(0);
- int32 ts31 = priority_to_timeslice(31);
-
- return ts0 == 20 && ts31 >= 1;
-}
-
-static bool test_enqueue_dequeue()
-{
- /* Create a test task and enqueue/dequeue it */
- TaskControlBlock* t = make_test_tcb(0, "test_ed", 10, 0);
-
- sched_enqueue(t);
- if (t->state != TaskState::Runnable) return false;
-
- sched_dequeue(t);
- /* After dequeue, run_link should be self-pointing */
- return t->run_link.next == &t->run_link;
-}
-
-static bool test_multiple_enqueue_dequeue()
-{
- /* Enqueue 3 tasks at different priorities, dequeue all */
- TaskControlBlock* t0 = make_test_tcb(0, "test_m0", 5, 0);
- TaskControlBlock* t1 = make_test_tcb(1, "test_m1", 10, 0);
- TaskControlBlock* t2 = make_test_tcb(2, "test_m2", 20, 0);
-
- sched_enqueue(t0);
- sched_enqueue(t1);
- sched_enqueue(t2);
-
- sched_dequeue(t0);
- sched_dequeue(t1);
- sched_dequeue(t2);
-
- return t0->run_link.is_empty() &&
- t1->run_link.is_empty() &&
- t2->run_link.is_empty();
-}
-
-static bool test_current_task_name()
-{
- TaskControlBlock* curr = current_task();
- if (!curr || !curr->name) return false;
-
- /* BSP boot TCB should be named "kernel_main" */
- const char* expected = "kernel_main";
- const char* actual = curr->name;
- while (*expected && *actual) {
- if (*expected != *actual) return false;
- expected++;
- actual++;
- }
- return *expected == *actual;
-}
-
-/* ========================================================================
- * Test runner
- * ======================================================================== */
-
-void selftest_sched()
-{
- st_begin("sched: current_task is valid after init");
- ST_ASSERT(test_current_task());
- st_pass();
-
- st_begin("sched: current_task name is kernel_main");
- ST_ASSERT(test_current_task_name());
- st_pass();
-
- st_begin("sched: priority_to_timeslice ordering");
- ST_ASSERT(test_priority_to_timeslice());
- st_pass();
-
- st_begin("sched: priority_to_timeslice bounds");
- ST_ASSERT(test_priority_to_timeslice_bounds());
- st_pass();
-
- st_begin("sched: enqueue/dequeue round-trip");
- ST_ASSERT(test_enqueue_dequeue());
- st_pass();
-
- st_begin("sched: multiple enqueue/dequeue");
- ST_ASSERT(test_multiple_enqueue_dequeue());
- st_pass();
-
- st_begin("sched: o1 pick_next priority");
- ST_ASSERT(test_o1_pick_next_priority());
- st_pass();
-
- st_begin("sched: o1 FIFO order");
- ST_ASSERT(test_o1_fifo_order());
- st_pass();
-
- st_begin("sched: o1 nr_running accounting");
- ST_ASSERT(test_o1_nr_running());
- st_pass();
-
- st_begin("sched: o1 pick_next empty");
- ST_ASSERT(test_o1_pick_next_empty());
- st_pass();
-}
-
diff --git a/test/test_signal.cc b/test/test_signal.cc
deleted file mode 100644
index 360b0e6..0000000
--- a/test/test_signal.cc
+++ /dev/null
@@ -1,193 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-/*
- * test_signal.cc — Self-tests for signal infrastructure
- *
- * Tests the kernel-internal signal APIs directly (not via syscalls,
- * since syscalls use copy_to_user which rejects kernel-space buffers).
- */
-
-#include <lume/selftest.h>
-#include <lume/signal.h>
-#include <lume/signal_types.h>
-#include <lume/signal_nr.h>
-#include <lume/task.h>
-#include <lume/errno.h>
-#include <lume/new.h>
-
-/* From signal.cc */
-extern int send_signal(int sig, kernel_siginfo* info, TaskControlBlock* task);
-
-/* Drain all queued sigqueue entries from a task's pending list */
-static void flush_pending(TaskControlBlock* task)
-{
- task->pending.signal = sigset_t{};
- list_node* head = &task->pending.list;
- while (head->next != head) {
- list_node* node = head->next;
- head->next = node->next;
- node->next->prev = head;
- auto* q = list_entry<sigqueue, &sigqueue::list>(node);
- delete q;
- }
-}
-
-void selftest_signal()
-{
- TaskControlBlock* curr = current_task();
-
- /* Ensure sighand exists for this task */
- if (!curr->sighand) {
- curr->sighand = new sighand_struct;
- curr->sighand->init();
- }
-
- /* Ensure pending list is initialized */
- if (curr->pending.list.next == nullptr) {
- curr->pending.list.next = &curr->pending.list;
- curr->pending.list.prev = &curr->pending.list;
- curr->pending.signal = sigset_t{};
- }
-
- /* ---- send_signal queues a signal ---- */
- st_begin("signal: send_signal queues to pending");
- {
- curr->pending.signal = sigset_t{};
-
- kernel_siginfo info{};
- info.si_signo = SIGUSR1;
- info.si_code = 0;
-
- int ret = send_signal(SIGUSR1, &info, curr);
- ST_ASSERT_EQ(ret, 0);
- ST_ASSERT(curr->pending.signal.has(SIGUSR1));
-
- flush_pending(curr);
- }
- st_pass();
-
- /* ---- send_signal: invalid signal returns -EINVAL ---- */
- st_begin("signal: send_signal invalid sig returns -EINVAL");
- {
- int ret = send_signal(0, nullptr, curr);
- ST_ASSERT_EQ(ret, -EINVAL);
-
- ret = send_signal(_NSIG + 1, nullptr, curr);
- ST_ASSERT_EQ(ret, -EINVAL);
- }
- st_pass();
-
- /* ---- send_signal: null task returns -ESRCH ---- */
- st_begin("signal: send_signal null task returns -ESRCH");
- {
- int ret = send_signal(SIGUSR1, nullptr, nullptr);
- ST_ASSERT_EQ(ret, -ESRCH);
- }
- st_pass();
-
- /* ---- duplicate standard signal is coalesced ---- */
- st_begin("signal: duplicate standard signal coalesced");
- {
- curr->pending.signal = sigset_t{};
-
- send_signal(SIGUSR1, nullptr, curr);
- ST_ASSERT(curr->pending.signal.has(SIGUSR1));
-
- int ret = send_signal(SIGUSR1, nullptr, curr);
- ST_ASSERT_EQ(ret, 0);
-
- flush_pending(curr);
- }
- st_pass();
-
- /* ---- sigset_t: add/del/has ---- */
- st_begin("signal: sigset_t add/del/has");
- {
- sigset_t s{};
- ST_ASSERT(!s.has(SIGUSR1));
- s.add(SIGUSR1);
- ST_ASSERT(s.has(SIGUSR1));
- s.del(SIGUSR1);
- ST_ASSERT(!s.has(SIGUSR1));
- }
- st_pass();
-
- /* ---- sigset_t: bitwise operators ---- */
- st_begin("signal: sigset_t bitwise OR and AND");
- {
- sigset_t a{};
- a.add(SIGUSR1);
- sigset_t b{};
- b.add(SIGUSR2);
-
- sigset_t c = a | b;
- ST_ASSERT(c.has(SIGUSR1));
- ST_ASSERT(c.has(SIGUSR2));
-
- sigset_t d = c & a;
- ST_ASSERT(d.has(SIGUSR1));
- ST_ASSERT(!d.has(SIGUSR2));
- }
- st_pass();
-
- /* ---- blocked mask: direct manipulation ---- */
- st_begin("signal: blocked mask direct manipulation");
- {
- sigset_t saved = curr->blocked;
- curr->blocked = sigset_t{};
-
- curr->blocked.add(SIGUSR1);
- curr->blocked.add(SIGUSR2);
- ST_ASSERT(curr->blocked.has(SIGUSR1));
- ST_ASSERT(curr->blocked.has(SIGUSR2));
-
- curr->blocked.del(SIGUSR1);
- ST_ASSERT(!curr->blocked.has(SIGUSR1));
- ST_ASSERT(curr->blocked.has(SIGUSR2));
-
- curr->blocked = saved;
- }
- st_pass();
-
- /* ---- sighand action table ---- */
- st_begin("signal: sighand action table defaults to SIG_DFL");
- {
- /* All handlers should default to SIG_DFL (0) */
- kernel_sigaction* ka = &curr->sighand->action[SIGUSR1 - 1];
- ST_ASSERT_EQ(ka->sa_handler, (uint64)SIG_DFL);
-
- /* Set a handler and verify */
- ka->sa_handler = 0x12345678ULL;
- ST_ASSERT_EQ(ka->sa_handler, (uint64)0x12345678ULL);
-
- /* Reset */
- ka->sa_handler = SIG_DFL;
- }
- st_pass();
-
- /* ---- sig_kernel_only rejects SIGKILL/SIGSTOP handlers ---- */
- st_begin("signal: SIGKILL/SIGSTOP are kernel-only");
- {
- ST_ASSERT(sig_kernel_only(SIGKILL));
- ST_ASSERT(sig_kernel_only(SIGSTOP));
- ST_ASSERT(!sig_kernel_only(SIGUSR1));
- ST_ASSERT(!sig_kernel_only(SIGINT));
- }
- st_pass();
-
- /* ---- sig_default_action correctness ---- */
- st_begin("signal: sig_default_action returns correct actions");
- {
- ST_ASSERT_EQ((int)sig_default_action(SIGCHLD), (int)SIG_ACT_IGN);
- ST_ASSERT_EQ((int)sig_default_action(SIGKILL), (int)SIG_ACT_TERM);
- ST_ASSERT_EQ((int)sig_default_action(SIGSTOP), (int)SIG_ACT_STOP);
- ST_ASSERT_EQ((int)sig_default_action(SIGCONT), (int)SIG_ACT_CONT);
- ST_ASSERT_EQ((int)sig_default_action(SIGSEGV), (int)SIG_ACT_CORE);
- }
- st_pass();
-
- /* Clean up */
- flush_pending(curr);
- curr->blocked = sigset_t{};
-}
diff --git a/test/test_slab.cc b/test/test_slab.cc
deleted file mode 100644
index 8a448b4..0000000
--- a/test/test_slab.cc
+++ /dev/null
@@ -1,255 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-/*
- * Boot Self-Test: Slab Allocator (kmalloc/kfree)
- *
- * Tests:
- * 1. Small allocation (8 bytes) and free
- * 2. Multiple size classes
- * 3. No aliasing between consecutive allocs
- * 4. Alignment check (minimum 8-byte aligned)
- * 5. Bulk alloc/free stress
- * 6. operator new / operator delete
- * 7. Large allocation (> 2048, falls back to PMM)
- * 8. Zero-size and null-free edge cases
- */
-
-#include <lume/selftest.h>
-#include <lume/new.h>
-#include <lume/pmm.h>
-#include <lume/addr.h>
-
-/* Forward declarations — defined in slab.cc */
-void* kmalloc(uint32 size);
-void kfree(void* ptr);
-
-void selftest_slab()
-{
- st_begin("slab: small alloc (8 bytes)");
- {
- void* p = kmalloc(8);
- ST_ASSERT(p != nullptr);
- // Write and read back to verify the memory is usable
- *static_cast<uint64*>(p) = 0xDEADBEEFCAFEBABEULL;
- ST_ASSERT_EQ(*static_cast<uint64*>(p), 0xDEADBEEFCAFEBABEULL);
- kfree(p);
- }
- st_pass();
-
- st_begin("slab: multiple size classes");
- {
- uint32 sizes[] = {8, 16, 32, 64, 128, 256, 512, 1024, 2048};
- void* ptrs[9];
- for (int i = 0; i < 9; i++) {
- ptrs[i] = kmalloc(sizes[i]);
- ST_ASSERT(ptrs[i] != nullptr);
- // Write a marker
- *static_cast<uint8*>(ptrs[i]) = static_cast<uint8>(i);
- }
- // Verify markers (no cross-contamination)
- for (int i = 0; i < 9; i++) {
- ST_ASSERT_EQ(*static_cast<uint8*>(ptrs[i]), static_cast<uint8>(i));
- }
- for (int i = 0; i < 9; i++) {
- kfree(ptrs[i]);
- }
- }
- st_pass();
-
- st_begin("slab: no aliasing");
- {
- void* a = kmalloc(32);
- void* b = kmalloc(32);
- ST_ASSERT(a != nullptr);
- ST_ASSERT(b != nullptr);
- ST_ASSERT_NE(a, b);
- kfree(b);
- kfree(a);
- }
- st_pass();
-
- st_begin("slab: alignment (8-byte minimum)");
- {
- for (int i = 0; i < 16; i++) {
- void* p = kmalloc(7); // odd size, should still be 8-aligned
- ST_ASSERT(p != nullptr);
- ST_ASSERT_EQ(reinterpret_cast<uint64>(p) % 8, 0ULL);
- kfree(p);
- }
- }
- st_pass();
-
- st_begin("slab: bulk alloc/free stress (128 x 64B)");
- {
- constexpr int N = 128;
- void* ptrs[N];
- for (int i = 0; i < N; i++) {
- ptrs[i] = kmalloc(64);
- ST_ASSERT(ptrs[i] != nullptr);
- }
- // Free in reverse order
- for (int i = N - 1; i >= 0; i--) {
- kfree(ptrs[i]);
- }
- }
- st_pass();
-
- st_begin("slab: operator new/delete");
- {
- struct TestObj {
- uint64 a;
- uint64 b;
- };
- TestObj* obj = new TestObj{42, 99};
- ST_ASSERT(obj != nullptr);
- ST_ASSERT_EQ(obj->a, 42ULL);
- ST_ASSERT_EQ(obj->b, 99ULL);
- delete obj;
- }
- st_pass();
-
- st_begin("slab: large alloc (4096 bytes, PMM fallback)");
- {
- void* p = kmalloc(4096);
- ST_ASSERT(p != nullptr);
- ST_ASSERT_EQ(reinterpret_cast<uint64>(p) % kPageSize, 0ULL);
- kfree(p);
- }
- st_pass();
-
- st_begin("slab: edge cases (zero size, null free)");
- {
- void* p = kmalloc(0);
- ST_ASSERT(p == nullptr);
- kfree(nullptr); // should not crash
- }
- st_pass();
- st_begin("slab: LIFO freelist reallocation (same address)");
- {
- void* a = kmalloc(32);
- kfree(a);
- void* b = kmalloc(32);
- ST_ASSERT_EQ(a, b);
- kfree(b);
- }
- st_pass();
-
- st_begin("slab: boundary conditions (2048 bytes size class)");
- {
- // 2048 bytes should still use slab (2 objects per page)
- // We allocate 3 to guarantee at least two fall in the exact same slab page
- void* p1 = kmalloc(2048);
- void* p2 = kmalloc(2048);
- void* p3 = kmalloc(2048);
-
- auto diff = [](void* a, void* b) -> uint64 {
- uint64 ua = reinterpret_cast<uint64>(a);
- uint64 ub = reinterpret_cast<uint64>(b);
- return ua > ub ? ua - ub : ub - ua;
- };
-
- // At least one adjacent pair MUST be exactly separated by 2048 bytes
- bool found_2048 = (diff(p1, p2) == 2048ULL) || (diff(p2, p3) == 2048ULL);
- ST_ASSERT(found_2048);
-
- kfree(p1);
- kfree(p2);
- kfree(p3);
- }
- st_pass();
-
- st_begin("slab: cross-page allocation (> 1 page equivalent objects)");
- {
- // Allocating more than one page worth of 32-byte objects (4096 / 32 = 128)
- constexpr int NUM = 150;
- void* ptrs[NUM];
- for (int i=0; i<NUM; i++) {
- ptrs[i] = kmalloc(32);
- ST_ASSERT(ptrs[i] != nullptr);
- }
- for (int i=0; i<NUM; i++) {
- kfree(ptrs[i]);
- }
- }
- st_pass();
-
- st_begin("slab: empty page reclamation (verifiable by page availability)");
- {
- // Allocate 128-byte objects until we hit the start of a completely new page.
- void* base_ptr = nullptr;
- void* drain[64];
- int drain_count = 0;
-
- while (true) {
- void* p = kmalloc(128);
- // Because 128 divides 4096 evenly, a new page starts at an exact multiple of kPageSize.
- if (reinterpret_cast<uint64>(p) % kPageSize == 0) {
- base_ptr = p;
- break;
- }
- drain[drain_count++] = p;
- }
-
- // base_ptr is the very first object in a fresh slab page!
- // The page holds exactly 32 objects (4096 / 128 = 32). We already hold 1 (base_ptr).
- void* ptrs[32];
- ptrs[0] = base_ptr;
- for (int i = 1; i < 32; i++) {
- ptrs[i] = kmalloc(128);
- }
-
- // At this point, we completely own all 32 objects of this slab page.
- uint64 page_pa = va_to_pa(virt_addr(reinterpret_cast<uint64>(base_ptr))).raw;
- Page* page = pa_to_page(phys_addr(page_pa));
-
- ST_ASSERT(page->type == PageType::Slab);
- ST_ASSERT(page->slab.obj_count == 32);
-
- // Free all items in this isolated slab page
- for (int i = 0; i < 32; i++) {
- kfree(ptrs[i]);
- }
-
- // The page MUST have been returned to the PMM (either global Free or Per-CPU Cached).
- ST_ASSERT(page->type == PageType::Free || page->type == PageType::PcpCached);
-
- // Clean up the drain array
- for (int i = 0; i < drain_count; i++) {
- kfree(drain[i]);
- }
- }
- st_pass();
-
- st_begin("slab: continuous operator new/delete memory leak loop");
- {
- struct BigObj { uint8 data[512]; };
- BigObj* ptr = nullptr;
- // 10K allocations to ensure no memory leak causes OOM
- for (int i = 0; i < 10000; i++) {
- ptr = new BigObj;
- ST_ASSERT(ptr != nullptr);
- delete ptr;
- }
- }
- st_pass();
-
- st_begin("slab: allocation pressure (mixed sizes)");
- {
- constexpr int MAX_ITEMS = 100;
- void* ptrs[MAX_ITEMS];
- uint32 sizes[MAX_ITEMS];
-
- for (int i = 0; i < MAX_ITEMS; i++) {
- sizes[i] = (i * 17) % 2000 + 8; // Pseudo-random sizes between 8 and 2008
- ptrs[i] = kmalloc(sizes[i]);
- ST_ASSERT(ptrs[i] != nullptr);
- ST_ASSERT(reinterpret_cast<uintptr>(ptrs[i]) % 8 == 0);
- }
-
- for (int i = 0; i < MAX_ITEMS; i++) {
- kfree(ptrs[i]);
- }
- }
- st_pass();
-}
diff --git a/test/test_smp.cc b/test/test_smp.cc
deleted file mode 100644
index f934526..0000000
--- a/test/test_smp.cc
+++ /dev/null
@@ -1,136 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-/*
- * test_smp.cc — Post-SMP boot tests
- *
- * These tests run after all APs are online. They verify:
- * 1. IPI delivery — BSP sends IPI to each AP, AP increments counter.
- * 2. Task migration — a task moves from one runqueue to another.
- * 3. TLB shootdown IPI — cross-CPU flush.
- *
- * Called from the init thread (PID 1) context, NOT from the pre-SMP
- * self-test suite.
- */
-
-#include <lume/selftest.h>
-#include <lume/smp.h>
-#include <lume/sched.h>
-#include <lume/atomic.h>
-#include <lume/config.h>
-#include <arch/cpu.h>
-
-/* Shared counter incremented by APs when they receive an IPI */
-static lume::atomic<uint32> g_ipi_ack_count{0};
-
-/* ------------------------------------------------------------------ */
-/* IPI delivery test */
-/* ------------------------------------------------------------------ */
-
-void selftest_ipi_delivery()
-{
- st_begin("smp: IPI delivery to all APs");
-
- /* Reset counter */
- g_ipi_ack_count.store(0, __ATOMIC_RELAXED);
-
- uint32 bsp = static_cast<uint32>(arch::cpu::id());
- uint32 num_cpus = 4; /* TODO: derive from FDT */
-
- /* Build mask of all APs */
- uint64 ap_mask = 0;
- uint32 expected = 0;
- for (uint32 i = 0; i < num_cpus; i++) {
- if (i == bsp) continue;
- ap_mask |= (1ULL << i);
- expected++;
- }
-
- /* Send reschedule IPI to all APs */
- smp_send_ipi(ap_mask, IPI_RESCHEDULE);
-
- /* Wait a short while for APs to process the interrupt.
- * APs are in wfi (idle loop), so they'll wake immediately
- * on interrupt delivery. We just need to give them time
- * to run the handler. */
- for (volatile int i = 0; i < 100000; i++) {
- /* spin-wait */
- }
-
- /* We can't directly check that IPI was received because
- * IPI_RESCHEDULE just sets need_resched — no ack.
- * Instead verify no crash occurred and the IPI infrastructure
- * didn't deadlock. This is a liveness test. */
-
- st_pass();
-}
-
-/* ------------------------------------------------------------------ */
-/* IPI halt test (send to self, skip) */
-/* ------------------------------------------------------------------ */
-
-void selftest_ipi_self_skip()
-{
- st_begin("smp: IPI reason bitmask accumulation");
-
- uint32 cpu = static_cast<uint32>(arch::cpu::id());
-
- /* Manually set multiple reasons atomically */
- g_ipi_data[cpu].pending.store(0, __ATOMIC_RELAXED);
- g_ipi_data[cpu].pending.fetch_or(IPI_RESCHEDULE, __ATOMIC_RELEASE);
- g_ipi_data[cpu].pending.fetch_or(IPI_TLB_SHOOTDOWN, __ATOMIC_RELEASE);
-
- uint32 val = g_ipi_data[cpu].pending.load(__ATOMIC_ACQUIRE);
- ST_ASSERT_EQ(val, IPI_RESCHEDULE | IPI_TLB_SHOOTDOWN);
-
- /* Clean up */
- g_ipi_data[cpu].pending.store(0, __ATOMIC_RELAXED);
-
- st_pass();
-}
-
-/* ------------------------------------------------------------------ */
-/* TLB shootdown IPI test */
-/* ------------------------------------------------------------------ */
-
-void selftest_tlb_shootdown()
-{
- st_begin("smp: TLB shootdown IPI (cross-CPU flush)");
-
- uint32 bsp = static_cast<uint32>(arch::cpu::id());
- uint32 num_cpus = 4;
-
- /* Build AP mask */
- uint64 ap_mask = 0;
- for (uint32 i = 0; i < num_cpus; i++) {
- if (i == bsp) continue;
- ap_mask |= (1ULL << i);
- }
-
- /* Set TLB range on each AP's IpiData */
- for (uint32 i = 0; i < num_cpus; i++) {
- if (i == bsp) continue;
- g_ipi_data[i].tlb_start = 0;
- g_ipi_data[i].tlb_end = 0; /* 0,0 = full flush */
- }
-
- /* Send TLB shootdown */
- smp_send_ipi(ap_mask, IPI_TLB_SHOOTDOWN);
-
- /* Wait for APs to process */
- for (volatile int i = 0; i < 100000; i++) {}
-
- /* If we got here without panic, TLB shootdown succeeded */
- st_pass();
-}
-
-/* ------------------------------------------------------------------ */
-/* Public entry point */
-/* ------------------------------------------------------------------ */
-
-void selftest_smp()
-{
- selftest_ipi_delivery();
- selftest_ipi_self_skip();
- selftest_tlb_shootdown();
-}
diff --git a/test/test_spinlock.cc b/test/test_spinlock.cc
deleted file mode 100644
index be794a0..0000000
--- a/test/test_spinlock.cc
+++ /dev/null
@@ -1,135 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-/*
- * Boot Self-Test: Spinlock
- *
- * Tests:
- * 1. Basic acquire/release
- * 2. LockGuard RAII acquire/release
- * 3. Nested locking (two different locks)
- * 4. Interrupt state preservation across lock/unlock
- * 5. is_held_by_current_cpu correctness
- *
- * Note: We cannot test multi-core contention in BSP-only self-test.
- * That requires AP participation (future Phase 2 test).
- */
-
-#include <lume/selftest.h>
-#include <kernel/sync/spinlock.h>
-#include <kernel/sync/cpu_state.h>
-#include <arch/cpu.h>
-
-void selftest_spinlock()
-{
- st_begin("spinlock: basic acquire/release");
- {
- Spinlock lock;
- lock.init("test_basic");
-
- ST_ASSERT(!lock.is_held_by_current_cpu());
-
- lock.acquire();
- ST_ASSERT(lock.is_held_by_current_cpu());
-
- lock.release();
- ST_ASSERT(!lock.is_held_by_current_cpu());
- }
- st_pass();
-
- st_begin("spinlock: LockGuard RAII");
- {
- Spinlock lock;
- lock.init("test_guard");
-
- {
- LockGuard guard(lock);
- ST_ASSERT(lock.is_held_by_current_cpu());
- }
- // After scope exit, lock should be released
- ST_ASSERT(!lock.is_held_by_current_cpu());
- }
- st_pass();
-
- st_begin("spinlock: nested locking (two locks)");
- {
- Spinlock lock_a, lock_b;
- lock_a.init("test_nest_a");
- lock_b.init("test_nest_b");
-
- lock_a.acquire();
- ST_ASSERT(lock_a.is_held_by_current_cpu());
-
- lock_b.acquire();
- ST_ASSERT(lock_b.is_held_by_current_cpu());
-
- lock_b.release();
- ST_ASSERT(!lock_b.is_held_by_current_cpu());
- ST_ASSERT(lock_a.is_held_by_current_cpu()); // A still held
-
- lock_a.release();
- ST_ASSERT(!lock_a.is_held_by_current_cpu());
- }
- st_pass();
-
- st_begin("spinlock: interrupt state preservation");
- {
- Spinlock lock;
- lock.init("test_intr");
-
- // Interrupts are currently off (BSP init period), but let's
- // verify the lock doesn't corrupt the state.
- bool before = arch::cpu::intr_enabled();
-
- lock.acquire();
- // Inside lock, interrupts must be disabled
- ST_ASSERT(!arch::cpu::intr_enabled());
- lock.release();
-
- // After release, interrupt state should be restored
- ST_ASSERT_EQ(arch::cpu::intr_enabled(), before);
- }
- st_pass();
- st_begin("spinlock: deep nesting tracking (> 2)");
- {
- Spinlock l1, l2, l3;
- l1.init("l1");
- l2.init("l2");
- l3.init("l3");
-
- bool before = arch::cpu::intr_enabled();
-
- l1.acquire();
- l2.acquire();
- l3.acquire();
-
- ST_ASSERT(!arch::cpu::intr_enabled());
-
- l3.release();
- ST_ASSERT(!arch::cpu::intr_enabled());
-
- l2.release();
- ST_ASSERT(!arch::cpu::intr_enabled());
-
- l1.release();
- ST_ASSERT_EQ(arch::cpu::intr_enabled(), before);
- }
- st_pass();
-
- st_begin("spinlock: interrupt leak detection logic (lock_depth metadata)");
- {
- Spinlock l;
- l.init("l");
-
- auto& state = sync::g_cpu_sync_states[arch::cpu::id()];
- int initial_depth = state.lock_depth;
-
- l.acquire();
- // The lock depth should strictly increase
- ST_ASSERT_EQ(state.lock_depth, initial_depth + 1);
- l.release();
-
- ST_ASSERT_EQ(state.lock_depth, initial_depth);
- }
- st_pass();
-}
diff --git a/test/test_sys_fs_extra.cc b/test/test_sys_fs_extra.cc
deleted file mode 100644
index 705ce63..0000000
--- a/test/test_sys_fs_extra.cc
+++ /dev/null
@@ -1,238 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-/*
- * test_sys_fs_extra.cc — Self-tests for additional filesystem syscalls
- *
- * Tests: dup3, chdir/getcwd, writev, ioctl (stub), unlinkat (stub).
- * Complements test_vfs_io.cc which covers the core read/write/open/close.
- */
-
-#include <lume/selftest.h>
-#include <lume/errno.h>
-#include <lume/file.h>
-#include <lume/fs.h>
-#include <lume/fs_types.h>
-#include <lume/task.h>
-
-/* Syscall entry points */
-int64 sys_openat(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_close(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_read(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_write(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_dup3(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_chdir(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_getcwd(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_writev(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_ioctl(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_unlinkat(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_mkdirat(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_lseek(uint64, uint64, uint64, uint64, uint64, uint64);
-
-/* ================================================================== */
-/* Helpers */
-/* ================================================================== */
-
-static int64 open_dev(const char* path, int flags)
-{
- return sys_openat(
- static_cast<uint64>(-100),
- reinterpret_cast<uint64>(path),
- static_cast<uint64>(flags), 0, 0, 0);
-}
-
-static int64 do_close(int64 fd)
-{
- return sys_close(static_cast<uint64>(fd), 0, 0, 0, 0, 0);
-}
-
-static int64 do_read(int64 fd, void* buf, uint64 count)
-{
- return sys_read(static_cast<uint64>(fd),
- reinterpret_cast<uint64>(buf), count, 0, 0, 0);
-}
-
-
-/* iovec must match sys_fs.cc definition */
-struct test_iovec {
- void* iov_base;
- uint64 iov_len;
-};
-
-/* ================================================================== */
-/* Tests */
-/* ================================================================== */
-
-void selftest_sys_fs_extra()
-{
- /* ---- dup3: duplicate fd to specific target ---- */
- st_begin("sys_fs: dup3 duplicates to specific fd");
- {
- int64 fd = open_dev("/dev/null", O_RDONLY);
- ST_ASSERT(fd >= 0);
-
- /* dup3 to fd 50 */
- int64 fd2 = sys_dup3(static_cast<uint64>(fd), 50, 0, 0, 0, 0);
- ST_ASSERT_EQ(fd2, (int64)50);
-
- /* Both should work */
- char buf[4];
- int64 r = do_read(fd2, buf, 4);
- ST_ASSERT_EQ(r, (int64)0); /* /dev/null → EOF */
-
- do_close(fd);
- do_close(fd2);
- }
- st_pass();
-
- /* ---- dup3: same fd returns -EINVAL ---- */
- st_begin("sys_fs: dup3 same fd returns -EINVAL");
- {
- int64 fd = open_dev("/dev/null", O_RDONLY);
- ST_ASSERT(fd >= 0);
-
- int64 ret = sys_dup3(static_cast<uint64>(fd),
- static_cast<uint64>(fd), 0, 0, 0, 0);
- ST_ASSERT_EQ(ret, (int64)-EINVAL);
-
- do_close(fd);
- }
- st_pass();
-
- /* ---- chdir to /dev, getcwd returns /dev ---- */
- st_begin("sys_fs: chdir then getcwd returns new dir");
- {
- /* Save cwd */
- char saved_cwd[64] = {};
- sys_getcwd(reinterpret_cast<uint64>(saved_cwd), 64, 0, 0, 0, 0);
-
- /* chdir to /dev */
- int64 ret = sys_chdir(
- reinterpret_cast<uint64>("/dev"), 0, 0, 0, 0, 0);
- ST_ASSERT_EQ(ret, (int64)0);
-
- /* getcwd should return /dev */
- char buf[64] = {};
- int64 len = sys_getcwd(
- reinterpret_cast<uint64>(buf), 64, 0, 0, 0, 0);
- ST_ASSERT(len > 0);
- ST_ASSERT_EQ(buf[0], '/');
- ST_ASSERT_EQ(buf[1], 'd');
- ST_ASSERT_EQ(buf[2], 'e');
- ST_ASSERT_EQ(buf[3], 'v');
-
- /* Restore cwd to / */
- sys_chdir(reinterpret_cast<uint64>("/"), 0, 0, 0, 0, 0);
- }
- st_pass();
-
- /* ---- chdir to nonexistent returns -ENOENT ---- */
- st_begin("sys_fs: chdir nonexistent returns -ENOENT");
- {
- int64 ret = sys_chdir(
- reinterpret_cast<uint64>("/no_such_dir"), 0, 0, 0, 0, 0);
- ST_ASSERT_EQ(ret, (int64)-ENOENT);
- }
- st_pass();
-
- /* ---- chdir to file returns -ENOTDIR ---- */
- st_begin("sys_fs: chdir to file returns -ENOTDIR");
- {
- int64 ret = sys_chdir(
- reinterpret_cast<uint64>("/dev/null"), 0, 0, 0, 0, 0);
- ST_ASSERT_EQ(ret, (int64)-ENOTDIR);
- }
- st_pass();
-
- /* ---- writev: scatter write to file ---- */
- st_begin("sys_fs: writev scatter write");
- {
- /* Create a file, write with writev, read back */
- int64 fd = sys_openat(
- static_cast<uint64>(-100),
- reinterpret_cast<uint64>("/writev_test"),
- O_CREAT | O_RDWR, 0644, 0, 0);
- ST_ASSERT(fd >= 0);
-
- char a[] = "AAA";
- char b[] = "BBB";
- test_iovec iov[2];
- iov[0].iov_base = a;
- iov[0].iov_len = 3;
- iov[1].iov_base = b;
- iov[1].iov_len = 3;
-
- int64 w = sys_writev(
- static_cast<uint64>(fd),
- reinterpret_cast<uint64>(iov), 2, 0, 0, 0);
- ST_ASSERT_EQ(w, (int64)6);
-
- /* Seek back to start and read */
- sys_lseek(static_cast<uint64>(fd), 0, 0 /* SEEK_SET */, 0, 0, 0);
-
- char buf[8] = {};
- int64 r = do_read(fd, buf, 8);
- ST_ASSERT_EQ(r, (int64)6);
- ST_ASSERT_EQ(buf[0], 'A');
- ST_ASSERT_EQ(buf[3], 'B');
-
- do_close(fd);
- }
- st_pass();
-
- /* ---- writev: bad fd returns -EBADF ---- */
- st_begin("sys_fs: writev bad fd returns -EBADF");
- {
- test_iovec iov[1];
- char dummy[] = "x";
- iov[0].iov_base = dummy;
- iov[0].iov_len = 1;
-
- int64 ret = sys_writev(999,
- reinterpret_cast<uint64>(iov), 1, 0, 0, 0);
- ST_ASSERT_EQ(ret, (int64)-EBADF);
- }
- st_pass();
-
- /* ---- ioctl stub returns -ENOTTY ---- */
- st_begin("sys_fs: ioctl stub returns -ENOTTY");
- {
- int64 fd = open_dev("/dev/null", O_RDONLY);
- ST_ASSERT(fd >= 0);
-
- int64 ret = sys_ioctl(static_cast<uint64>(fd), 0, 0, 0, 0, 0);
- ST_ASSERT_EQ(ret, (int64)-ENOTTY);
-
- do_close(fd);
- }
- st_pass();
-
- /* ---- unlinkat stub returns -ENOSYS ---- */
- st_begin("sys_fs: unlinkat stub returns -ENOSYS");
- {
- int64 ret = sys_unlinkat(
- static_cast<uint64>(-100),
- reinterpret_cast<uint64>("/some_file"),
- 0, 0, 0, 0);
- ST_ASSERT_EQ(ret, (int64)-ENOSYS);
- }
- st_pass();
-
- /* ---- getcwd: null buf returns -EINVAL ---- */
- st_begin("sys_fs: getcwd null buf returns -EINVAL");
- {
- int64 ret = sys_getcwd(0, 64, 0, 0, 0, 0);
- ST_ASSERT_EQ(ret, (int64)-EINVAL);
- }
- st_pass();
-
- /* ---- getcwd: zero size returns -EINVAL ---- */
- st_begin("sys_fs: getcwd zero size returns -EINVAL");
- {
- char buf[4];
- int64 ret = sys_getcwd(
- reinterpret_cast<uint64>(buf), 0, 0, 0, 0, 0);
- ST_ASSERT_EQ(ret, (int64)-EINVAL);
- }
- st_pass();
-}
diff --git a/test/test_sys_mm.cc b/test/test_sys_mm.cc
deleted file mode 100644
index 2c1a7dc..0000000
--- a/test/test_sys_mm.cc
+++ /dev/null
@@ -1,178 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-#include <lume/selftest.h>
-#include <lume/task.h>
-#include <lume/vmm.h>
-#include <lume/mman.h>
-#include <lume/errno.h>
-
-/* sys_mm.cc APIs */
-int64 sys_brk(uint64 new_brk, uint64, uint64, uint64, uint64, uint64);
-int64 sys_mmap(uint64 addr, uint64 length, uint64 prot,
- uint64 flags, uint64 fd, uint64 offset);
-int64 sys_munmap(uint64 addr, uint64 length, uint64, uint64, uint64, uint64);
-int64 sys_mprotect(uint64 addr, uint64 len, uint64 prot,
- uint64, uint64, uint64);
-
-void selftest_sys_mm()
-{
- st_begin("sys_mm: mmap invalid arguments");
- {
- TaskControlBlock* curr = current_task();
- VmSpace* old_mm = curr->mm;
-
- VmSpace* space = VmSpace::create();
- ST_ASSERT(space != nullptr);
- curr->mm = space;
-
- ST_ASSERT_EQ(sys_mmap(0, 0, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0), -EINVAL);
- ST_ASSERT_EQ(sys_mmap(0, 0x1000, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, 0, 1), -EINVAL);
- ST_ASSERT_EQ(sys_mmap(0, 0x1000, PROT_READ, 0, 0, 0), -EINVAL);
- ST_ASSERT_EQ(sys_mmap(0, 0x1000, PROT_READ, MAP_PRIVATE | MAP_SHARED, 0, 0), -EINVAL);
- ST_ASSERT_EQ(sys_mmap(0x123, 0x1000, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, 0, 0), -EINVAL);
-
- curr->mm = old_mm;
- space->destroy();
- }
- st_pass();
-
- st_begin("sys_mm: mmap/munmap/mprotect basic flow");
- {
- TaskControlBlock* curr = current_task();
- VmSpace* old_mm = curr->mm;
-
- VmSpace* space = VmSpace::create();
- ST_ASSERT(space != nullptr);
- curr->mm = space;
-
- int64 addr = sys_mmap(0, 0x3000, PROT_READ | PROT_WRITE,
- MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
- ST_ASSERT(addr > 0);
-
- ST_ASSERT_EQ(vmm_handle_page_fault(space, addr, VmFaultCause::Read), 0);
-
- int64 mp = sys_mprotect(addr + kPageSize, kPageSize, PROT_READ, 0, 0, 0);
- ST_ASSERT_EQ(mp, 0);
- ST_ASSERT_EQ(vmm_handle_page_fault(space, addr + kPageSize, VmFaultCause::Write), -EFAULT);
-
- int64 mu = sys_munmap(addr, 0x3000, 0, 0, 0, 0);
- ST_ASSERT_EQ(mu, 0);
- ST_ASSERT_EQ(vmm_handle_page_fault(space, addr, VmFaultCause::Read), -EFAULT);
-
- curr->mm = old_mm;
- space->destroy();
- }
- st_pass();
-
- st_begin("sys_mm: MAP_FIXED replaces existing mapping");
- {
- TaskControlBlock* curr = current_task();
- VmSpace* old_mm = curr->mm;
-
- VmSpace* space = VmSpace::create();
- ST_ASSERT(space != nullptr);
- curr->mm = space;
-
- uint64 base = 0x600000;
- ST_ASSERT_EQ(sys_mmap(base, 0x2000, PROT_READ,
- MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, 0, 0),
- static_cast<int64>(base));
-
- ST_ASSERT_EQ(sys_mmap(base, 0x1000, PROT_READ | PROT_WRITE,
- MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, 0, 0),
- static_cast<int64>(base));
-
- ST_ASSERT_EQ(vmm_handle_page_fault(space, base, VmFaultCause::Read), 0);
- ST_ASSERT_EQ(vmm_handle_page_fault(space, base, VmFaultCause::Write), 0);
-
- curr->mm = old_mm;
- space->destroy();
- }
- st_pass();
-
- st_begin("sys_mm: mmap hint fallback on overlap");
- {
- TaskControlBlock* curr = current_task();
- VmSpace* old_mm = curr->mm;
-
- VmSpace* space = VmSpace::create();
- ST_ASSERT(space != nullptr);
- curr->mm = space;
-
- uint64 base = 0x700000;
- ST_ASSERT_EQ(sys_mmap(base, 0x2000, PROT_READ,
- MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, 0, 0),
- static_cast<int64>(base));
-
- int64 hint = sys_mmap(base, 0x1000, PROT_READ,
- MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
- ST_ASSERT(hint > 0);
- ST_ASSERT(hint != static_cast<int64>(base));
- ST_ASSERT_EQ(vmm_handle_page_fault(space, hint, VmFaultCause::Read), 0);
-
- curr->mm = old_mm;
- space->destroy();
- }
- st_pass();
-
- st_begin("sys_mm: brk growth and shrink");
- {
- TaskControlBlock* curr = current_task();
- VmSpace* old_mm = curr->mm;
-
- VmSpace* space = VmSpace::create();
- ST_ASSERT(space != nullptr);
- curr->mm = space;
-
- int64 brk0 = sys_brk(0x400000, 0, 0, 0, 0, 0);
- ST_ASSERT_EQ(brk0, 0x400000);
-
- int64 brk1 = sys_brk(0x402000, 0, 0, 0, 0, 0);
- ST_ASSERT_EQ(brk1, 0x402000);
- ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x400000, VmFaultCause::Read), 0);
-
- int64 brk2 = sys_brk(0x400000, 0, 0, 0, 0, 0);
- ST_ASSERT_EQ(brk2, 0x400000);
- ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x401000, VmFaultCause::Read), -EFAULT);
-
- curr->mm = old_mm;
- space->destroy();
- }
- st_pass();
-
- st_begin("sys_mm: brk invalid shrink below start");
- {
- TaskControlBlock* curr = current_task();
- VmSpace* old_mm = curr->mm;
-
- VmSpace* space = VmSpace::create();
- ST_ASSERT(space != nullptr);
- curr->mm = space;
-
- ST_ASSERT_EQ(sys_brk(0x500000, 0, 0, 0, 0, 0), 0x500000);
- ST_ASSERT_EQ(sys_brk(0x4FF000, 0, 0, 0, 0, 0), -EINVAL);
-
- curr->mm = old_mm;
- space->destroy();
- }
- st_pass();
-
- st_begin("sys_mm: mprotect invalid range");
- {
- TaskControlBlock* curr = current_task();
- VmSpace* old_mm = curr->mm;
-
- VmSpace* space = VmSpace::create();
- ST_ASSERT(space != nullptr);
- curr->mm = space;
-
- ST_ASSERT_EQ(sys_mmap(0, 0x1000, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0) > 0, true);
- ST_ASSERT_EQ(sys_mprotect(0x123, 0x1000, PROT_READ, 0, 0, 0), -EINVAL);
- ST_ASSERT_EQ(sys_mprotect(0x900000, 0x1000, PROT_READ, 0, 0, 0), -ENOMEM);
-
- curr->mm = old_mm;
- space->destroy();
- }
- st_pass();
-}
diff --git a/test/test_sys_process.cc b/test/test_sys_process.cc
deleted file mode 100644
index 686bed6..0000000
--- a/test/test_sys_process.cc
+++ /dev/null
@@ -1,93 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-/*
- * test_sys_process.cc — Self-tests for process-control syscalls
- *
- * Tests: getpid, gettid, getppid, getuid, getgid, set_tid_address,
- * sched_yield, uname.
- */
-
-#include <lume/selftest.h>
-#include <lume/errno.h>
-#include <lume/task.h>
-#include <lume/types.h>
-
-/* Syscall entry points */
-int64 sys_getpid(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_gettid(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_getppid(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_getuid(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_getgid(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_set_tid_address(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_sched_yield_wrapper(uint64, uint64, uint64, uint64, uint64, uint64);
-
-void selftest_sys_process()
-{
- /* ---- getpid returns current tgid ---- */
- st_begin("sys_process: getpid returns valid pid");
- {
- int64 pid = sys_getpid(0, 0, 0, 0, 0, 0);
- /* Boot task pid is -1 but tgid may differ; just verify it returns */
- TaskControlBlock* curr = current_task();
- ST_ASSERT_EQ(pid, (int64)curr->tgid);
- }
- st_pass();
-
- /* ---- gettid returns current pid (tid) ---- */
- st_begin("sys_process: gettid returns current tid");
- {
- int64 tid = sys_gettid(0, 0, 0, 0, 0, 0);
- TaskControlBlock* curr = current_task();
- ST_ASSERT_EQ(tid, (int64)curr->pid);
- }
- st_pass();
-
- /* ---- getppid returns parent tgid ---- */
- st_begin("sys_process: getppid returns parent tgid");
- {
- int64 ppid = sys_getppid(0, 0, 0, 0, 0, 0);
- TaskControlBlock* curr = current_task();
- int64 expected = curr->parent ? (int64)curr->parent->tgid : 0;
- ST_ASSERT_EQ(ppid, expected);
- }
- st_pass();
-
- /* ---- getuid returns 0 (root) ---- */
- st_begin("sys_process: getuid returns 0 (root)");
- {
- int64 uid = sys_getuid(0, 0, 0, 0, 0, 0);
- ST_ASSERT_EQ(uid, (int64)0);
- }
- st_pass();
-
- /* ---- getgid returns 0 (root) ---- */
- st_begin("sys_process: getgid returns 0 (root)");
- {
- int64 gid = sys_getgid(0, 0, 0, 0, 0, 0);
- ST_ASSERT_EQ(gid, (int64)0);
- }
- st_pass();
-
- /* ---- set_tid_address stores pointer and returns tid ---- */
- st_begin("sys_process: set_tid_address returns tid");
- {
- uint64 dummy_addr = 0x12345678ULL;
- int64 tid = sys_set_tid_address(dummy_addr, 0, 0, 0, 0, 0);
- TaskControlBlock* curr = current_task();
- ST_ASSERT_EQ(tid, (int64)curr->pid);
- ST_ASSERT_EQ(curr->clear_child_tid, dummy_addr);
-
- /* Reset it to avoid side effects */
- curr->clear_child_tid = 0;
- }
- st_pass();
-
- /* ---- sched_yield returns 0 ---- */
- st_begin("sys_process: sched_yield returns 0");
- {
- int64 ret = sys_sched_yield_wrapper(0, 0, 0, 0, 0, 0);
- ST_ASSERT_EQ(ret, (int64)0);
- }
-}
-
diff --git a/test/test_trap.cc b/test/test_trap.cc
deleted file mode 100644
index c91948a..0000000
--- a/test/test_trap.cc
+++ /dev/null
@@ -1,197 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-/*
- * Boot self-tests for the Trap/IRQ subsystem.
- *
- * Tests MD→MI translation, IrqManager register/dispatch/unregister,
- * and stvec installation verification.
- */
-
-#include <lume/types.h>
-#include <lume/selftest.h>
-#include <lume/trap.h>
-#include <lume/irq.h>
-#include <lume/errno.h>
-#include <lume/console.h>
-#include <arch/trap.h>
-
-TrapCause trap_translate_scause(uint64 scause);
-
-/* ========================================================================
- * IrqManager test helpers
- * ======================================================================== */
-
-static volatile uint32 g_test_irq_fired = 0;
-static volatile void* g_test_irq_arg = nullptr;
-
-static void test_irq_handler(uint32 irq, void* arg)
-{
- g_test_irq_fired = irq;
- g_test_irq_arg = arg;
-}
-
-/* ========================================================================
- * Test cases
- * ======================================================================== */
-
-static bool test_stvec_installed() {
- uint64 vec = arch::trap::get_vector();
- return vec != 0;
-}
-
-static bool test_scause_exception_translation() {
- /* Verify all exception causes map correctly */
- bool ok = true;
- ok = ok && (trap_translate_scause(EXC_LOAD_PAGE_FAULT) == TrapCause::PageFaultLoad);
- ok = ok && (trap_translate_scause(EXC_STORE_PAGE_FAULT) == TrapCause::PageFaultStore);
- ok = ok && (trap_translate_scause(EXC_INST_PAGE_FAULT) == TrapCause::PageFaultExec);
- ok = ok && (trap_translate_scause(EXC_ECALL_FROM_U) == TrapCause::Syscall);
- ok = ok && (trap_translate_scause(EXC_ILLEGAL_INST) == TrapCause::IllegalInst);
- ok = ok && (trap_translate_scause(EXC_BREAKPOINT) == TrapCause::Breakpoint);
- ok = ok && (trap_translate_scause(EXC_LOAD_ACCESS_FAULT) == TrapCause::AccessFault);
- ok = ok && (trap_translate_scause(EXC_INST_MISALIGNED) == TrapCause::AlignFault);
- return ok;
-}
-
-static bool test_scause_interrupt_translation() {
- bool ok = true;
- ok = ok && (trap_translate_scause(SCAUSE_INTERRUPT | IRQ_S_TIMER) == TrapCause::IrqTimer);
- ok = ok && (trap_translate_scause(SCAUSE_INTERRUPT | IRQ_S_SOFTWARE) == TrapCause::IrqSoftware);
- ok = ok && (trap_translate_scause(SCAUSE_INTERRUPT | IRQ_S_EXTERNAL) == TrapCause::IrqExternal);
- ok = ok && (trap_translate_scause(SCAUSE_INTERRUPT | 99) == TrapCause::Unknown);
- return ok;
-}
-
-static bool test_scause_unknown() {
- /* ecall from S-mode should map to Unknown (we only handle U-mode ecall) */
- return trap_translate_scause(EXC_ECALL_FROM_S) == TrapCause::Unknown;
-}
-
-static bool test_irqmanager_register_dispatch() {
- IrqManager mgr;
- mgr.init();
-
- g_test_irq_fired = 0;
- g_test_irq_arg = nullptr;
-
- int dummy_arg = 42;
- int ret = mgr.register_handler(10, test_irq_handler, &dummy_arg);
- if (ret != 0) return false;
-
- /* Dispatch IRQ 10 — handler should be called */
- mgr.dispatch(10);
-
- if (g_test_irq_fired != 10) return false;
- if (g_test_irq_arg != &dummy_arg) return false;
-
- return true;
-}
-
-static bool test_irqmanager_unregister() {
- IrqManager mgr;
- mgr.init();
-
- g_test_irq_fired = 0;
-
- mgr.register_handler(5, test_irq_handler, nullptr);
- mgr.unregister_handler(5);
-
- /* Dispatch after unregister — handler should NOT be called */
- mgr.dispatch(5);
-
- return g_test_irq_fired == 0;
-}
-
-static bool test_irqmanager_double_register() {
- IrqManager mgr;
- mgr.init();
-
- int ret1 = mgr.register_handler(7, test_irq_handler, nullptr);
- int ret2 = mgr.register_handler(7, test_irq_handler, nullptr);
-
- return ret1 == 0 && ret2 == -EBUSY;
-}
-
-static bool test_irqmanager_invalid_irq() {
- IrqManager mgr;
- mgr.init();
-
- int ret = mgr.register_handler(999, test_irq_handler, nullptr);
- return ret == -EINVAL;
-}
-
-static bool test_irqmanager_has_handler() {
- IrqManager mgr;
- mgr.init();
-
- if (mgr.has_handler(10)) return false; /* not yet registered */
-
- mgr.register_handler(10, test_irq_handler, nullptr);
- if (!mgr.has_handler(10)) return false; /* now registered */
-
- mgr.unregister_handler(10);
- if (mgr.has_handler(10)) return false; /* unregistered */
-
- return true;
-}
-
-static bool test_irqmanager_spurious_dispatch() {
- IrqManager mgr;
- mgr.init();
-
- g_test_irq_fired = 0;
-
- /* Dispatch an unregistered IRQ — should not crash or fire */
- mgr.dispatch(0);
- mgr.dispatch(63);
-
- return g_test_irq_fired == 0;
-}
-
-/* ========================================================================
- * Test runner
- * ======================================================================== */
-
-void selftest_trap()
-{
- st_begin("trap: stvec is installed");
- ST_ASSERT(test_stvec_installed());
- st_pass();
-
- st_begin("trap: scause exception translation");
- ST_ASSERT(test_scause_exception_translation());
- st_pass();
-
- st_begin("trap: scause interrupt translation");
- ST_ASSERT(test_scause_interrupt_translation());
- st_pass();
-
- st_begin("trap: scause unknown (ecall from S)");
- ST_ASSERT(test_scause_unknown());
- st_pass();
-
- st_begin("trap: IrqManager register/dispatch");
- ST_ASSERT(test_irqmanager_register_dispatch());
- st_pass();
-
- st_begin("trap: IrqManager unregister");
- ST_ASSERT(test_irqmanager_unregister());
- st_pass();
-
- st_begin("trap: IrqManager double register");
- ST_ASSERT(test_irqmanager_double_register());
- st_pass();
-
- st_begin("trap: IrqManager invalid IRQ");
- ST_ASSERT(test_irqmanager_invalid_irq());
- st_pass();
-
- st_begin("trap: IrqManager has_handler");
- ST_ASSERT(test_irqmanager_has_handler());
- st_pass();
-
- st_begin("trap: IrqManager spurious dispatch");
- ST_ASSERT(test_irqmanager_spurious_dispatch());
- st_pass();
-}
diff --git a/test/test_uaccess.cc b/test/test_uaccess.cc
deleted file mode 100644
index c5a8c6c..0000000
--- a/test/test_uaccess.cc
+++ /dev/null
@@ -1,97 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-#include <lume/selftest.h>
-#include <lume/uaccess.h>
-#include <lume/errno.h>
-
-#include <lume/vmm.h>
-#include <lume/pmm.h>
-#include <lume/tlb.h>
-#include <lume/mm_layout.h>
-#include <arch/pmap.h>
-
-void selftest_uaccess()
-{
- /* To test valid user addresses (< kKernelVaBase), we need a writable page mapped there.
- * We cannot use VmSpace::create() because it does not yet inherit kernel mappings,
- * so switching to it hangs. We also cannot map it with the U bit because S-mode
- * would need the SUM bit enabled.
- * So we manually allocate a physical page and map it into the CURRENT page table
- * at 0x100000 without the U bit, just for this test.
- */
- uint64 mock_user_addr = 0x100000;
-
- uint64 satp;
- asm volatile("csrr %0, satp" : "=r"(satp));
- uint64 root_pa = (satp & 0xFFFFFFFFFFFULL) << 12;
-
- Page* page = pmm_alloc_page();
- ST_ASSERT(page != nullptr);
- uint64 paddr = page_to_pa(page).raw;
-
- pmap::map(root_pa, mock_user_addr, paddr, pmap::PTE_R | pmap::PTE_W | pmap::PTE_V | pmap::PTE_A | pmap::PTE_D);
-
- /* Flush TLB to see the new mapping */
- tlb_shootdown_all();
-
- st_begin("uaccess: copy_to_user valid range");
- char kernel_buf[32] = "Hello User Space!";
- int ret = copy_to_user(mock_user_addr, kernel_buf, 18);
- ST_ASSERT_EQ(ret, 0);
- st_pass();
-
- st_begin("uaccess: copy_from_user valid range");
- char read_buf[32] = {0};
- /* We wrote "Hello User Space!" to mock_user_addr previously */
- ret = copy_from_user(read_buf, mock_user_addr, 18);
- ST_ASSERT_EQ(ret, 0);
- ST_ASSERT(read_buf[0] == 'H');
- ST_ASSERT(read_buf[1] == 'e');
- ST_ASSERT(read_buf[6] == 'U');
- st_pass();
-
- st_begin("uaccess: strncpy_from_user valid range");
- char str_buf[32] = {0};
- int64 len = strncpy_from_user(str_buf, mock_user_addr, 32);
- ST_ASSERT_EQ(len, 17); /* "Hello User Space!" is 17 chars long */
- ST_ASSERT(str_buf[0] == 'H');
- st_pass();
-
- st_begin("uaccess: strncpy_from_user max_len truncation");
- char trunc_buf[32] = {0};
- len = strncpy_from_user(trunc_buf, mock_user_addr, 6);
- ST_ASSERT_EQ(len, 5); /* Will copy 5 chars and NUL terminate */
- ST_ASSERT(trunc_buf[4] == 'o');
- ST_ASSERT(trunc_buf[5] == '\0');
- st_pass();
-
- st_begin("uaccess: copy_to_user invalid range (crosses boundary)");
- ret = copy_to_user(kKernelVaBase - 10, kernel_buf, 20);
- ST_ASSERT_EQ(ret, -EFAULT);
- st_pass();
-
- st_begin("uaccess: copy_to_user invalid range (entirely in kernel)");
- ret = copy_to_user(kKernelVaBase + 0x1000, kernel_buf, 20);
- ST_ASSERT_EQ(ret, -EFAULT);
- st_pass();
-
- st_begin("uaccess: copy_to_user invalid range (overflow)");
- ret = copy_to_user(0xFFFFFFFFFFFFFFF0ULL, kernel_buf, 0x100);
- ST_ASSERT_EQ(ret, -EFAULT);
- st_pass();
-
- st_begin("uaccess: copy_from_user invalid ranges");
- ret = copy_from_user(read_buf, kKernelVaBase - 5, 10);
- ST_ASSERT_EQ(ret, -EFAULT);
-
- ret = copy_from_user(read_buf, kKernelVaBase, 10);
- ST_ASSERT_EQ(ret, -EFAULT);
- st_pass();
-
- st_begin("uaccess: strncpy_from_user invalid range");
- uint64 invalid_addr = kKernelVaBase + 0x1000;
- len = strncpy_from_user(str_buf, invalid_addr, 10);
- ST_ASSERT_EQ(len, -EFAULT);
- st_pass();
-}
diff --git a/test/test_vfs_io.cc b/test/test_vfs_io.cc
deleted file mode 100644
index 3d3b125..0000000
--- a/test/test_vfs_io.cc
+++ /dev/null
@@ -1,345 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-/*
- * test_vfs_io.cc — Self-tests for VFS file I/O via syscalls
- *
- * Tests openat, read, write, close, lseek, dup, fstat, getdents64,
- * getcwd, chdir, and mkdirat through the kernel syscall layer.
- */
-
-#include <lume/selftest.h>
-#include <lume/errno.h>
-#include <lume/file.h>
-#include <lume/fs.h>
-#include <lume/fs_types.h>
-#include <lume/task.h>
-#include <lume/fdtable.h>
-
-/* Syscall entry points */
-int64 sys_openat(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_close(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_read(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_write(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_lseek(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_dup(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_dup3(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_fstat_wrapper(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_getcwd(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_chdir(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_mkdirat(uint64, uint64, uint64, uint64, uint64, uint64);
-int64 sys_getdents64(uint64, uint64, uint64, uint64, uint64, uint64);
-
-/* Helper: open a file, returns fd or negative error */
-static int64 open_file(const char* path, int flags, uint32 mode)
-{
- return sys_openat(
- static_cast<uint64>(-100), /* AT_FDCWD */
- reinterpret_cast<uint64>(path),
- static_cast<uint64>(flags),
- static_cast<uint64>(mode), 0, 0);
-}
-
-void selftest_vfs_io()
-{
- /* ---- openat O_CREAT creates a file ---- */
- st_begin("vfs_io: openat O_CREAT creates file");
- {
- int64 fd = open_file("/vfs_test_file", O_CREAT | O_WRONLY, 0644);
- ST_ASSERT(fd >= 0);
- sys_close(static_cast<uint64>(fd), 0, 0, 0, 0, 0);
- }
- st_pass();
-
- /* ---- openat existing file ---- */
- st_begin("vfs_io: openat existing file succeeds");
- {
- int64 fd = open_file("/vfs_test_file", O_RDONLY, 0);
- ST_ASSERT(fd >= 0);
- sys_close(static_cast<uint64>(fd), 0, 0, 0, 0, 0);
- }
- st_pass();
-
- /* ---- openat nonexistent without O_CREAT returns -ENOENT ---- */
- st_begin("vfs_io: openat nonexistent returns -ENOENT");
- {
- int64 fd = open_file("/no_such_file_ever", O_RDONLY, 0);
- ST_ASSERT_EQ(fd, (int64)-ENOENT);
- }
- st_pass();
-
- /* ---- write then read ---- */
- st_begin("vfs_io: write then read back data");
- {
- /* Write */
- int64 fd = open_file("/vfs_rw_test", O_CREAT | O_WRONLY, 0644);
- ST_ASSERT(fd >= 0);
-
- const char* msg = "Hello VFS!";
- int64 written = sys_write(
- static_cast<uint64>(fd),
- reinterpret_cast<uint64>(msg),
- 10, 0, 0, 0);
- ST_ASSERT_EQ(written, (int64)10);
- sys_close(static_cast<uint64>(fd), 0, 0, 0, 0, 0);
-
- /* Read */
- fd = open_file("/vfs_rw_test", O_RDONLY, 0);
- ST_ASSERT(fd >= 0);
-
- char buf[32] = {};
- int64 nread = sys_read(
- static_cast<uint64>(fd),
- reinterpret_cast<uint64>(buf),
- sizeof(buf), 0, 0, 0);
- ST_ASSERT_EQ(nread, (int64)10);
-
- bool match = true;
- for (int i = 0; i < 10; i++) {
- if (buf[i] != msg[i]) { match = false; break; }
- }
- ST_ASSERT(match);
- sys_close(static_cast<uint64>(fd), 0, 0, 0, 0, 0);
- }
- st_pass();
-
- /* ---- read past EOF returns 0 ---- */
- st_begin("vfs_io: read past EOF returns 0");
- {
- int64 fd = open_file("/vfs_rw_test", O_RDONLY, 0);
- ST_ASSERT(fd >= 0);
-
- char buf[64];
- /* First read: 10 bytes */
- int64 n1 = sys_read(static_cast<uint64>(fd),
- reinterpret_cast<uint64>(buf), 64, 0, 0, 0);
- ST_ASSERT_EQ(n1, (int64)10);
-
- /* Second read: should be 0 (EOF) */
- int64 n2 = sys_read(static_cast<uint64>(fd),
- reinterpret_cast<uint64>(buf), 64, 0, 0, 0);
- ST_ASSERT_EQ(n2, (int64)0);
-
- sys_close(static_cast<uint64>(fd), 0, 0, 0, 0, 0);
- }
- st_pass();
-
- /* ---- O_TRUNC clears file content ---- */
- st_begin("vfs_io: O_TRUNC clears file content");
- {
- int64 fd = open_file("/vfs_rw_test", O_WRONLY | O_TRUNC, 0);
- ST_ASSERT(fd >= 0);
- sys_close(static_cast<uint64>(fd), 0, 0, 0, 0, 0);
-
- fd = open_file("/vfs_rw_test", O_RDONLY, 0);
- ST_ASSERT(fd >= 0);
-
- char buf[16];
- int64 n = sys_read(static_cast<uint64>(fd),
- reinterpret_cast<uint64>(buf), 16, 0, 0, 0);
- ST_ASSERT_EQ(n, (int64)0); /* truncated = empty */
- sys_close(static_cast<uint64>(fd), 0, 0, 0, 0, 0);
- }
- st_pass();
-
- /* ---- close invalid fd returns -EBADF ---- */
- st_begin("vfs_io: close bad fd returns -EBADF");
- {
- int64 ret = sys_close(999, 0, 0, 0, 0, 0);
- ST_ASSERT_EQ(ret, (int64)-EBADF);
- }
- st_pass();
-
- /* ---- read/write on bad fd returns -EBADF ---- */
- st_begin("vfs_io: read/write bad fd returns -EBADF");
- {
- char buf[8];
- int64 r = sys_read(999, reinterpret_cast<uint64>(buf), 8, 0, 0, 0);
- ST_ASSERT_EQ(r, (int64)-EBADF);
-
- int64 w = sys_write(999, reinterpret_cast<uint64>(buf), 8, 0, 0, 0);
- ST_ASSERT_EQ(w, (int64)-EBADF);
- }
- st_pass();
-
- /* ---- lseek SEEK_SET / SEEK_CUR / SEEK_END ---- */
- st_begin("vfs_io: lseek SEEK_SET/CUR/END");
- {
- /* Write 20 bytes */
- int64 fd = open_file("/vfs_lseek_test", O_CREAT | O_RDWR, 0644);
- ST_ASSERT(fd >= 0);
-
- char data[20];
- for (int i = 0; i < 20; i++) data[i] = static_cast<char>('A' + i);
- sys_write(static_cast<uint64>(fd),
- reinterpret_cast<uint64>(data), 20, 0, 0, 0);
-
- /* SEEK_SET to 5 */
- int64 pos = sys_lseek(static_cast<uint64>(fd), 5, SEEK_SET, 0, 0, 0);
- ST_ASSERT_EQ(pos, (int64)5);
-
- /* Read 1 byte, should be 'F' */
- char c = 0;
- sys_read(static_cast<uint64>(fd),
- reinterpret_cast<uint64>(&c), 1, 0, 0, 0);
- ST_ASSERT_EQ(c, 'F');
-
- /* SEEK_CUR +3 → pos 9 */
- pos = sys_lseek(static_cast<uint64>(fd), 3, SEEK_CUR, 0, 0, 0);
- ST_ASSERT_EQ(pos, (int64)9);
-
- /* SEEK_END -1 → pos 19 */
- pos = sys_lseek(static_cast<uint64>(fd), -1, SEEK_END, 0, 0, 0);
- ST_ASSERT_EQ(pos, (int64)19);
-
- sys_read(static_cast<uint64>(fd),
- reinterpret_cast<uint64>(&c), 1, 0, 0, 0);
- ST_ASSERT_EQ(c, 'T');
-
- sys_close(static_cast<uint64>(fd), 0, 0, 0, 0, 0);
- }
- st_pass();
-
- /* ---- lseek invalid whence returns -EINVAL ---- */
- st_begin("vfs_io: lseek invalid whence");
- {
- int64 fd = open_file("/vfs_lseek_test", O_RDONLY, 0);
- ST_ASSERT(fd >= 0);
- int64 ret = sys_lseek(static_cast<uint64>(fd), 0, 99, 0, 0, 0);
- ST_ASSERT_EQ(ret, (int64)-EINVAL);
- sys_close(static_cast<uint64>(fd), 0, 0, 0, 0, 0);
- }
- st_pass();
-
- /* ---- dup: duplicate fd shares file position ---- */
- st_begin("vfs_io: dup shares file position");
- {
- int64 fd1 = open_file("/vfs_lseek_test", O_RDONLY, 0);
- ST_ASSERT(fd1 >= 0);
-
- int64 fd2 = sys_dup(static_cast<uint64>(fd1), 0, 0, 0, 0, 0);
- ST_ASSERT(fd2 >= 0);
- ST_ASSERT_NE(fd1, fd2);
-
- /* Both should work for reading */
- char c = 0;
- int64 n = sys_read(static_cast<uint64>(fd2),
- reinterpret_cast<uint64>(&c), 1, 0, 0, 0);
- ST_ASSERT_EQ(n, (int64)1);
-
- sys_close(static_cast<uint64>(fd1), 0, 0, 0, 0, 0);
- sys_close(static_cast<uint64>(fd2), 0, 0, 0, 0, 0);
- }
- st_pass();
-
- /* ---- fstat: basic stat ---- */
- st_begin("vfs_io: fstat returns valid kstat");
- {
- int64 fd = open_file("/vfs_lseek_test", O_RDONLY, 0);
- ST_ASSERT(fd >= 0);
-
- kstat st{};
- int64 ret = sys_fstat_wrapper(
- static_cast<uint64>(fd),
- reinterpret_cast<uint64>(&st), 0, 0, 0, 0);
- ST_ASSERT_EQ(ret, (int64)0);
- ST_ASSERT(S_ISREG(st.st_mode));
- ST_ASSERT_EQ(st.st_size, (int64)20);
-
- sys_close(static_cast<uint64>(fd), 0, 0, 0, 0, 0);
- }
- st_pass();
-
- /* ---- getcwd: returns "/" ---- */
- st_begin("vfs_io: getcwd returns /");
- {
- char buf[64] = {};
- int64 len = sys_getcwd(
- reinterpret_cast<uint64>(buf), 64, 0, 0, 0, 0);
- ST_ASSERT(len > 0);
- ST_ASSERT_EQ(buf[0], '/');
- }
- st_pass();
-
- /* ---- mkdirat: create directory ---- */
- st_begin("vfs_io: mkdirat creates directory");
- {
- int64 ret = sys_mkdirat(
- static_cast<uint64>(-100),
- reinterpret_cast<uint64>("/vfs_io_dir"),
- 0755, 0, 0, 0);
- ST_ASSERT_EQ(ret, (int64)0);
-
- /* Open it to confirm it's a directory */
- int64 fd = open_file("/vfs_io_dir", O_RDONLY, 0);
- ST_ASSERT(fd >= 0);
-
- kstat st{};
- sys_fstat_wrapper(static_cast<uint64>(fd),
- reinterpret_cast<uint64>(&st), 0, 0, 0, 0);
- ST_ASSERT(S_ISDIR(st.st_mode));
-
- sys_close(static_cast<uint64>(fd), 0, 0, 0, 0, 0);
- }
- st_pass();
-
- /* ---- getdents64: list root directory ---- */
- st_begin("vfs_io: getdents64 lists root children");
- {
- int64 fd = open_file("/", O_RDONLY, 0);
- ST_ASSERT(fd >= 0);
-
- char buf[512] = {};
- int64 n = sys_getdents64(
- static_cast<uint64>(fd),
- reinterpret_cast<uint64>(buf),
- 512, 0, 0, 0);
- ST_ASSERT(n > 0); /* At least "." and ".." */
-
- sys_close(static_cast<uint64>(fd), 0, 0, 0, 0, 0);
- }
- st_pass();
-
- /* ---- write to O_RDONLY fd returns -EBADF ---- */
- st_begin("vfs_io: write to O_RDONLY returns -EBADF");
- {
- int64 fd = open_file("/vfs_rw_test", O_RDONLY, 0);
- ST_ASSERT(fd >= 0);
-
- char buf[] = "x";
- int64 w = sys_write(static_cast<uint64>(fd),
- reinterpret_cast<uint64>(buf), 1, 0, 0, 0);
- ST_ASSERT_EQ(w, (int64)-EBADF);
- sys_close(static_cast<uint64>(fd), 0, 0, 0, 0, 0);
- }
- st_pass();
-
- /* ---- double close returns -EBADF ---- */
- st_begin("vfs_io: double close returns -EBADF");
- {
- int64 fd = open_file("/vfs_rw_test", O_RDONLY, 0);
- ST_ASSERT(fd >= 0);
-
- int64 r1 = sys_close(static_cast<uint64>(fd), 0, 0, 0, 0, 0);
- ST_ASSERT_EQ(r1, (int64)0);
-
- int64 r2 = sys_close(static_cast<uint64>(fd), 0, 0, 0, 0, 0);
- ST_ASSERT_EQ(r2, (int64)-EBADF);
- }
- st_pass();
-
- /* ---- fd reuse after close ---- */
- st_begin("vfs_io: fd reuse after close");
- {
- int64 fd1 = open_file("/vfs_rw_test", O_RDONLY, 0);
- ST_ASSERT(fd1 >= 0);
- sys_close(static_cast<uint64>(fd1), 0, 0, 0, 0, 0);
-
- int64 fd2 = open_file("/vfs_rw_test", O_RDONLY, 0);
- ST_ASSERT(fd2 >= 0);
- /* fd2 should reuse the slot of fd1 */
- ST_ASSERT(fd2 <= fd1);
- sys_close(static_cast<uint64>(fd2), 0, 0, 0, 0, 0);
- }
- st_pass();
-}
diff --git a/test/test_vma.cc b/test/test_vma.cc
deleted file mode 100644
index 5278914..0000000
--- a/test/test_vma.cc
+++ /dev/null
@@ -1,282 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-#include <lume/selftest.h>
-#include <lume/vmm.h>
-#include <lume/addr.h>
-#include <lume/errno.h>
-#include <arch/pmap.h>
-
-void selftest_vma() {
- st_begin("vma: create and destroy VmSpace");
- {
- VmSpace* space = VmSpace::create();
- ST_ASSERT(space != nullptr);
- ST_ASSERT(space->root_pa != 0);
-
- // Add a few regions to ensure destruction works
- vmm_map_user(space, 0x1000, 0x1000, VM_READ);
- vmm_map_user(space, 0x3000, 0x1000, VM_READ | VM_WRITE);
-
- space->destroy();
- }
- st_pass();
-
- st_begin("vma: map user region and handle demand paging");
- {
- VmSpace* space = VmSpace::create();
-
- // Map 8KB (2 pages) at 0x400000 using MI-layer flags
- int ret = vmm_map_user(space, 0x400000, 0x2000, VM_READ | VM_WRITE);
- ST_ASSERT_EQ(ret, 0);
-
- // Fault in the first page
- ret = vmm_handle_page_fault(space, 0x400500, VmFaultCause::Read);
- ST_ASSERT_EQ(ret, 0);
-
- // Verify mapping in hardware page table
- uint64 pa = 0;
- bool mapped = pmap::lookup(space->root_pa, 0x400000, &pa);
- ST_ASSERT(mapped);
- ST_ASSERT_NE(pa, 0ULL);
-
- // Fault in the second page
- ret = vmm_handle_page_fault(space, 0x401FFF, VmFaultCause::Read);
- ST_ASSERT_EQ(ret, 0);
-
- // Should reject fault outside VMA with -EFAULT
- ret = vmm_handle_page_fault(space, 0x402000, VmFaultCause::Read);
- ST_ASSERT_EQ(ret, -EFAULT);
-
- space->destroy();
- }
- st_pass();
-
- st_begin("vma: unmap user region prevents page fault");
- {
- VmSpace* space = VmSpace::create();
- vmm_map_user(space, 0x10000, 0x2000, VM_READ);
-
- // Inside the region — should succeed
- ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x10500, VmFaultCause::Read), 0);
-
- // Unmap exactly
- vmm_unmap_user(space, 0x10000, 0x2000);
-
- // Should fail now with -EFAULT
- ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x10500, VmFaultCause::Read), -EFAULT);
-
- space->destroy();
- }
- st_pass();
-
- st_begin("vma: duplicate page fault on same page returns 0");
- {
- VmSpace* space = VmSpace::create();
- vmm_map_user(space, 0x200000, 0x1000, VM_READ | VM_WRITE);
-
- // First fault allocates the physical page
- int ret = vmm_handle_page_fault(space, 0x200000, VmFaultCause::Read);
- ST_ASSERT_EQ(ret, 0);
-
- // Second fault on the same page should hit "already mapped" path
- ret = vmm_handle_page_fault(space, 0x200100, VmFaultCause::Read);
- ST_ASSERT_EQ(ret, 0);
-
- // Verify both land on the same physical page
- uint64 pa1 = 0, pa2 = 0;
- pmap::lookup(space->root_pa, 0x200000, &pa1);
- pmap::lookup(space->root_pa, 0x200100, &pa2);
- // Same page, so PA for both addresses should be the same base page
- ST_ASSERT_EQ(pa1, pa2);
-
- space->destroy();
- }
- st_pass();
-
- st_begin("vma: vm_perm_to_pte flag translation correctness");
- {
- // Test each MI flag maps to correct hardware PTE bit
- uint64 pte_r = pmap::vm_perm_to_pte(VM_READ);
- ST_ASSERT(pte_r & pmap::PTE_R);
- ST_ASSERT(!(pte_r & pmap::PTE_W));
- ST_ASSERT(!(pte_r & pmap::PTE_X));
- ST_ASSERT(!(pte_r & pmap::PTE_U));
-
- uint64 pte_rw = pmap::vm_perm_to_pte(VM_READ | VM_WRITE);
- ST_ASSERT(pte_rw & pmap::PTE_R);
- ST_ASSERT(pte_rw & pmap::PTE_W);
-
- uint64 pte_rx = pmap::vm_perm_to_pte(VM_READ | VM_EXEC);
- ST_ASSERT(pte_rx & pmap::PTE_R);
- ST_ASSERT(pte_rx & pmap::PTE_X);
- ST_ASSERT(!(pte_rx & pmap::PTE_W));
-
- uint64 pte_u = pmap::vm_perm_to_pte(VM_READ | VM_USER);
- ST_ASSERT(pte_u & pmap::PTE_R);
- ST_ASSERT(pte_u & pmap::PTE_U);
-
- // All combined
- uint64 pte_all = pmap::vm_perm_to_pte(VM_READ | VM_WRITE | VM_EXEC | VM_USER);
- ST_ASSERT(pte_all & pmap::PTE_R);
- ST_ASSERT(pte_all & pmap::PTE_W);
- ST_ASSERT(pte_all & pmap::PTE_X);
- ST_ASSERT(pte_all & pmap::PTE_U);
- ST_ASSERT(pte_all & pmap::PTE_V); // Always valid
- }
- st_pass();
-
- st_begin("vma: unmap subset only removes contained VMAs");
- {
- VmSpace* space = VmSpace::create();
- // Map three separate regions
- vmm_map_user(space, 0x50000, 0x1000, VM_READ);
- vmm_map_user(space, 0x60000, 0x1000, VM_READ);
- vmm_map_user(space, 0x70000, 0x1000, VM_READ);
-
- // Unmap only the middle one (range covers 0x60000-0x61000)
- vmm_unmap_user(space, 0x60000, 0x1000);
-
- // First and third should still be reachable
- ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x50000, VmFaultCause::Read), 0);
- ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x70000, VmFaultCause::Read), 0);
-
- // Middle should fail
- ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x60000, VmFaultCause::Read), -EFAULT);
-
- space->destroy();
- }
- st_pass();
-
- st_begin("vma: null space returns -EINVAL");
- {
- ST_ASSERT_EQ(vmm_map_user(nullptr, 0x1000, 0x1000, VM_READ), -EINVAL);
- ST_ASSERT_EQ(vmm_unmap_user(nullptr, 0x1000, 0x1000), -EINVAL);
- ST_ASSERT_EQ(vmm_handle_page_fault(nullptr, 0x1000, VmFaultCause::Read), -EINVAL);
- }
- st_pass();
-
- st_begin("vma: clone and copy-on-write");
- {
- VmSpace* parent = VmSpace::create();
- vmm_map_user(parent, 0x80000, 0x1000, VM_READ | VM_WRITE);
-
- /* Trigger read fault in parent to allocate physical page */
- ST_ASSERT_EQ(vmm_handle_page_fault(parent, 0x80000, VmFaultCause::Read), 0);
- uint64 parent_pa_initial;
- ST_ASSERT(pmap::lookup(parent->root_pa, 0x80000, &parent_pa_initial));
-
- /* Clone the address space */
- VmSpace* child = vmm_clone(parent);
- ST_ASSERT(child != nullptr);
-
- /* Verify child has the same physical page mapped but COW */
- uint64 child_pa;
- ST_ASSERT(pmap::lookup(child->root_pa, 0x80000, &child_pa));
- ST_ASSERT_EQ(child_pa, parent_pa_initial);
- ST_ASSERT(pmap::is_cow(child->root_pa, 0x80000));
- ST_ASSERT(pmap::is_cow(parent->root_pa, 0x80000)); /* Parent also marked COW */
-
- /* Trigger Store Page Fault (cause=15) in child */
- ST_ASSERT_EQ(vmm_handle_page_fault(child, 0x80000, VmFaultCause::Write), 0);
-
- /* Child should now have a new physical page, no longer COW */
- uint64 child_pa_new;
- ST_ASSERT(pmap::lookup(child->root_pa, 0x80000, &child_pa_new));
- ST_ASSERT(child_pa_new != parent_pa_initial);
- ST_ASSERT(!pmap::is_cow(child->root_pa, 0x80000));
-
- /* Parent is still mapped to the old page, still COW because it hasn't written */
- uint64 parent_pa_current;
- ST_ASSERT(pmap::lookup(parent->root_pa, 0x80000, &parent_pa_current));
- ST_ASSERT_EQ(parent_pa_current, parent_pa_initial);
- ST_ASSERT(pmap::is_cow(parent->root_pa, 0x80000));
-
- /* Trigger Store Page Fault in parent */
- ST_ASSERT_EQ(vmm_handle_page_fault(parent, 0x80000, VmFaultCause::Write), 0);
- ST_ASSERT(!pmap::is_cow(parent->root_pa, 0x80000));
-
- child->destroy();
- parent->destroy();
- }
- st_pass();
-
- st_begin("vma: gap search finds next free range");
- {
- VmSpace* space = VmSpace::create();
- vmm_map_user(space, 0x1000, 0x3000, VM_READ);
-
- uint64 va = mm_find_free_va(space, 0x1000, kPageSize);
- ST_ASSERT_EQ(va, 0x4000ULL);
-
- space->destroy();
- }
- st_pass();
-
- st_begin("vma: reject overlapping mapping");
- {
- VmSpace* space = VmSpace::create();
- ST_ASSERT_EQ(vmm_map_user(space, 0x20000, 0x2000, VM_READ), 0);
- ST_ASSERT_EQ(vmm_map_user(space, 0x21000, 0x1000, VM_READ), -EINVAL);
- space->destroy();
- }
- st_pass();
-
- st_begin("vma: partial unmap splits a single VMA");
- {
- VmSpace* space = VmSpace::create();
- ST_ASSERT_EQ(vmm_map_user(space, 0x80000, 0x3000, VM_READ | VM_WRITE), 0);
-
- ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x80000, VmFaultCause::Read), 0);
- ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x81000, VmFaultCause::Read), 0);
- ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x82000, VmFaultCause::Read), 0);
-
- ST_ASSERT_EQ(vmm_unmap_user(space, 0x81000, 0x1000), 0);
-
- ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x80000, VmFaultCause::Read), 0);
- ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x82000, VmFaultCause::Read), 0);
- ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x81000, VmFaultCause::Read), -EFAULT);
-
- space->destroy();
- }
- st_pass();
-
- st_begin("vma: mprotect read-only blocks write faults");
- {
- VmSpace* space = VmSpace::create();
- ST_ASSERT_EQ(vmm_map_user(space, 0x90000, 0x2000, VM_READ | VM_WRITE), 0);
- ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x90000, VmFaultCause::Read), 0);
-
- ST_ASSERT_EQ(vmm_protect_user(space, 0x90000, 0x1000, VM_READ), 0);
- ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x90000, VmFaultCause::Write), -EFAULT);
- ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x91000, VmFaultCause::Write), 0);
-
- space->destroy();
- }
- st_pass();
-
- st_begin("vma: PROT_NONE unmaps existing pages");
- {
- VmSpace* space = VmSpace::create();
- ST_ASSERT_EQ(vmm_map_user(space, 0xA0000, 0x1000, VM_READ | VM_WRITE), 0);
- ST_ASSERT_EQ(vmm_handle_page_fault(space, 0xA0000, VmFaultCause::Read), 0);
-
- ST_ASSERT_EQ(vmm_protect_user(space, 0xA0000, 0x1000, 0), 0);
- ST_ASSERT_EQ(vmm_handle_page_fault(space, 0xA0000, VmFaultCause::Read), -EFAULT);
-
- space->destroy();
- }
- st_pass();
-
- st_begin("vma: range covered check");
- {
- VmSpace* space = VmSpace::create();
- ST_ASSERT_EQ(vmm_map_user(space, 0xB0000, 0x2000, VM_READ), 0);
-
- ST_ASSERT(vma_range_covered(space, 0xB0000, 0xB1000));
- ST_ASSERT(!vma_range_covered(space, 0xB0000, 0xB3000));
-
- space->destroy();
- }
- st_pass();
-}
diff --git a/test/test_vmm.cc b/test/test_vmm.cc
deleted file mode 100644
index 635e3f3..0000000
--- a/test/test_vmm.cc
+++ /dev/null
@@ -1,58 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-/*
- * Boot Self-Test: VMM and Address Translation
- */
-
-#include <lume/selftest.h>
-#include <lume/klog.h>
-#include <lume/config.h>
-#include <lume/addr.h>
-#include <lume/vmm.h>
-#include <lume/pmm.h>
-#include <arch/config.h>
-
-namespace {
-constexpr uint64 kUartBase = 0x10000000ULL;
-constexpr uint64 kPageOffsetMask = kPageSize - 1;
-}
-
-void selftest_vmm() {
- st_begin("vmm: virtual/physical address translation");
- {
- PhysAddr pa = pmm_get_mem_base();
- VirtAddr va = pa_to_va(pa);
- ST_ASSERT_EQ(va.raw, arch::g_direct_map_base + pa.raw);
- ST_ASSERT_EQ(va_to_pa(va).raw, pa.raw);
-
- Pfn p = pa_to_pfn(pa);
- ST_ASSERT_EQ(p.raw, pa.raw / kPageSize);
- ST_ASSERT_EQ(pfn_to_pa(p).raw, pa.raw);
- }
- st_pass();
-
- st_begin("vmm: page alignment macros");
- {
- uint64 base = pmm_get_mem_base().raw;
- uint64 addr = base + kPageOffsetMask;
- ST_ASSERT_EQ(page_align_down(addr), base);
- ST_ASSERT_EQ(page_align_up(addr), base + kPageSize);
- ST_ASSERT_EQ(page_align_down(base), base);
- ST_ASSERT_EQ(page_align_up(base), base);
- }
- st_pass();
-
- st_begin("vmm: mapping kernel MMIO (driver API)");
- {
- /* Use UART MMIO address (0x10000000 on RISC-V virt).
- * Must NOT use pmm_get_mem_base() — that's PMP-protected firmware. */
- uint64 mmio_pa = kUartBase;
- vmm_map_kernel_mmio(mmio_pa, kPageSize);
-
- // Verify it doesn't crash and address is reachable in higher half
- uint64 va = pa_to_va(phys_addr(mmio_pa)).raw;
- ST_ASSERT(va >= arch::g_direct_map_base);
- }
- st_pass();
-}
diff --git a/test/test_waitqueue.cc b/test/test_waitqueue.cc
deleted file mode 100644
index 7f50d6b..0000000
--- a/test/test_waitqueue.cc
+++ /dev/null
@@ -1,91 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-#include <lume/selftest.h>
-#include <lume/waitqueue.h>
-#include <lume/task.h>
-#include <lume/sched.h>
-
-static WaitQueue g_test_wq;
-static volatile bool g_thread_woken = false;
-static volatile bool g_thread_started = false;
-static volatile int g_wake_count = 0;
-
-extern TaskControlBlock* kthread_create(const char* name, void (*entry)(void*), void* arg, uint8 priority);
-
-static void waitqueue_test_thread(void*)
-{
- g_thread_started = true;
- g_test_wq.sleep();
- g_thread_woken = true;
-
- extern void task_exit(int code);
- task_exit(0);
-}
-
-static void waitqueue_test_thread_all(void*)
-{
- g_test_wq.sleep();
- __sync_fetch_and_add(&g_wake_count, 1);
-
- extern void task_exit(int code);
- task_exit(0);
-}
-
-void selftest_waitqueue()
-{
- st_begin("waitqueue: sleep and wake_one");
-
- g_test_wq.init("test_wq");
- g_thread_started = false;
- g_thread_woken = false;
-
- TaskControlBlock* child = kthread_create("test_wq_child", waitqueue_test_thread, nullptr, 0);
- ST_ASSERT(child != nullptr);
-
- sched_yield();
-
- ST_ASSERT(g_thread_started == true);
- ST_ASSERT(g_thread_woken == false);
- ST_ASSERT(!g_test_wq.is_empty());
-
- kprintf("[test_wq] waking child\n");
- bool woken = g_test_wq.wake_one();
- ST_ASSERT(woken == true);
- ST_ASSERT(g_test_wq.is_empty());
-
- kprintf("[test_wq] yielding to child again\n");
- sched_yield();
- kprintf("[test_wq] returned from yield 2\n");
-
- ST_ASSERT(g_thread_woken == true);
-
- st_pass();
-
- st_begin("waitqueue: wake_all");
- g_wake_count = 0;
- for (int i = 0; i < 3; i++) {
- kthread_create("test_wq_all", waitqueue_test_thread_all, nullptr, 0);
- }
-
- /* Give all 3 threads a chance to start and sleep */
- kprintf("[test_wq] yielding 1 for wake_all\n");
- sched_yield();
- sched_yield();
- sched_yield();
-
- ST_ASSERT(!g_test_wq.is_empty());
-
- int woken_count = g_test_wq.wake_all();
-
- ST_ASSERT_EQ(woken_count, 3);
- ST_ASSERT(g_test_wq.is_empty());
-
- /* Give all 3 threads a chance to wake up and run */
- sched_yield();
- sched_yield();
- sched_yield();
-
- ST_ASSERT_EQ(g_wake_count, 3);
- st_pass();
-}