From 79b581984bfe38530eab09e0d69b65908176340d Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Tue, 8 Sep 2026 15:41:00 -0400 Subject: [PATCH] [Fix, Test] (Pipe): read a forked refusal drive's log from the offset it had instead of unlinking the file - once a case initialises the library the log is already open, so the child was writing into a deleted inode and the parent read nothing --- .../MG_Test/Pipe/CompositeResolverTest.cpp | 25 ++++++++++++++++--- MobileGL/MG_Test/Pipe/ImageEmitTest.cpp | 25 ++++++++++++++++--- MobileGL/MG_Test/Pipe/ProgramEmitTest.cpp | 25 ++++++++++++++++--- MobileGL/MG_Test/Pipe/SamplerEmitTest.cpp | 25 ++++++++++++++++--- 4 files changed, 84 insertions(+), 16 deletions(-) diff --git a/MobileGL/MG_Test/Pipe/CompositeResolverTest.cpp b/MobileGL/MG_Test/Pipe/CompositeResolverTest.cpp index 0ae906e3..8a4cf117 100644 --- a/MobileGL/MG_Test/Pipe/CompositeResolverTest.cpp +++ b/MobileGL/MG_Test/Pipe/CompositeResolverTest.cpp @@ -78,13 +78,24 @@ namespace { } #if MOBILEGL_PIPE_PUSH - std::string ReadLog() { + // `from` is a byte offset, and it exists because of the fork below: the library's log file + // is already open by the time a case runs, so the child's lines are APPENDED to it rather + // than written to a fresh file, and only what the child appended is this drive's evidence. + std::string ReadLog(std::streamoff from = 0) { std::ifstream in(g_logPath, std::ios::binary); + if (from > 0) in.seekg(from, std::ios::beg); std::ostringstream ss; ss << in.rdbuf(); return ss.str(); } + // Where the library's log file currently ends. Reading from here after the child has + // aborted gives exactly the lines that drive produced. + std::streamoff LogEnd() { + std::ifstream in(g_logPath, std::ios::binary | std::ios::ate); + return in ? static_cast(in.tellg()) : std::streamoff{0}; + } + // A fresh applier per case, BOTH SCOPES, and it takes both because there are two: a reset // is a make-current and deliberately KEEPS the object records, so a fixture that wants a // genuinely empty applier has to say the other one as well. Every case is its own process @@ -110,8 +121,14 @@ namespace { template ChildResult RunInChild(Body body) { ChildResult result; - std::error_code ec; - std::filesystem::remove(g_logPath, ec); + // THE LOG PATH IS NOT UNLINKED HERE, and that is what this helper had to learn when the + // client's cases landed in the same file as the applier's: main() calls + // MobileGL::Initialize(), so the library's log FILE* is already open on this path and + // fork() duplicates it. Removing the path would leave the child writing into a deleted + // inode and the parent reading an empty file - the child would still abort, and the + // assertion on WHAT it named could never see the line. So the log's end is remembered + // and only what the child appended is read back. + const std::streamoff before = LogEnd(); std::fflush(nullptr); const pid_t pid = ::fork(); if (pid < 0) return result; @@ -122,7 +139,7 @@ namespace { int status = 0; if (::waitpid(pid, &status, 0) != pid) return result; result.Status = status; - result.Log = ReadLog(); + result.Log = ReadLog(before); return result; } diff --git a/MobileGL/MG_Test/Pipe/ImageEmitTest.cpp b/MobileGL/MG_Test/Pipe/ImageEmitTest.cpp index 6387c652..536f6879 100644 --- a/MobileGL/MG_Test/Pipe/ImageEmitTest.cpp +++ b/MobileGL/MG_Test/Pipe/ImageEmitTest.cpp @@ -69,13 +69,24 @@ namespace { } #if MOBILEGL_PIPE_PUSH - std::string ReadLog() { + // `from` is a byte offset, and it exists because of the fork below: the library's log file + // is already open by the time a case runs, so the child's lines are APPENDED to it rather + // than written to a fresh file, and only what the child appended is this drive's evidence. + std::string ReadLog(std::streamoff from = 0) { std::ifstream in(g_logPath, std::ios::binary); + if (from > 0) in.seekg(from, std::ios::beg); std::ostringstream ss; ss << in.rdbuf(); return ss.str(); } + // Where the library's log file currently ends. Reading from here after the child has + // aborted gives exactly the lines that drive produced. + std::streamoff LogEnd() { + std::ifstream in(g_logPath, std::ios::binary | std::ios::ate); + return in ? static_cast(in.tellg()) : std::streamoff{0}; + } + // A fresh applier per case, BOTH SCOPES, and it takes both because there are two: a reset // is a make-current and deliberately KEEPS the object records, so a fixture that wants a // genuinely empty applier has to say the other one as well. Every case is its own process @@ -101,8 +112,14 @@ namespace { template ChildResult RunInChild(Body body) { ChildResult result; - std::error_code ec; - std::filesystem::remove(g_logPath, ec); + // THE LOG PATH IS NOT UNLINKED HERE, and that is what this helper had to learn when the + // client's cases landed in the same file as the applier's: main() calls + // MobileGL::Initialize(), so the library's log FILE* is already open on this path and + // fork() duplicates it. Removing the path would leave the child writing into a deleted + // inode and the parent reading an empty file - the child would still abort, and the + // assertion on WHAT it named could never see the line. So the log's end is remembered + // and only what the child appended is read back. + const std::streamoff before = LogEnd(); std::fflush(nullptr); const pid_t pid = ::fork(); if (pid < 0) return result; @@ -113,7 +130,7 @@ namespace { int status = 0; if (::waitpid(pid, &status, 0) != pid) return result; result.Status = status; - result.Log = ReadLog(); + result.Log = ReadLog(before); return result; } diff --git a/MobileGL/MG_Test/Pipe/ProgramEmitTest.cpp b/MobileGL/MG_Test/Pipe/ProgramEmitTest.cpp index e9626e50..e1c2205e 100644 --- a/MobileGL/MG_Test/Pipe/ProgramEmitTest.cpp +++ b/MobileGL/MG_Test/Pipe/ProgramEmitTest.cpp @@ -77,13 +77,24 @@ namespace { } #if MOBILEGL_PIPE_PUSH - std::string ReadLog() { + // `from` is a byte offset, and it exists because of the fork below: the library's log file + // is already open by the time a case runs, so the child's lines are APPENDED to it rather + // than written to a fresh file, and only what the child appended is this drive's evidence. + std::string ReadLog(std::streamoff from = 0) { std::ifstream in(g_logPath, std::ios::binary); + if (from > 0) in.seekg(from, std::ios::beg); std::ostringstream ss; ss << in.rdbuf(); return ss.str(); } + // Where the library's log file currently ends. Reading from here after the child has + // aborted gives exactly the lines that drive produced. + std::streamoff LogEnd() { + std::ifstream in(g_logPath, std::ios::binary | std::ios::ate); + return in ? static_cast(in.tellg()) : std::streamoff{0}; + } + // A fresh applier per case, BOTH SCOPES, and it takes both because there are two: a reset // is a make-current and deliberately KEEPS the object records, so a fixture that wants a // genuinely empty applier has to say the other one as well. Every case is its own process @@ -109,8 +120,14 @@ namespace { template ChildResult RunInChild(Body body) { ChildResult result; - std::error_code ec; - std::filesystem::remove(g_logPath, ec); + // THE LOG PATH IS NOT UNLINKED HERE, and that is what this helper had to learn when the + // client's cases landed in the same file as the applier's: main() calls + // MobileGL::Initialize(), so the library's log FILE* is already open on this path and + // fork() duplicates it. Removing the path would leave the child writing into a deleted + // inode and the parent reading an empty file - the child would still abort, and the + // assertion on WHAT it named could never see the line. So the log's end is remembered + // and only what the child appended is read back. + const std::streamoff before = LogEnd(); std::fflush(nullptr); const pid_t pid = ::fork(); if (pid < 0) return result; @@ -121,7 +138,7 @@ namespace { int status = 0; if (::waitpid(pid, &status, 0) != pid) return result; result.Status = status; - result.Log = ReadLog(); + result.Log = ReadLog(before); return result; } diff --git a/MobileGL/MG_Test/Pipe/SamplerEmitTest.cpp b/MobileGL/MG_Test/Pipe/SamplerEmitTest.cpp index e6bc3f65..4e7ee50d 100644 --- a/MobileGL/MG_Test/Pipe/SamplerEmitTest.cpp +++ b/MobileGL/MG_Test/Pipe/SamplerEmitTest.cpp @@ -80,13 +80,24 @@ namespace { } #if MOBILEGL_PIPE_PUSH - std::string ReadLog() { + // `from` is a byte offset, and it exists because of the fork below: the library's log file + // is already open by the time a case runs, so the child's lines are APPENDED to it rather + // than written to a fresh file, and only what the child appended is this drive's evidence. + std::string ReadLog(std::streamoff from = 0) { std::ifstream in(g_logPath, std::ios::binary); + if (from > 0) in.seekg(from, std::ios::beg); std::ostringstream ss; ss << in.rdbuf(); return ss.str(); } + // Where the library's log file currently ends. Reading from here after the child has + // aborted gives exactly the lines that drive produced. + std::streamoff LogEnd() { + std::ifstream in(g_logPath, std::ios::binary | std::ios::ate); + return in ? static_cast(in.tellg()) : std::streamoff{0}; + } + // A fresh applier per case, BOTH SCOPES, and it takes both because there are two: a reset // is a make-current and deliberately KEEPS the object records, so a fixture that wants a // genuinely empty applier has to say the other one as well. Every case is its own process @@ -112,8 +123,14 @@ namespace { template ChildResult RunInChild(Body body) { ChildResult result; - std::error_code ec; - std::filesystem::remove(g_logPath, ec); + // THE LOG PATH IS NOT UNLINKED HERE, and that is what this helper had to learn when the + // client's cases landed in the same file as the applier's: main() calls + // MobileGL::Initialize(), so the library's log FILE* is already open on this path and + // fork() duplicates it. Removing the path would leave the child writing into a deleted + // inode and the parent reading an empty file - the child would still abort, and the + // assertion on WHAT it named could never see the line. So the log's end is remembered + // and only what the child appended is read back. + const std::streamoff before = LogEnd(); std::fflush(nullptr); const pid_t pid = ::fork(); if (pid < 0) return result; @@ -124,7 +141,7 @@ namespace { int status = 0; if (::waitpid(pid, &status, 0) != pid) return result; result.Status = status; - result.Log = ReadLog(); + result.Log = ReadLog(before); return result; }