#include "ktest.h" #include "pipe.h" #include "vfs.h" #include "string.h" /* * Coverage for the shared-pipe machinery added this wave (pipe.h's * pipeCreateDetached/pipeAddReader/pipeAddWriter/pipeWriteBroken/ * pipeChannel, plus vfs.h's vfsPipeCreateHandle/vfsPipeAttach/ * vfsBlockingChannel) -- everything two independently spawned processes * need to rendezvous over a real blocking pipe without fork(). The genuine * cross-process park/wake handshake itself only runs for real at boot * (fifomaster.c spawning fifowriter.c/fiforeader.c, needles * "fifomaster: pipeshare ok"/"fiforeader: payload-ok" in tools/ci-test.sh), * the same division of labor pipeBlockTest.c already uses for * pipeReadBlocking/pipeWriteBlocking: what is deterministic and * scheduler-free lives here, what genuinely needs two live execution * contexts is proven live. */ KTEST(pipeCreateDetachedStartsWithNeitherEndAttached) { int h = pipeCreateDetached(); KT_GE(t, h, 0); /* Nobody has attached a writer yet: a read must report "try again" (-1), not spurious end-of-file (0) the way a writers-already-0 pipeCreate() pipe correctly would. */ char buffer[8]; KT_EQ(t, pipeRead(h, buffer, sizeof(buffer)), -1); /* Nobody has attached a reader yet either, but a write must still succeed (buffered for whenever a reader shows up), not report a broken pipe the way a readers-already-0 pipeCreate() pipe would. */ KT_FALSE(t, pipeWriteBroken(h)); KT_EQ(t, pipeWrite(h, "hi", 2), 2); char got[8]; KT_EQ(t, pipeRead(h, got, sizeof(got)), 2); KT_TRUE(t, memcmp(got, "hi", 2) == 0); pipeAddReader(h); pipeAddWriter(h); pipeCloseRead(h); pipeCloseWrite(h); } KTEST(pipeAddWriterThenCloseMakesRealEofNotBeforehand) { int h = pipeCreateDetached(); KT_GE(t, h, 0); pipeAddReader(h); /* Still no writer attached: not EOF. */ char buffer[4]; KT_EQ(t, pipeRead(h, buffer, sizeof(buffer)), -1); pipeAddWriter(h); KT_EQ(t, pipeWrite(h, "z", 1), 1); KT_EQ(t, pipeRead(h, buffer, sizeof(buffer)), 1); /* Now that a writer really was attached and has left, an empty ring means true end-of-file. */ pipeCloseWrite(h); KT_EQ(t, pipeRead(h, buffer, sizeof(buffer)), 0); pipeCloseRead(h); } KTEST(pipeAddReaderThenCloseMakesRealBrokenPipeNotBeforehand) { int h = pipeCreateDetached(); KT_GE(t, h, 0); pipeAddWriter(h); /* No reader attached yet: writing still succeeds, not "broken". */ KT_FALSE(t, pipeWriteBroken(h)); KT_EQ(t, pipeWrite(h, "a", 1), 1); pipeAddReader(h); char buffer[4]; KT_EQ(t, pipeRead(h, buffer, sizeof(buffer)), 1); /* A reader really was attached and has now left: genuinely broken. */ pipeCloseRead(h); KT_TRUE(t, pipeWriteBroken(h)); KT_EQ(t, pipeWrite(h, "b", 1), -1); pipeCloseWrite(h); } KTEST(pipeWriteBrokenAndChannelRejectInvalidHandles) { KT_TRUE(t, pipeWriteBroken(-1)); KT_TRUE(t, pipeWriteBroken(999)); KT_NULL(t, pipeChannel(-1)); KT_NULL(t, pipeChannel(999)); int h = pipeCreateDetached(); KT_GE(t, h, 0); KT_NOTNULL(t, pipeChannel(h)); int h2 = pipeCreateDetached(); KT_GE(t, h2, 0); KT_NE(t, pipeChannel(h), pipeChannel(h2)); pipeAddReader(h); pipeAddWriter(h); pipeCloseRead(h); pipeCloseWrite(h); pipeAddReader(h2); pipeAddWriter(h2); pipeCloseRead(h2); pipeCloseWrite(h2); } KTEST(vfsPipeCreateHandleAndAttachInstallBlockingFds) { int handle = vfsPipeCreateHandle(); KT_GE(t, handle, 0); int readFd = vfsPipeAttach(handle, false); int writeFd = vfsPipeAttach(handle, true); KT_GE(t, readFd, 0); KT_GE(t, writeFd, 0); KT_NE(t, readFd, writeFd); /* An empty, both-ends-attached shared pipe is exactly the "would block, but retrying could resolve it" state sysRead's retry path (syscall.c) depends on. */ KT_NOTNULL(t, vfsBlockingChannel(readFd, false)); KT_EQ(t, vfsWrite(writeFd, "shared", 6), 6); char buffer[8]; memset(buffer, 0, sizeof(buffer)); KT_EQ(t, vfsRead(readFd, buffer, sizeof(buffer)), 6); KT_TRUE(t, memcmp(buffer, "shared", 6) == 0); /* Wrong direction / wrong fd kind must never hand back a channel. */ KT_NULL(t, vfsBlockingChannel(readFd, true)); KT_NULL(t, vfsBlockingChannel(writeFd, false)); KT_NULL(t, vfsBlockingChannel(-1, false)); vfsClose(readFd); vfsClose(writeFd); } KTEST(vfsPipeAttachRejectsInvalidHandle) { KT_EQ(t, vfsPipeAttach(-1, false), -1); KT_EQ(t, vfsPipeAttach(999, true), -1); } KTEST(vfsBlockingChannelIsNullForOrdinaryNonBlockingPipe) { /* vfsPipe's anonymous pipe (both ends in the caller's own table) must keep reporting "try again" (-1) to the caller directly, the way pipetest.c depends on -- vfsBlockingChannel must never treat it as blockable, or sysRead/sysWrite would start parking a single-process pipe reader/writer that expects an immediate -1 back. */ int readFd, writeFd; KT_EQ(t, vfsPipe(&readFd, &writeFd), 0); char probe[1]; KT_EQ(t, vfsRead(readFd, probe, 0), 0); KT_NULL(t, vfsBlockingChannel(readFd, false)); KT_NULL(t, vfsBlockingChannel(writeFd, true)); vfsClose(readFd); vfsClose(writeFd); }