[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

This commit is contained in:
2026-09-08 15:41:13 -04:00
parent d6e52f75c3
commit 79b581984b
4 changed files with 84 additions and 16 deletions
@@ -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<std::streamoff>(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 <class Body>
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;
}
+21 -4
View File
@@ -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<std::streamoff>(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 <class Body>
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;
}
+21 -4
View File
@@ -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<std::streamoff>(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 <class Body>
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;
}
+21 -4
View File
@@ -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<std::streamoff>(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 <class Body>
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;
}