From 3009fe6f807b793438558ce42cc7632ba09d5842 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Thu, 10 Sep 2026 19:49:02 +0200 Subject: [PATCH] test: cover pipeBlockTest --- src/pipeBlockTest.c | 113 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 src/pipeBlockTest.c diff --git a/src/pipeBlockTest.c b/src/pipeBlockTest.c new file mode 100644 index 0000000..512432e --- /dev/null +++ b/src/pipeBlockTest.c @@ -0,0 +1,113 @@ +#include "ktest.h" +#include "pipe.h" +#include "string.h" + +/* + * pipeReadBlocking/pipeWriteBlocking only return without parking the thread + * when the very first attempt already resolves (data/space/EOF/broken-pipe + * already true) - any scenario that would genuinely need to park calls + * halt(), which requires a live, interrupt-driven scheduler to ever resume + * from. ktestRunAll() runs before schedStart() (see ktest.h) on the sole + * execution context there is, so these tests deliberately only cover the + * "resolves immediately, never blocks" paths. The genuine cross-thread + * park/wake handshake is exercised for real at boot by the producer/consumer + * demo in kmain.c (needle "pipe demo: eof, count=10" in tools/ci-test.sh). + */ + +KTEST(pipeReadBlockingReturnsQueuedDataImmediately) +{ + int h = pipeCreate(); + KT_GE(t, h, 0); + + const char* msg = "already here"; + KT_EQ(t, pipeWrite(h, msg, strlen(msg)), (int64)strlen(msg)); + + char buffer[32]; + memset(buffer, 0, sizeof(buffer)); + int64 got = pipeReadBlocking(h, buffer, sizeof(buffer)); + KT_EQ(t, got, (int64)strlen(msg)); + KT_TRUE(t, memcmp(buffer, msg, strlen(msg)) == 0); + + pipeCloseRead(h); + pipeCloseWrite(h); +} + +KTEST(pipeReadBlockingReturnsEofWithoutParking) +{ + int h = pipeCreate(); + KT_GE(t, h, 0); + + pipeCloseWrite(h); + + char buffer[8]; + KT_EQ(t, pipeReadBlocking(h, buffer, sizeof(buffer)), 0); + + pipeCloseRead(h); +} + +KTEST(pipeReadBlockingRejectsInvalidHandleWithoutParking) +{ + char buffer[8]; + KT_EQ(t, pipeReadBlocking(-1, buffer, sizeof(buffer)), -1); + KT_EQ(t, pipeReadBlocking(999, buffer, sizeof(buffer)), -1); +} + +KTEST(pipeWriteBlockingSucceedsImmediatelyWhenRoomExists) +{ + int h = pipeCreate(); + KT_GE(t, h, 0); + + const char* msg = "room to spare"; + int64 written = pipeWriteBlocking(h, msg, strlen(msg)); + KT_EQ(t, written, (int64)strlen(msg)); + + char buffer[32]; + memset(buffer, 0, sizeof(buffer)); + KT_EQ(t, pipeRead(h, buffer, sizeof(buffer)), (int64)strlen(msg)); + + pipeCloseRead(h); + pipeCloseWrite(h); +} + +KTEST(pipeWriteBlockingOnBrokenPipeReturnsWithoutParking) +{ + int h = pipeCreate(); + KT_GE(t, h, 0); + + pipeCloseRead(h); + + const char* msg = "nobody listening"; + KT_EQ(t, pipeWriteBlocking(h, msg, strlen(msg)), -1); + + pipeCloseWrite(h); +} + +KTEST(pipeWriteBlockingRejectsInvalidHandleWithoutParking) +{ + char buffer[4] = { 0 }; + KT_EQ(t, pipeWriteBlocking(-1, buffer, sizeof(buffer)), -1); + KT_EQ(t, pipeWriteBlocking(999, buffer, sizeof(buffer)), -1); +} + +KTEST(pipeWriteBlockingSucceedsRightAfterDrainFreesRoom) +{ + int h = pipeCreate(); + KT_GE(t, h, 0); + + uint8 chunk[pipeBufferSize]; + memset(chunk, 0x5A, sizeof(chunk)); + + KT_EQ(t, pipeWrite(h, chunk, sizeof(chunk)), (int64)sizeof(chunk)); + + /* buffer is completely full: a blocking write would have to park here if + nothing ever drained it, so proving this call returns at all (rather + than hanging the whole test run) is exactly the point. */ + uint8 drained[16]; + KT_EQ(t, pipeRead(h, drained, sizeof(drained)), (int64)sizeof(drained)); + + int64 written = pipeWriteBlocking(h, chunk, sizeof(drained)); + KT_EQ(t, written, (int64)sizeof(drained)); + + pipeCloseRead(h); + pipeCloseWrite(h); +}