test: extend coverage of sigTest

This commit is contained in:
dualfroz committed 2026-09-12 16:39:30 +02:00
1 parent 0419e31b12
commit a4a523ed11
1 file changed
+150
+150
View File
@@ -1095,3 +1095,153 @@ KTEST(sigConsumePendingLeavesRsiRdxUntouchedWithoutSigFlagSigInfo)
int signum = -1; int signum = -1;
KT_EQ(t, sigConsumePending(thread, &regs, &signum), sigOutcomeHandler); KT_EQ(t, sigConsumePending(thread, &regs, &signum), sigOutcomeHandler);
/* Untouched, exactly as documented -- a handler with no
sigFlagSigInfo never reads either register, so sigConsumePending
must not spend any effort computing values for them. */
KT_EQU(t, regs.rsi, 0x5151515151515151ull);
KT_EQU(t, regs.rdx, 0x6262626262626262ull);
KT_TRUE(t, threadDiscard(id));
}
KTEST(sigRaisePlainWrapperDefaultsToSiCodeKernelWithNoSenderOrStatus)
{
uint32 id = threadCreate("sigInfoDefaultTarget", noopThreadEntry);
KT_NE(t, id, threadInvalidId);
Thread* thread = threadAt(id);
sigSetHandler(thread, sigHup, 0x40009000ull, 0, sigFlagSigInfo);
/* The plain, pre-existing sigRaise -- every one of this file's other
~50 call sites uses exactly this, and none of them supply detail,
so sigRaise's own thin-wrapper default (siCodeKernel/0/0) is what
they all still get. */
KT_TRUE(t, sigRaise(id, sigHup));
Registers regs = makeUserRegs();
int signum = -1;
KT_EQ(t, sigConsumePending(thread, &regs, &signum), sigOutcomeHandler);
SigInfo* info = (SigInfo*)regs.rsi;
KT_EQ(t, info->siSignum, sigHup);
KT_EQ(t, info->siCode, siCodeKernel);
KT_EQ(t, info->siPid, 0);
KT_EQ(t, info->siStatus, 0);
KT_TRUE(t, threadDiscard(id));
}
KTEST(sigRaiseInfoOverwritesStalePendingInfoOnReRaiseBeforeDelivery)
{
uint32 id = threadCreate("sigInfoOverwriteTarget", noopThreadEntry);
KT_NE(t, id, threadInvalidId);
Thread* thread = threadAt(id);
sigSetHandler(thread, sigUsr2, 0x40009000ull, 0, sigFlagSigInfo);
KT_TRUE(t, sigRaiseInfo(id, sigUsr2, siCodeUser, 111, 1));
/* Same signum raised again before it was ever delivered -- non-queuing
signals coalesce, so only the SECOND raise's detail should ever be
observable, matching sigPending itself staying a single bit rather
than a count. */
KT_TRUE(t, sigRaiseInfo(id, sigUsr2, siCodeUser, 222, 2));
Registers regs = makeUserRegs();
int signum = -1;
KT_EQ(t, sigConsumePending(thread, &regs, &signum), sigOutcomeHandler);
SigInfo* info = (SigInfo*)regs.rsi;
KT_EQ(t, info->siPid, 222);
KT_EQ(t, info->siStatus, 2);
KT_TRUE(t, threadDiscard(id));
}
/*
* sigRaiseGroup (signal.h) -- the group-targeted half of real kill(2)'s
* pid<0/pid==0 convention (syscall.c's sysSigSend) and keyboard.c's own
* real Ctrl-C/Ctrl-Z, delivered to the FOREGROUND process group
* (sched.h's Thread.pgid) rather than one hardcoded target. The live
* ring-3 proof that this scoping actually protects a DIFFERENT group from
* a keyboard-generated signal is user/pgroupfg.c (killed by it) versus
* user/pgroupbg.c (proven still alive afterwards) in kmain.c's boot
* sequence; what is deterministic here is the plain fan-out-and-filter
* logic itself, mirroring schedTest.c's threadKillGroup coverage for the
* exact same shape of primitive one layer down.
*/
KTEST(sigRaiseGroupDeliversToEveryLiveMemberOfThatGroupOnly)
{
uint32 a = threadCreate("sigGroupA", noopThreadEntry);
uint32 b = threadCreate("sigGroupB", noopThreadEntry);
uint32 outsider = threadCreate("sigGroupOutsider", 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, sigRaiseGroup(pgid, sigUsr1, siCodeKernel, 0));
KT_EQU(t, threadAt(a)->sigPending & (1ull << sigUsr1), (1ull << sigUsr1));
KT_EQU(t, threadAt(b)->sigPending & (1ull << sigUsr1), (1ull << sigUsr1));
/* The whole point: a DIFFERENT group must be completely untouched --
this is what keeps a real Ctrl-C from ever reaching a background
job. */
KT_EQU(t, threadAt(outsider)->sigPending, 0ull);
KT_TRUE(t, threadDiscard(a));
KT_TRUE(t, threadDiscard(b));
KT_TRUE(t, threadDiscard(outsider));
}
KTEST(sigRaiseGroupReturnsFalseWhenNoLiveThreadHasThatPgid)
{
KT_FALSE(t, sigRaiseGroup(0xBEEF0001u, sigUsr1, siCodeKernel, 0));
}
KTEST(sigRaiseGroupSkipsDeadOrUnusedMembersButStillFindsLiveOnes)
{
uint32 a = threadCreate("sigGroupDeadA", noopThreadEntry);
uint32 b = threadCreate("sigGroupDeadB", noopThreadEntry);
KT_NE(t, a, threadInvalidId);
KT_NE(t, b, threadInvalidId);
uint32 pgid = threadAt(a)->pgid;
KT_TRUE(t, threadSetPgid(b, pgid));
KT_TRUE(t, threadKill(b));
KT_TRUE(t, sigRaiseGroup(pgid, sigUsr2, siCodeKernel, 0));
KT_EQU(t, threadAt(a)->sigPending & (1ull << sigUsr2), (1ull << sigUsr2));
/* Dead members are silently skipped, exactly like sigRaiseInfo's own
Dead/Unused guard already refuses a single-target send against one
-- no crash, no stale sigPending bit written into a slot that is no
longer this thread's own. */
KT_EQU(t, threadAt(b)->sigPending, 0ull);
KT_TRUE(t, threadDiscard(a));
KT_TRUE(t, threadReap(b));
}
KTEST(sigRaiseGroupWithSigStopStopsEveryLiveMemberOfThatGroup)
{
uint32 a = threadCreate("sigGroupStopA", noopThreadEntry);
uint32 b = threadCreate("sigGroupStopB", noopThreadEntry);
KT_NE(t, a, threadInvalidId);
KT_NE(t, b, threadInvalidId);
uint32 pgid = threadAt(a)->pgid;
KT_TRUE(t, threadSetPgid(b, pgid));
/* sigStop is delegated straight to threadStop per-target, bypassing
sigPending entirely -- the same bypass sigRaiseInfo's own file
comment documents for a single-target sigStop, now proven to apply
independently to every member the group fan-out visits. */
KT_TRUE(t, sigRaiseGroup(pgid, sigStop, siCodeKernel, 0));
KT_EQ(t, threadAt(a)->state, threadStopped);
KT_EQ(t, threadAt(b)->state, threadStopped);
KT_TRUE(t, threadContinue(a));
KT_TRUE(t, threadContinue(b));
KT_TRUE(t, threadDiscard(a));
KT_TRUE(t, threadDiscard(b));
}