From 44ffafb2dc10a7976fd3895a8c6bd4d3588ee922 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Sun, 6 Sep 2026 02:55:17 -0400 Subject: [PATCH] [Test] (Pipe): pin the pre-fill window - every stamp of 0 is stale at serial 0, a read before any fill aborts naming "", 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@"}, 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". --- MobileGL/MG_Test/Pipe/PipeCatalogueTest.cpp | 6 +++ MobileGL/MG_Test/Pipe/PipeInputsTest.cpp | 42 ++++++++++++++++++++- 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/MobileGL/MG_Test/Pipe/PipeCatalogueTest.cpp b/MobileGL/MG_Test/Pipe/PipeCatalogueTest.cpp index c5c720e0..9af67520 100644 --- a/MobileGL/MG_Test/Pipe/PipeCatalogueTest.cpp +++ b/MobileGL/MG_Test/Pipe/PipeCatalogueTest.cpp @@ -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 "@"). + EXPECT_EQ(state.CurrentVerbSerial, 0u); + for (SizeT f = 0; f < kMGPipeInputFieldCount; ++f) { + EXPECT_FALSE(MGPipeInputFieldIsFresh(state, static_cast(f))) << kMGPipeInputFieldNames[f]; + } state.CurrentVerbSerial = 1; EXPECT_FALSE(MGPipeInputFieldIsFresh(state, MGPipeInputField::GetRenderStateParameters)); state.FilledGen[static_cast(MGPipeInputField::GetRenderStateParameters)] = 1; diff --git a/MobileGL/MG_Test/Pipe/PipeInputsTest.cpp b/MobileGL/MG_Test/Pipe/PipeInputsTest.cpp index 8b307535..69be8437 100644 --- a/MobileGL/MG_Test/Pipe/PipeInputsTest.cpp +++ b/MobileGL/MG_Test/Pipe/PipeInputsTest.cpp @@ -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: "@"). 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@"} + ::_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@\"}"), 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=" +