test: strengthen tests for schedTest
This commit is contained in:
1 parent
67d097e51b
commit
faaf04fc8c
1 file changed
+155
+155
@@ -629,3 +629,158 @@ KTEST(threadKillAgainstAnOrphanWithADeadParentIdRaisesNothing)
|
||||
KT_EQ(t, threadAt(orphan)->state, threadDead);
|
||||
KT_EQU(t, threadAt(deadParent)->sigPending & (1ull << sigChld), 0ull);
|
||||
|
||||
KT_TRUE(t, threadReap(deadParent));
|
||||
KT_TRUE(t, threadReap(orphan));
|
||||
}
|
||||
|
||||
/*
|
||||
* threadJoinAnyChild (sched.c) -- waitpid(-1)-style "wait for ANY child"
|
||||
* behind sysWait's waitAnyPid case (syscall.c) / userWaitAny (ulib.c). Live
|
||||
* proof that it genuinely blocks and is genuinely woken by an arbitrary
|
||||
* child's death (not just a specifically-named one) is user/waitanytest.c
|
||||
* (needle "waitanytest: any-child-reap-set-ok" in tools/ci-test.sh); what
|
||||
* is deterministic here is its own non-blocking scan logic, the same shape
|
||||
* as the parentId/sigChld block above.
|
||||
*/
|
||||
|
||||
KTEST(threadJoinAnyChildReturnsNoChildrenForAThreadWithNoneAtAll)
|
||||
{
|
||||
uint32 parent = threadCreate("anyNoKids", noopThreadEntry);
|
||||
KT_NE(t, parent, threadInvalidId);
|
||||
|
||||
uint32 outId = 0;
|
||||
int64 outCode = 0;
|
||||
KT_EQ(t, threadJoinAnyChild(parent, &outId, &outCode), waitScanNoChildren);
|
||||
|
||||
KT_TRUE(t, threadDiscard(parent));
|
||||
}
|
||||
|
||||
KTEST(threadJoinAnyChildReturnsNoneDeadWhileAllChildrenAreStillAlive)
|
||||
{
|
||||
uint32 parent = threadCreate("anyLiveParent", noopThreadEntry);
|
||||
KT_NE(t, parent, threadInvalidId);
|
||||
uint32 child = threadCreate("anyLiveChild", noopThreadEntry);
|
||||
KT_NE(t, child, threadInvalidId);
|
||||
threadAt(child)->parentId = parent;
|
||||
|
||||
uint32 outId = 0;
|
||||
int64 outCode = 0;
|
||||
KT_EQ(t, threadJoinAnyChild(parent, &outId, &outCode), waitScanNoneDead);
|
||||
/* Not reaped -- still exactly as threadCreate left it. */
|
||||
KT_EQ(t, threadAt(child)->state, threadRunnable);
|
||||
|
||||
KT_TRUE(t, threadDiscard(child));
|
||||
KT_TRUE(t, threadDiscard(parent));
|
||||
}
|
||||
|
||||
KTEST(threadJoinAnyChildPicksTheLowestIndexedDeadChildAndCanBeCalledAgainForTheRest)
|
||||
{
|
||||
uint32 parent = threadCreate("anyTwoKidsParent", noopThreadEntry);
|
||||
KT_NE(t, parent, threadInvalidId);
|
||||
uint32 childLow = threadCreate("anyChildLow", noopThreadEntry);
|
||||
KT_NE(t, childLow, threadInvalidId);
|
||||
uint32 childHigh = threadCreate("anyChildHigh", noopThreadEntry);
|
||||
KT_NE(t, childHigh, threadInvalidId);
|
||||
/* allocSlot (sched.c) always hands out the lowest currently-free slot
|
||||
and nothing else has been created in between, so childHigh's own id
|
||||
is guaranteed higher than childLow's -- the "lowest index wins"
|
||||
assertion below actually depends on this. */
|
||||
KT_LT(t, childLow, childHigh);
|
||||
threadAt(childLow)->parentId = parent;
|
||||
threadAt(childHigh)->parentId = parent;
|
||||
|
||||
/* Kill the HIGHER-indexed one first, on purpose: proves the scan below
|
||||
picks the lowest matching slot, not whichever died most recently. */
|
||||
KT_TRUE(t, threadKill(childHigh));
|
||||
KT_TRUE(t, threadKill(childLow));
|
||||
|
||||
uint32 outId = 0xFFFFFFFFu;
|
||||
int64 outCode = -1;
|
||||
KT_EQ(t, threadJoinAnyChild(parent, &outId, &outCode), waitScanFoundDead);
|
||||
KT_EQU(t, outId, childLow);
|
||||
KT_EQ(t, outCode, threadKilledExitCode);
|
||||
/* childLow is fully reaped already -- its slot is threadUnused now. */
|
||||
KT_EQ(t, threadAt(childLow)->state, threadUnused);
|
||||
/* childHigh is untouched by that call: still Dead, NOT yet reaped. */
|
||||
KT_EQ(t, threadAt(childHigh)->state, threadDead);
|
||||
|
||||
outId = 0xFFFFFFFFu;
|
||||
outCode = -1;
|
||||
KT_EQ(t, threadJoinAnyChild(parent, &outId, &outCode), waitScanFoundDead);
|
||||
KT_EQU(t, outId, childHigh);
|
||||
KT_EQ(t, outCode, threadKilledExitCode);
|
||||
|
||||
KT_EQ(t, threadJoinAnyChild(parent, &outId, &outCode), waitScanNoChildren);
|
||||
|
||||
KT_TRUE(t, threadDiscard(parent));
|
||||
}
|
||||
|
||||
KTEST(threadKillWakesAParentGenuinelyBlockedInWaitAnyOnAnyChildDeath)
|
||||
{
|
||||
uint32 parent = threadCreate("anyBlockedParent", noopThreadEntry);
|
||||
KT_NE(t, parent, threadInvalidId);
|
||||
uint32 child = threadCreate("anyBlockedChild", noopThreadEntry);
|
||||
KT_NE(t, child, threadInvalidId);
|
||||
threadAt(child)->parentId = parent;
|
||||
|
||||
/* Fabricate exactly what sysWait's waitAnyPid path leaves behind via
|
||||
schedBlockCurrent(currentThread()) when parent itself is the one
|
||||
calling it (see threadStopExcludesTargetFromUnrelatedWaitChannelWake
|
||||
below for the same white-box convention) -- channel is the PARENT's
|
||||
own slot address, not the child's, unlike
|
||||
threadKillWakesAThreadParkedInJoin's channel above. */
|
||||
threadAt(parent)->state = threadBlocked;
|
||||
threadAt(parent)->waitChannel = threadAt(parent);
|
||||
|
||||
KT_TRUE(t, threadKill(child));
|
||||
KT_EQ(t, threadAt(parent)->state, threadRunnable);
|
||||
|
||||
KT_TRUE(t, threadReap(child));
|
||||
KT_TRUE(t, threadDiscard(parent));
|
||||
}
|
||||
|
||||
/*
|
||||
* threadStop/threadContinue (sched.c) -- job control (signal.h's SIGSTOP/
|
||||
* SIGCONT/SIGTSTP) on top of the new threadStopped state. The live proof
|
||||
* that SIGSTOP genuinely stops a thread parked mid-syscall and SIGCONT
|
||||
* genuinely resumes it through the exact restart-syscall path sysRead
|
||||
* already uses is user/jobtest.c spawning user/jobtarget.c (needle
|
||||
* "jobtest: job-stop-continue-ok" in tools/ci-test.sh); what is
|
||||
* deterministic here is threadStop/threadContinue's own state-machine
|
||||
* bookkeeping, mirroring threadKillRejects.../threadKillMarks... above.
|
||||
*/
|
||||
|
||||
KTEST(threadStopRejectsIdleOutOfRangeAndNeverCreatedSlots)
|
||||
{
|
||||
KT_FALSE(t, threadStop(0));
|
||||
KT_FALSE(t, threadStop(threadCapacity()));
|
||||
KT_FALSE(t, threadStop(threadCapacity() + 1000));
|
||||
|
||||
uint32 neverUsed = threadCount();
|
||||
if (neverUsed < threadCapacity())
|
||||
KT_FALSE(t, threadStop(neverUsed));
|
||||
}
|
||||
|
||||
KTEST(threadStopMovesRunnableThreadToStoppedAndIsIdempotent)
|
||||
{
|
||||
uint32 id = threadCreate("stopMe", noopThreadEntry);
|
||||
KT_NE(t, id, threadInvalidId);
|
||||
|
||||
KT_EQ(t, threadAt(id)->state, threadRunnable);
|
||||
KT_TRUE(t, threadStop(id));
|
||||
KT_EQ(t, threadAt(id)->state, threadStopped);
|
||||
|
||||
/* Idempotent: stopping an already-Stopped thread still succeeds,
|
||||
matching real kill(pid, SIGSTOP) sent a second time. */
|
||||
KT_TRUE(t, threadStop(id));
|
||||
KT_EQ(t, threadAt(id)->state, threadStopped);
|
||||
|
||||
KT_TRUE(t, threadContinue(id));
|
||||
KT_TRUE(t, threadDiscard(id));
|
||||
}
|
||||
|
||||
KTEST(threadStopAcceptsSleepingBlockedAndRunningStates)
|
||||
{
|
||||
uint32 id = threadCreate("stopFromEveryState", noopThreadEntry);
|
||||
KT_NE(t, id, threadInvalidId);
|
||||
|
||||
Reference in new issue
Block a user