diff options
| author | Richard Qin <richard.qin.x@gmail.com> | 2026-05-13 16:30:00 +0800 |
|---|---|---|
| committer | Richard Qin <richard.qin.x@gmail.com> | 2026-05-13 16:30:00 +0800 |
| commit | a34fe6882def73dfd8bfc8c6ce68eaf2193eadaa (patch) | |
| tree | 9f184aff64f80ac5a7542fdf675c5ebcedc62a69 | |
| parent | 1ca36835b3bf2d029d9e56e96c1f77791daa4983 (diff) | |
| download | lume-a34fe6882def73dfd8bfc8c6ce68eaf2193eadaa.tar.gz lume-a34fe6882def73dfd8bfc8c6ce68eaf2193eadaa.tar.xz | |
feat: add self-tests for Scheduler subsystem functionality
| -rw-r--r-- | na/test/test_sched.cc | 257 |
1 files changed, 257 insertions, 0 deletions
diff --git a/na/test/test_sched.cc b/na/test/test_sched.cc new file mode 100644 index 0000000..d90c30a --- /dev/null +++ b/na/test/test_sched.cc @@ -0,0 +1,257 @@ +/* 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(); +} + |
