diff --git a/src/pollTest.c b/src/pollTest.c new file mode 100644 index 0000000..d4a2840 --- /dev/null +++ b/src/pollTest.c @@ -0,0 +1,146 @@ +#include "ktest.h" +#include "poll.h" +#include "vfs.h" +#include "pipe.h" + +/* + * Pure, scheduler-free coverage for vfsPollReady (vfs.c) and its two + * non-consuming peek helpers (pipeHasData/pipeHasSpace, pipe.h; + * keyboardReaderHasData, keyboard.h) -- everything that can be driven + * single-threaded, exactly like pipeShareTest.c/vfsFifoTest.c already do + * for the machinery poll.h's sysPoll is built on top of. The genuine + * cross-process block-then-wake handshake (schedule()'s own pollDeadline + * expiry scan, sched.c; the shared schedPollChannel wake from a SECOND, + * independently scheduled writer) only runs for real at boot + * (user/polltest.c + user/pollwriter.c, needles in tools/ci-test.sh) -- + * the same division of labor this file's neighbors already use, and + * schedTest.c's own doc comment explains why: ktestRunAll() runs before + * schedStart(), with no second execution context to switch into. + */ + +KTEST(vfsPollReadyReportsNvalForClosedOrOutOfRangeFd) +{ + int16 revents = 99; + KT_TRUE(t, vfsPollReady(-1, pollIn, &revents)); + KT_EQ(t, revents, pollNval); + + revents = 99; + KT_TRUE(t, vfsPollReady(9999, pollIn | pollOut, &revents)); + KT_EQ(t, revents, pollNval); +} + +KTEST(vfsPollReadyPipeReadEndTracksRealData) +{ + int readFd, writeFd; + KT_EQ(t, vfsPipe(&readFd, &writeFd), 0); + + int16 revents = 99; + KT_FALSE(t, vfsPollReady(readFd, pollIn, &revents)); + KT_EQ(t, revents, 0); + + KT_EQ(t, vfsWrite(writeFd, "hi", 2), 2); + + revents = 0; + KT_TRUE(t, vfsPollReady(readFd, pollIn, &revents)); + KT_EQ(t, revents, pollIn); + + /* Asking for pollOut on a read-only end is never satisfiable, no + matter how much data is queued. */ + revents = 99; + KT_FALSE(t, vfsPollReady(readFd, pollOut, &revents)); + KT_EQ(t, revents, 0); + + char buffer[8]; + KT_EQ(t, vfsRead(readFd, buffer, sizeof(buffer)), 2); + vfsClose(readFd); + vfsClose(writeFd); +} + +KTEST(vfsPollReadyPipeWriteEndTracksFreeSpaceThenFull) +{ + int readFd, writeFd; + KT_EQ(t, vfsPipe(&readFd, &writeFd), 0); + + int16 revents = 0; + KT_TRUE(t, vfsPollReady(writeFd, pollOut, &revents)); + KT_EQ(t, revents, pollOut); + + /* Fill the ring completely (pipeBufferSize, pipe.h) with non-blocking + writes -- vfsPipe's ends are exactly the unshared, "-1 means try + again" kind pipetest.c already depends on, so this can never park + the calling (only) thread the way a fdFlagBlocking end would. */ + char chunk[64]; + for (int i = 0; i < 64; i++) + chunk[i] = (char)i; + int64 written = 0; + for (int guard = 0; guard < 16; guard++) { + int64 got = vfsWrite(writeFd, chunk, sizeof(chunk)); + if (got <= 0) + break; + written += got; + } + KT_TRUE(t, written > 0); + KT_EQ(t, vfsWrite(writeFd, chunk, sizeof(chunk)), -1); + + revents = 99; + KT_FALSE(t, vfsPollReady(writeFd, pollOut, &revents)); + KT_EQ(t, revents, 0); + + /* Draining even a little frees space back up. */ + char sink[16]; + KT_EQ(t, vfsRead(readFd, sink, sizeof(sink)), 16); + revents = 0; + KT_TRUE(t, vfsPollReady(writeFd, pollOut, &revents)); + KT_EQ(t, revents, pollOut); + + vfsClose(readFd); + vfsClose(writeFd); +} + +KTEST(vfsPollReadyReadEndReportsEofAfterWriterCloses) +{ + int readFd, writeFd; + KT_EQ(t, vfsPipe(&readFd, &writeFd), 0); + + int16 revents = 0; + KT_FALSE(t, vfsPollReady(readFd, pollIn, &revents)); + + vfsClose(writeFd); + + /* A true EOF pipe is "readable" in real poll(2) too -- the next read + call returning 0 immediately is exactly what pollIn promises here. */ + revents = 0; + KT_TRUE(t, vfsPollReady(readFd, pollIn, &revents)); + KT_EQ(t, revents, pollIn); + char probe[4]; + KT_EQ(t, vfsRead(readFd, probe, sizeof(probe)), 0); + + vfsClose(readFd); +} + +KTEST(vfsPollReadyConsoleWriteAlwaysReadyReadNeverWithoutInput) +{ + /* fd 0 (console) is already live for the current (idle) thread by the + time ktestRunAll() runs -- schedInit() calls vfsFdInit before + kmain.c ever reaches ktestRunAll(). Every open reader (idle's fd 0 + included) gets a copy of every keystroke keyboardHandleScancode ever + broadcasts (keyboard.h), including the synthetic ones OTHER test + files inject (keyboardTest.c's own coverage) -- KTEST execution + order across files is whatever the ".ktests" linker section happens + to lay them out in, so idle's fd 0 must be drained here rather than + assumed empty, or this test's own result would depend on that order. */ + char drain[16]; + while (vfsRead(0, drain, sizeof(drain)) > 0) { } + + int16 revents = 99; + KT_FALSE(t, vfsPollReady(0, pollIn, &revents)); + KT_EQ(t, revents, 0); + + revents = 0; + KT_TRUE(t, vfsPollReady(0, pollOut, &revents)); + KT_EQ(t, revents, pollOut); + + revents = 0; + KT_TRUE(t, vfsPollReady(0, pollIn | pollOut, &revents)); + KT_EQ(t, revents, pollOut); +}