test: cover schedTest

This commit is contained in:
dualfroz committed 2026-09-11 15:04:25 +02:00
1 parent c31ac87199
commit 6c6c8d8b1a
1 file changed
+153
+153
View File
@@ -942,3 +942,156 @@ KTEST(threadStopEarlyWakesAParentGenuinelyBlockedWaitingForSigChldOnStop)
KT_NE(t, child, threadInvalidId); KT_NE(t, child, threadInvalidId);
threadAt(child)->parentId = parent; threadAt(child)->parentId = parent;
/* Same real-handler-required setup as
threadKillEarlyWakesAParentGenuinelyBlockedWaitingForSigChld above --
sigChld's default disposition is Ignore, so sigWouldDeliver would
(correctly) decline to early-wake without one. */
sigSetHandler(threadAt(parent), sigChld, 0x40005000ull, 0, 0);
threadAt(parent)->state = threadBlocked;
threadAt(parent)->waitChannel = &channel;
KT_TRUE(t, threadStop(child));
KT_EQ(t, threadAt(parent)->state, threadRunnable);
KT_TRUE(t, threadContinue(child));
KT_TRUE(t, threadDiscard(child));
KT_TRUE(t, threadDiscard(parent));
}
KTEST(threadContinueRaisesSigChldAgainstParentIdOnTheActualResumeTransition)
{
uint32 parent = threadCreate("contChldParent", noopThreadEntry);
KT_NE(t, parent, threadInvalidId);
uint32 child = threadCreate("contChldVictim", noopThreadEntry);
KT_NE(t, child, threadInvalidId);
threadAt(child)->parentId = parent;
KT_TRUE(t, threadStop(child));
/* Drain the stop's own notification first (see the "already stopped"
test above for why) so the assertion below is unambiguously about
threadContinue's OWN raise, not a leftover bit from the stop. */
threadAt(parent)->sigPending &= ~(1ull << sigChld);
KT_TRUE(t, threadContinue(child));
KT_EQU(t, threadAt(parent)->sigPending & (1ull << sigChld), (1ull << sigChld));
KT_TRUE(t, threadDiscard(child));
KT_TRUE(t, threadDiscard(parent));
}
KTEST(threadContinueOnATargetThatWasNeverStoppedRaisesNothing)
{
uint32 parent = threadCreate("contChldParent2", noopThreadEntry);
KT_NE(t, parent, threadInvalidId);
uint32 child = threadCreate("contChldVictim2", noopThreadEntry);
KT_NE(t, child, threadInvalidId);
threadAt(child)->parentId = parent;
KT_EQ(t, threadAt(child)->state, threadRunnable);
KT_FALSE(t, threadContinue(child));
KT_EQU(t, threadAt(parent)->sigPending & (1ull << sigChld), 0ull);
KT_TRUE(t, threadDiscard(child));
KT_TRUE(t, threadDiscard(parent));
}
KTEST(threadStopAgainstAnOrphanWithADeadParentIdRaisesNothing)
{
uint32 deadParent = threadCreate("stopDeadParent", noopThreadEntry);
KT_NE(t, deadParent, threadInvalidId);
KT_TRUE(t, threadKill(deadParent));
KT_EQ(t, threadAt(deadParent)->state, threadDead);
uint32 orphan = threadCreate("stopOrphan", noopThreadEntry);
KT_NE(t, orphan, threadInvalidId);
threadAt(orphan)->parentId = deadParent;
/* Same sigRaise targetId-state guard as
threadKillAgainstAnOrphanWithADeadParentIdRaisesNothing above -- an
orphan whose spawning parent already died has nowhere for its own
stop notification to land either. */
KT_TRUE(t, threadStop(orphan));
KT_EQ(t, threadAt(orphan)->state, threadStopped);
KT_EQU(t, threadAt(deadParent)->sigPending & (1ull << sigChld), 0ull);
KT_TRUE(t, threadContinue(orphan));
KT_TRUE(t, threadReap(deadParent));
KT_TRUE(t, threadDiscard(orphan));
}
KTEST(threadWakeForSignalRejectsIdleOutOfRangeAndNeverCreatedSlots)
{
KT_FALSE(t, threadWakeForSignal(0));
KT_FALSE(t, threadWakeForSignal(threadCapacity()));
KT_FALSE(t, threadWakeForSignal(threadCapacity() + 1000));
uint32 neverUsed = threadCount();
if (neverUsed < threadCapacity())
KT_FALSE(t, threadWakeForSignal(neverUsed));
}
KTEST(threadWakeForSignalMovesSleepingOrBlockedThreadToRunnable)
{
uint32 id = threadCreate("wakeFromSleep", noopThreadEntry);
KT_NE(t, id, threadInvalidId);
threadAt(id)->state = threadSleeping;
KT_TRUE(t, threadWakeForSignal(id));
KT_EQ(t, threadAt(id)->state, threadRunnable);
threadAt(id)->state = threadBlocked;
KT_TRUE(t, threadWakeForSignal(id));
KT_EQ(t, threadAt(id)->state, threadRunnable);
KT_TRUE(t, threadDiscard(id));
}
KTEST(threadWakeForSignalIsNoOpOnRunnableRunningStoppedDeadAndUnused)
{
uint32 id = threadCreate("wakeNoOp", noopThreadEntry);
KT_NE(t, id, threadInvalidId);
/* Already Runnable/Running: nothing to force. */
KT_EQ(t, threadAt(id)->state, threadRunnable);
KT_FALSE(t, threadWakeForSignal(id));
KT_EQ(t, threadAt(id)->state, threadRunnable);
threadAt(id)->state = threadRunning;
KT_FALSE(t, threadWakeForSignal(id));
KT_EQ(t, threadAt(id)->state, threadRunning);
/* Stopped is the one deliberately EXCLUDED state -- an ordinary signal
must never undo job control's SIGSTOP; only threadContinue (an
explicit SIGCONT) may ever move a Stopped thread. This is what
signal.c's sigRaise relies on for a signal raised against a Stopped
target: sigWouldDeliver may say yes, but threadWakeForSignal still
refuses. */
KT_TRUE(t, threadStop(id));
KT_FALSE(t, threadWakeForSignal(id));
KT_EQ(t, threadAt(id)->state, threadStopped);
KT_TRUE(t, threadContinue(id));
threadAt(id)->state = threadDead;
KT_FALSE(t, threadWakeForSignal(id));
KT_TRUE(t, threadReap(id));
/* threadUnused again after reap: still rejected. */
KT_FALSE(t, threadWakeForSignal(id));
}
KTEST(threadKillAcceptsStoppedThreadLikeRealSigkillOnAStoppedProcess)
{
uint32 id = threadCreate("killStopped", noopThreadEntry);
KT_NE(t, id, threadInvalidId);
KT_TRUE(t, threadStop(id));
KT_EQ(t, threadAt(id)->state, threadStopped);
KT_TRUE(t, threadKill(id));
KT_EQ(t, threadAt(id)->state, threadDead);
KT_EQU(t, threadAt(id)->exitCode, threadKilledExitCode);
KT_TRUE(t, threadReap(id));
}