test: strengthen tests for schedTest

This commit is contained in:
dualfroz committed 2026-09-11 16:55:14 +02:00
1 parent 86ef404a1f
commit 0d5554866e
1 file changed
+159
+159
View File
@@ -1411,3 +1411,162 @@ KTEST(threadSetPriorityChangesWeightOfALiveThread)
uint32 id = threadCreate("priorityChange", noopThreadEntry); uint32 id = threadCreate("priorityChange", noopThreadEntry);
KT_NE(t, id, threadInvalidId); KT_NE(t, id, threadInvalidId);
KT_TRUE(t, threadSetPriority(id, 4096));
KT_EQU(t, threadPriority(id), 4096u);
KT_TRUE(t, threadDiscard(id));
}
KTEST(threadSetPriorityRejectsDeadOrUnusedTarget)
{
uint32 id = threadCreate("prioritySetOnDead", noopThreadEntry);
KT_NE(t, id, threadInvalidId);
KT_TRUE(t, threadKill(id));
KT_FALSE(t, threadSetPriority(id, 4096));
KT_TRUE(t, threadReap(id));
KT_FALSE(t, threadSetPriority(id, 4096));
}
KTEST(threadKillGroupKillsEveryLiveMemberAndLeavesOtherGroupsAlone)
{
uint32 a = threadCreate("groupKillA", noopThreadEntry);
uint32 b = threadCreate("groupKillB", noopThreadEntry);
uint32 outsider = threadCreate("groupKillOutsider", noopThreadEntry);
KT_NE(t, a, threadInvalidId);
KT_NE(t, b, threadInvalidId);
KT_NE(t, outsider, threadInvalidId);
uint32 pgid = threadAt(a)->pgid;
KT_TRUE(t, threadSetPgid(b, pgid));
KT_NE(t, threadAt(outsider)->pgid, pgid);
KT_TRUE(t, threadKillGroup(pgid));
KT_EQ(t, threadAt(a)->state, threadDead);
KT_EQ(t, threadAt(b)->state, threadDead);
/* A completely different group must be untouched -- the exact scoping
property keyboard.c's real Ctrl-C/Ctrl-Z depends on (user/pgroupfg.c/
user/pgroupbg.c is the live ring-3 proof for sigRaiseGroup's own
version of this same scoping, signal.c). */
KT_EQ(t, threadAt(outsider)->state, threadRunnable);
KT_TRUE(t, threadReap(a));
KT_TRUE(t, threadReap(b));
KT_TRUE(t, threadDiscard(outsider));
}
KTEST(threadKillGroupReturnsFalseWhenNoLiveThreadHasThatPgid)
{
/* No thread created anywhere in this whole KTEST run ever uses this
exact value as its pgid -- ids never reach anywhere near it (see
threadCapacity/maxThreads), so it can never collide with a real
group by coincidence. */
KT_FALSE(t, threadKillGroup(0xBEEF0000u));
}
KTEST(threadKillGroupSkipsAlreadyDeadMembersButStillReportsTheGroupAsFound)
{
uint32 a = threadCreate("groupKillDeadA", noopThreadEntry);
uint32 b = threadCreate("groupKillDeadB", noopThreadEntry);
KT_NE(t, a, threadInvalidId);
KT_NE(t, b, threadInvalidId);
uint32 pgid = threadAt(a)->pgid;
KT_TRUE(t, threadSetPgid(b, pgid));
/* b is killed FIRST, independently -- threadKillGroup must still find
and kill a (already-Dead members are simply skipped, exactly like
sigRaiseGroup below skips them via sigRaiseInfo's own Dead/Unused
guard), and must still report true since a real target existed. */
KT_TRUE(t, threadKill(b));
KT_TRUE(t, threadKillGroup(pgid));
KT_EQ(t, threadAt(a)->state, threadDead);
KT_TRUE(t, threadReap(a));
KT_TRUE(t, threadReap(b));
}
/* currentThreadGetEnv/currentThreadSetEnv (sched.h's new Thread.env) always
act on whoever is "current" -- at KTEST time that is the idle thread
(id 0, same "always-present current thread" reasoning this file's own
header comment already gives for schedBlockCurrent above), so every test
below resets threadAt(0)->envCount to 0 first: idle's own table is pure
scratch space for these tests (initialEnv gives every REAL boot-time
spawn an empty table regardless of whatever idle's own happens to hold,
see initialEnv's own "currentIndex == 0" branch, sched.c), but leaving
it dirty between tests would let an earlier test's entries leak into a
later one's count/index assumptions. */
KTEST(currentThreadSetEnvThenGetEnvRoundTrips)
{
threadAt(0)->envCount = 0;
KT_TRUE(t, currentThreadSetEnv("FOO", "bar"));
KT_STREQ(t, currentThreadGetEnv("FOO"), "bar");
KT_NULL(t, currentThreadGetEnv("MISSING"));
}
KTEST(currentThreadSetEnvOverwritesExistingNameInPlace)
{
threadAt(0)->envCount = 0;
KT_TRUE(t, currentThreadSetEnv("FOO", "bar"));
KT_TRUE(t, currentThreadSetEnv("FOO", "baz"));
KT_STREQ(t, currentThreadGetEnv("FOO"), "baz");
/* An overwrite must never consume a second slot. */
KT_EQ(t, currentThreadEnvCount(), 1);
}
KTEST(currentThreadSetEnvRejectsEntryTooLongForStorage)
{
threadAt(0)->envCount = 0;
/* envEntryMax (sched.h) bounds "NAME=VALUE\0" together -- a value long
enough to blow that budget must be rejected outright, leaving no
partially-written entry behind. */
char hugeValue[envEntryMax];
for (int i = 0; i < envEntryMax - 1; i++)
hugeValue[i] = 'x';
hugeValue[envEntryMax - 1] = 0;
KT_FALSE(t, currentThreadSetEnv("FOO", hugeValue));
KT_NULL(t, currentThreadGetEnv("FOO"));
KT_EQ(t, currentThreadEnvCount(), 0);
}
KTEST(currentThreadSetEnvRejectsNewNameOnceTableIsFull)
{
threadAt(0)->envCount = 0;
char name[8];
for (int i = 0; i < envMaxVars; i++) {
snprintf(name, sizeof(name), "V%d", i);
KT_TRUE(t, currentThreadSetEnv(name, "x"));
}
KT_EQ(t, currentThreadEnvCount(), envMaxVars);
KT_FALSE(t, currentThreadSetEnv("ONE_TOO_MANY", "x"));
KT_EQ(t, currentThreadEnvCount(), envMaxVars);
/* Overwriting an EXISTING name must still work even while the table is
completely full -- it takes the already-occupied slot, never a new
one. */
KT_TRUE(t, currentThreadSetEnv("V0", "changed"));
KT_STREQ(t, currentThreadGetEnv("V0"), "changed");
KT_EQ(t, currentThreadEnvCount(), envMaxVars);
threadAt(0)->envCount = 0;
}
KTEST(currentThreadEnvEntryExposesRawStorageForListing)
{
threadAt(0)->envCount = 0;
KT_TRUE(t, currentThreadSetEnv("A", "1"));
KT_TRUE(t, currentThreadSetEnv("B", "2"));
KT_EQ(t, currentThreadEnvCount(), 2);
KT_STREQ(t, currentThreadEnvEntry(0), "A=1");
KT_STREQ(t, currentThreadEnvEntry(1), "B=2");
KT_NULL(t, currentThreadEnvEntry(2));
KT_NULL(t, currentThreadEnvEntry(-1));