test: extend coverage of schedTest

This commit is contained in:
dualfroz committed 2026-09-11 14:20:52 +02:00
1 parent bf31b363a6
commit d992d971e7
1 file changed
+158
+158
View File
@@ -310,3 +310,161 @@ KTEST(threadReapRejectsIdleOutOfRangeAndNeverCreatedSlots)
if (neverUsed < threadCapacity())
KT_FALSE(t, threadReap(neverUsed));
uint32 runnable = threadCreate("notDeadYet", noopThreadEntry);
KT_NE(t, runnable, threadInvalidId);
KT_FALSE(t, threadReap(runnable));
KT_TRUE(t, threadDiscard(runnable));
}
KTEST(threadJoinRejectsInvalidIdsWithoutBlocking)
{
/* All of these must return immediately (no schedBlockCurrent/halt())
since there is no live scheduler to ever wake this thread back up --
a wrong implementation here would hang the whole test battery, not
just fail an assertion. */
KT_EQ(t, threadJoin(0), -1);
KT_EQ(t, threadJoin(threadCapacity()), -1);
uint32 neverUsed = threadCount();
if (neverUsed < threadCapacity())
KT_EQ(t, threadJoin(neverUsed), -1);
}
KTEST(threadJoinOnDiscardedSlotFailsCleanly)
{
uint32 id = threadCreate("transient", noopThreadEntry);
KT_NE(t, id, threadInvalidId);
KT_TRUE(t, threadDiscard(id));
/* The slot is threadUnused again -- joining it must behave exactly like
joining an id that was never created, not crash or block. */
KT_EQ(t, threadJoin(id), -1);
}
/*
* threadCreateUser/threadReap/threadDiscard user-memory reclamation
* (Thread.userSlotBase, sched.h). Every real spawn/exec used to leak its
* whole elfSlotSize slot's worth of physical frames on death, since
* frostbyte's single shared page table is never touched by thread teardown
* -- these tests drive that path directly with a synthetic "process" (its
* own fake slot, far above any real USER_BASE any binary is linked at, so
* it can never collide with a real program's mapping) rather than relying
* only on the live boot demo (kmain.c's proctest/spawntarget/exectarget
* chain, needles "user-mem-reclaimed-ok"/"exec-old-slot-freed-ok" in
* ci-test.sh) to prove it.
*/
KTEST(threadCreateUserSetsUserSlotBaseFromEntry)
{
uint64 entry = 0x320000000ull + 0x2000;
uint32 id = threadCreateUser("probe", entry, elfSlotStackTop(entry));
KT_NE(t, id, threadInvalidId);
KT_EQU(t, threadUserSlotBase(id), elfSlotBase(entry));
KT_EQU(t, threadUserSlotBase(id), 0x320000000ull);
KT_TRUE(t, threadDiscard(id));
}
KTEST(threadReapOnDeadUserThreadReclaimsItsMappedPages)
{
uint64 fakeSlotBase = 0x330000000ull;
uint64 entry = fakeSlotBase + 0x2000;
uint64 stackTop = elfSlotStackTop(entry);
uint64 stackSize = 4 * frameSize;
uint64 before = pmmFreeFrames();
/* Mirror what a real loadImageByPath/spawnUser call maps for any user
thread: a sliver of "image" plus an eagerly-mapped stack, both well
inside the 256 MiB slot -- most of the slot stays unmapped, exactly
like a real small program. */
/* At least the image page plus the stack pages -- entry and fakeSlotBase
are virgin address space, so mapping also pays for whatever PDPT/PD/PT
page-table pages do not exist yet (paging.c's nextTable create path),
which this test does not try to predict the exact count of. The reap/
discard side below is the precise assertion: every frame spent here,
data or structural, must come all the way back. */
KT_TRUE(t, pagingMapUserRange(entry, frameSize));
KT_TRUE(t, pagingMapUserRange(stackTop - stackSize, stackSize));
KT_LE(t, pmmFreeFrames(), before - 1 - (stackSize / frameSize));
uint32 id = threadCreateUser("fakeProc", entry, stackTop);
KT_NE(t, id, threadInvalidId);
KT_EQU(t, threadUserSlotBase(id), fakeSlotBase);
/* No live scheduler to run threadExit through before schedStart() --
poke the state directly, the same workaround
schedBlockCurrentMarksBlockedWithChannel uses above for the same
reason. */
threadAt(id)->state = threadDead;
KT_TRUE(t, threadReap(id));
KT_EQU(t, pmmFreeFrames(), before);
KT_EQ(t, pagingResolve(entry), 0);
KT_EQ(t, pagingResolve(stackTop - 1), 0);
}
KTEST(threadDiscardOnNeverRunUserThreadReclaimsItsMappedPages)
{
uint64 fakeSlotBase = 0x340000000ull;
uint64 entry = fakeSlotBase + 0x2000;
uint64 stackTop = elfSlotStackTop(entry);
uint64 stackSize = 4 * frameSize;
uint64 before = pmmFreeFrames();
/* At least the image page plus the stack pages -- entry and fakeSlotBase
are virgin address space, so mapping also pays for whatever PDPT/PD/PT
page-table pages do not exist yet (paging.c's nextTable create path),
which this test does not try to predict the exact count of. The reap/
discard side below is the precise assertion: every frame spent here,
data or structural, must come all the way back. */
KT_TRUE(t, pagingMapUserRange(entry, frameSize));
KT_TRUE(t, pagingMapUserRange(stackTop - stackSize, stackSize));
KT_LE(t, pmmFreeFrames(), before - 1 - (stackSize / frameSize));
uint32 id = threadCreateUser("fakeProc2", entry, stackTop);
KT_NE(t, id, threadInvalidId);
/* Runnable, never scheduled: the threadDiscard path, not threadReap. */
KT_TRUE(t, threadDiscard(id));
KT_EQU(t, pmmFreeFrames(), before);
KT_EQ(t, pagingResolve(entry), 0);
KT_EQ(t, pagingResolve(stackTop - 1), 0);
}
KTEST(threadCreateKernelThreadHasNoUserSlotAndReapLeavesPmmUntouched)
{
uint64 before = pmmFreeFrames();
uint32 id = threadCreate("kernelOnly", noopThreadEntry);
KT_NE(t, id, threadInvalidId);
KT_EQU(t, threadUserSlotBase(id), 0);
threadAt(id)->state = threadDead;
KT_TRUE(t, threadReap(id));
KT_EQU(t, pmmFreeFrames(), before);
}
/*
* threadKill (sched.c) -- the only way to recover a thread genuinely stuck
* forever, now that sysRead/sysWrite can really block on a shared pipe
* (pipe.h/vfs.h). The live proof that it tears down a thread ACTUALLY
* parked mid-syscall is killtest.c spawning killvictim.c (needles
* "killtest: killed victim"/"killtest: kill-ok" in tools/ci-test.sh); what
* is deterministic here is threadKill's own state-machine bookkeeping.
*/
KTEST(threadKillRejectsIdleOutOfRangeAndNeverCreatedSlots)
{
KT_FALSE(t, threadKill(0));
KT_FALSE(t, threadKill(threadCapacity()));
KT_FALSE(t, threadKill(threadCapacity() + 1000));
uint32 neverUsed = threadCount();
if (neverUsed < threadCapacity())
KT_FALSE(t, threadKill(neverUsed));
}
KTEST(threadKillMarksRunnableThreadDeadWithKilledExitCode)
{
uint32 id = threadCreate("killMe", noopThreadEntry);
KT_NE(t, id, threadInvalidId);