[Test] (Pipe): pin the pre-fill window - every stamp of 0 is stale at serial 0, a read before any fill aborts naming "<none>", and the FATAL=0 child leaves through std::exit so the teardown summary is observed

- PipeCatalogue.PipeInputFieldsStartUnfilled asserted "unfilled" only after setting the serial to 1 by hand, stepping around the serial-0 window; it now asserts every field stale on a value-initialised state first (sticky fields included).
- PipeInputsTest.ReadingBeforeAnyFillAbortsNamingNoVerb: a live context, no fill, gPipeInputs.GetLineWidth() in a forked child; the parent expects SIGABRT, exactly Fatal{UnmigratedPipeInput, "GetLineWidth@<none>"}, no PipeVerifyDiffer and no arming line. In a verify build the child sets Features.PipeVerify first, the lane's shape.
- VerifyFatalOffLogsTheDivergenceAndContinues: the child _exit(0)ed, so VerifyState's destructor never ran and the "verify summary" line was covered only by a lane run; std::exit(0) runs it, and the parent now asserts "2 divergence(s) survived MOBILEGL_PIPE_VERIFY_FATAL=0".
This commit is contained in:
2026-09-06 02:55:17 -04:00
parent 12b57055b7
commit 44ffafb2dc
2 changed files with 46 additions and 2 deletions
@@ -225,6 +225,12 @@ TEST(PipeCatalogue, CoverageAccountsForEveryInventoryRow) {
TEST(PipeCatalogue, PipeInputFieldsStartUnfilled) {
EXPECT_EQ(kMGPipeInputFieldCount, 63u);
MGPipeFilledState state{};
// Before the first fill the serial is 0 as well: 0 == 0 must not read as fresh, on the
// sticky branch either (the window D6 names "<Field>@<none>").
EXPECT_EQ(state.CurrentVerbSerial, 0u);
for (SizeT f = 0; f < kMGPipeInputFieldCount; ++f) {
EXPECT_FALSE(MGPipeInputFieldIsFresh(state, static_cast<MGPipeInputField>(f))) << kMGPipeInputFieldNames[f];
}
state.CurrentVerbSerial = 1;
EXPECT_FALSE(MGPipeInputFieldIsFresh(state, MGPipeInputField::GetRenderStateParameters));
state.FilledGen[static_cast<SizeT>(MGPipeInputField::GetRenderStateParameters)] = 1;
+40 -2
View File
@@ -143,6 +143,9 @@ TEST(PipeInputsTest,ReadingAnOmittedFieldAbortsNamingTheVerb) {
TEST(PipeInputsTest,ReadingAFilledFieldCompletes) {
GTEST_SKIP() << "push not compiled in (MOBILEGL_PIPE_PUSH=OFF)";
}
TEST(PipeInputsTest,ReadingBeforeAnyFillAbortsNamingNoVerb) {
GTEST_SKIP() << "push not compiled in (MOBILEGL_PIPE_PUSH=OFF)";
}
TEST(PipeInputsTest,CorruptedSnapshotFieldIsNamedWithItsSerial) {
GTEST_SKIP() << "push not compiled in (MOBILEGL_PIPE_PUSH=OFF)";
}
@@ -257,6 +260,36 @@ TEST_F(PipeInputsTest, ReadingAFilledFieldCompletes) {
#endif
}
// The window before the first verb (P1 brief D6: "<Field>@<none>"). Nothing has filled this
// process's block, so a backend-style read of a value field is Fatal naming no verb: the
// serial and every stamp are 0, and 0 == 0 is not fresh. In a verify build the child also
// sets the lane's knob first, the shape of a read reached from initialisation: the poison
// fires at MGP_INPUT_CHECK, before the read hook (armed only by the first fill) could matter.
TEST_F(PipeInputsTest, ReadingBeforeAnyFillAbortsNamingNoVerb) {
#if !MOBILEGL_PIPE_POISON
GTEST_SKIP() << "poison not compiled in (MOBILEGL_PIPE_POISON=0)";
#elif !MGTEST_HAVE_FORK
GTEST_SKIP() << "no fork() on this platform";
#else
ASSERT_FALSE(g_logPath.empty()) << "main() did not set MOBILEGL_LOG_FILE_PATH";
if (gPipeInputs.FilledState().CurrentVerbSerial != 0) {
GTEST_SKIP() << "another case already filled in this process; gtest_discover_tests runs each case alone";
}
MG_State::pGLContext->SetLineWidth(7.0f); // a live value the default storage (0) does not hold
const ChildResult r = RunInChild([] {
#if MOBILEGL_PIPE_VERIFY
MG_Config::Features.PipeVerify = true;
#endif
(void)gPipeInputs.GetLineWidth(); // Fatal{UnmigratedPipeInput, "GetLineWidth@<none>"}
::_exit(3); // reached only if the pre-fill window read as fresh
});
ASSERT_TRUE(DiedOfAbort(r)) << DescribeStatus(r) << "\n" << r.Log;
EXPECT_NE(r.Log.find("Fatal{UnmigratedPipeInput, \"GetLineWidth@<none>\"}"), std::string::npos) << r.Log;
EXPECT_EQ(r.Log.find("PipeVerifyDiffer"), std::string::npos) << r.Log;
EXPECT_EQ(r.Log.find("verify armed"), std::string::npos) << r.Log;
#endif
}
// The MOBILEGL_PIPE_POISON_OMIT parser, through the Features field the loader fills: the
// child sets the knob after its parent filled without it, and the first fill after that
// parses it, logs the arming line and withholds the stamp exactly as the programmatic form.
@@ -427,7 +460,9 @@ TEST_F(PipeInputsTest, BadVerifyCorruptKnobIsFatalNamingTheKnob) {
}
// MOBILEGL_PIPE_VERIFY_FATAL=0: the divergence is logged with its field and serial and the
// process goes on (the fill returns, the next fill's compare runs again).
// process goes on (the fill returns, the next fill's compare runs again), and the teardown
// summary counts both. The child leaves through std::exit so the comparator's static
// destructor runs (a _exit would skip the summary line).
TEST_F(PipeInputsTest, VerifyFatalOffLogsTheDivergenceAndContinues) {
#if !MOBILEGL_PIPE_VERIFY
GTEST_SKIP() << "verify not compiled in (MOBILEGL_PIPE_VERIFY=OFF)";
@@ -443,10 +478,13 @@ TEST_F(PipeInputsTest, VerifyFatalOffLogsTheDivergenceAndContinues) {
MG_Config::Features.PipeVerifyCorrupt = "GetRenderStateParameters";
MGPipeFillForVerb(MGPipeVerb::DrawArrays);
MGPipeFillForVerb(MGPipeVerb::DrawElements);
::_exit(0);
std::exit(0);
});
ASSERT_TRUE(ExitedWith(r, 0)) << DescribeStatus(r) << "\n" << r.Log;
EXPECT_NE(r.Log.find("MGPipe: verify armed - 63 fields, 69 verbs, fatal=0"), std::string::npos) << r.Log;
EXPECT_NE(r.Log.find("MGPipe: verify summary - 2 divergence(s) survived MOBILEGL_PIPE_VERIFY_FATAL=0"),
std::string::npos)
<< r.Log;
const std::string first = "Fatal{PipeVerifyDiffer, \"GetRenderStateParameters@DrawArrays\", verb=" +
std::to_string(serial) + ", where=entry}";
const std::string second = "Fatal{PipeVerifyDiffer, \"GetRenderStateParameters@DrawElements\", verb=" +