test: extend coverage of schedTest
This commit is contained in:
1 parent
bf31b363a6
commit
d992d971e7
1 file changed
+158
+158
@@ -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);
|
||||
|
||||
Reference in new issue
Block a user