summaryrefslogtreecommitdiff
path: root/test/test_sched.cc
blob: d90c30ad869174a0ce0ac814853537f934e66c92 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
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();
}