mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-07 19:58:32 +09:00
[Test] (Pipe): give every PipeInputsTest process its own log file, and cover the compare-at-read arm, the three knob parsers and VERIFY_FATAL=0 with forked children
- gtest_discover_tests runs each case as its own process; under ctest -j the five shared one fixed log path, each main() unlinked it and each fork parent re-read it by path, so a sibling's Fatal line or unlink landed in another case's assertion (red 40/40 at -j 8, for a reason unrelated to the poison). The name now carries the pid and the file is removed on the way out; a forked child inherits the path on purpose.
- MutatedFieldIsNamedAtRead: the first falsifier of MGPipeVerifyReadHook - the child arms verify, fills DrawArrays, reads GetLineWidth (completes), mutates the live context's line width and reads again, and the parent expects SIGABRT with Fatal{PipeVerifyDiffer, "GetLineWidth@DrawArrays", verb=<serial>, where=read} and no where=entry.
- PoisonOmitKnobArmsTheOmission / BadPoisonOmitKnobIsFatalNamingTheKnob / VerifyCorruptKnobNamesTheFieldAtEntry / BadVerifyCorruptKnobIsFatalNamingTheKnob / VerifyFatalOffLogsTheDivergenceAndContinues: the knob parsers through MG_Config::Features, their arming lines, the exact Fatal{PipeVerifyBadKnob} text, and a FATAL=0 run that logs two divergences at consecutive serials and exits 0.
- OmittingOneFieldForOneVerbLeavesExactlyThatFieldStale now loops every field: fresh iff in kTextureOp's mask and not the omitted one, so the name is true.
- The fork/waitpid/log-delta shape is one RunInChild helper; every case is still a visible skip where its switch is off.
This commit is contained in:
Executable → Regular
+264
-33
@@ -12,6 +12,10 @@
|
||||
// pre-flight shape): the child performs the read that must be Fatal{UnmigratedPipeInput}
|
||||
// and the parent reads SIGABRT out of waitpid and the exact line out of the log file that
|
||||
// main() below points MOBILEGL_LOG_FILE_PATH at (LogLevelTest's shape). Never EXPECT_DEATH.
|
||||
//
|
||||
// The log file is per PROCESS: gtest_discover_tests runs every case as its own process, in
|
||||
// parallel under ctest -j, and a name shared between them let a sibling's unlink or Fatal
|
||||
// line land in this process's read. A forked child inherits its parent's path on purpose.
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
@@ -26,6 +30,7 @@
|
||||
#include <MG_Pipe/MGPipe.h>
|
||||
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
#include <Config.h>
|
||||
#include <MG_Backend/MGPipe/PipeInputs.h>
|
||||
#include <MG_Impl/Pipe/PipeFill.h>
|
||||
#include <MG_State/GLState/Core.h>
|
||||
@@ -37,6 +42,7 @@
|
||||
#include <unistd.h>
|
||||
#define MGTEST_HAVE_FORK 1
|
||||
#else
|
||||
#include <process.h>
|
||||
#define MGTEST_HAVE_FORK 0
|
||||
#endif
|
||||
|
||||
@@ -53,6 +59,14 @@ namespace {
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
long ProcessId() {
|
||||
#if defined(_WIN32)
|
||||
return static_cast<long>(::_getpid());
|
||||
#else
|
||||
return static_cast<long>(::getpid());
|
||||
#endif
|
||||
}
|
||||
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
using GLContext = MG_State::GLState::GLContext;
|
||||
|
||||
@@ -77,6 +91,44 @@ namespace {
|
||||
#endif
|
||||
|
||||
[[maybe_unused]] constexpr const char* kOmittedFatal ="Fatal{UnmigratedPipeInput, \"GetActiveTextureUnit@GenerateMipmap\"}";
|
||||
[[maybe_unused]] constexpr const char* kOmissionKnob = "GenerateMipmap:GetActiveTextureUnit";
|
||||
|
||||
#if MGTEST_HAVE_FORK
|
||||
struct ChildResult {
|
||||
int Status = -1; // waitpid's status; -1 when fork or waitpid failed
|
||||
std::string Log; // the lines the child appended to the log file
|
||||
};
|
||||
|
||||
// Runs `body` in a forked child and returns its wait status and log delta. The child
|
||||
// must not use gtest assertions; it _exit(0)s when `body` returns, so a body that is
|
||||
// expected to die must be asserted dead by the parent (WIFSIGNALED), never assumed.
|
||||
template <class Body>
|
||||
ChildResult RunInChild(Body body) {
|
||||
ChildResult result;
|
||||
const std::string before = ReadLog();
|
||||
std::fflush(nullptr);
|
||||
const pid_t pid = ::fork();
|
||||
if (pid < 0) return result;
|
||||
if (pid == 0) {
|
||||
body();
|
||||
::_exit(0);
|
||||
}
|
||||
int status = 0;
|
||||
if (::waitpid(pid, &status, 0) != pid) return result;
|
||||
result.Status = status;
|
||||
result.Log = ReadLog().substr(before.size());
|
||||
return result;
|
||||
}
|
||||
|
||||
Bool DiedOfAbort(const ChildResult& r) { return WIFSIGNALED(r.Status) && WTERMSIG(r.Status) == SIGABRT; }
|
||||
Bool ExitedWith(const ChildResult& r, int code) { return WIFEXITED(r.Status) && WEXITSTATUS(r.Status) == code; }
|
||||
std::string DescribeStatus(const ChildResult& r) {
|
||||
if (r.Status < 0) return "fork/waitpid failed";
|
||||
if (WIFEXITED(r.Status)) return "exited " + std::to_string(WEXITSTATUS(r.Status));
|
||||
if (WIFSIGNALED(r.Status)) return "signal " + std::to_string(WTERMSIG(r.Status));
|
||||
return "status " + std::to_string(r.Status);
|
||||
}
|
||||
#endif // MGTEST_HAVE_FORK
|
||||
#endif // MOBILEGL_PIPE_PUSH
|
||||
} // namespace
|
||||
|
||||
@@ -97,12 +149,31 @@ TEST(PipeInputsTest,CorruptedSnapshotFieldIsNamedWithItsSerial) {
|
||||
TEST(PipeInputsTest,EveryVerbFillsItsClassAndNothingElse) {
|
||||
GTEST_SKIP() << "push not compiled in (MOBILEGL_PIPE_PUSH=OFF)";
|
||||
}
|
||||
TEST(PipeInputsTest,MutatedFieldIsNamedAtRead) {
|
||||
GTEST_SKIP() << "push not compiled in (MOBILEGL_PIPE_PUSH=OFF)";
|
||||
}
|
||||
TEST(PipeInputsTest,PoisonOmitKnobArmsTheOmission) {
|
||||
GTEST_SKIP() << "push not compiled in (MOBILEGL_PIPE_PUSH=OFF)";
|
||||
}
|
||||
TEST(PipeInputsTest,BadPoisonOmitKnobIsFatalNamingTheKnob) {
|
||||
GTEST_SKIP() << "push not compiled in (MOBILEGL_PIPE_PUSH=OFF)";
|
||||
}
|
||||
TEST(PipeInputsTest,VerifyCorruptKnobNamesTheFieldAtEntry) {
|
||||
GTEST_SKIP() << "push not compiled in (MOBILEGL_PIPE_PUSH=OFF)";
|
||||
}
|
||||
TEST(PipeInputsTest,BadVerifyCorruptKnobIsFatalNamingTheKnob) {
|
||||
GTEST_SKIP() << "push not compiled in (MOBILEGL_PIPE_PUSH=OFF)";
|
||||
}
|
||||
TEST(PipeInputsTest,VerifyFatalOffLogsTheDivergenceAndContinues) {
|
||||
GTEST_SKIP() << "push not compiled in (MOBILEGL_PIPE_PUSH=OFF)";
|
||||
}
|
||||
|
||||
#else // MOBILEGL_PIPE_PUSH
|
||||
|
||||
// Negative control B, layer 1 (P1 brief D6 / G5): the omitted (verb, field) pair is the
|
||||
// ONLY thing that goes stale - a sibling field of the same verb is fresh, and the verb after
|
||||
// it neither heals the field (not in kDraw's mask) nor loses one of its own.
|
||||
// ONLY thing that goes stale - every other bit of the verb class's mask is fresh, every
|
||||
// field outside it is not, and the verb after it neither heals the field (not in kDraw's
|
||||
// mask) nor loses one of its own.
|
||||
TEST_F(PipeInputsTest, OmittingOneFieldForOneVerbLeavesExactlyThatFieldStale) {
|
||||
#if !MOBILEGL_PIPE_POISON
|
||||
GTEST_SKIP() << "poison not compiled in (MOBILEGL_PIPE_POISON=0)";
|
||||
@@ -117,6 +188,17 @@ TEST_F(PipeInputsTest, OmittingOneFieldForOneVerbLeavesExactlyThatFieldStale) {
|
||||
EXPECT_FALSE(Fresh(MGPipeInputField::GetActiveTextureUnit));
|
||||
// The value was still copied: only the stamp is withheld.
|
||||
EXPECT_EQ(gPipeInputs.CurrentVerb(), MGPipeVerb::GenerateMipmap);
|
||||
// Exactly that field: a field is fresh iff its bit is in kTextureOp's mask and it is not
|
||||
// the omitted one.
|
||||
{
|
||||
const auto cls = kMGPipeVerbClass[static_cast<SizeT>(MGPipeVerb::GenerateMipmap)];
|
||||
const MGPipeFieldMask& mask = kMGPipeClassFieldMask[static_cast<SizeT>(cls)];
|
||||
for (SizeT f = 0; f < kMGPipeInputFieldCount; ++f) {
|
||||
const auto field = static_cast<MGPipeInputField>(f);
|
||||
const Bool expected = MGPipeFieldMaskHas(mask, field) && field != MGPipeInputField::GetActiveTextureUnit;
|
||||
EXPECT_EQ(Fresh(field), expected) << kMGPipeInputFieldNames[f] << " after the omitted GenerateMipmap fill";
|
||||
}
|
||||
}
|
||||
|
||||
MGPipeFillForVerb(MGPipeVerb::DrawArrays);
|
||||
EXPECT_FALSE(Fresh(MGPipeInputField::GetActiveTextureUnit));
|
||||
@@ -141,12 +223,7 @@ TEST_F(PipeInputsTest, ReadingAnOmittedFieldAbortsNamingTheVerb) {
|
||||
GTEST_SKIP() << "no fork() on this platform";
|
||||
#else
|
||||
ASSERT_FALSE(g_logPath.empty()) << "main() did not set MOBILEGL_LOG_FILE_PATH";
|
||||
const std::string before = ReadLog();
|
||||
std::fflush(nullptr);
|
||||
const pid_t pid = ::fork();
|
||||
ASSERT_GE(pid, 0);
|
||||
if (pid == 0) {
|
||||
// Child: no gtest assertions, _exit never exit.
|
||||
const ChildResult r = RunInChild([] {
|
||||
MGPipeSetPoisonOmission("GenerateMipmap", "GetActiveTextureUnit");
|
||||
MGPipeFillForVerb(MGPipeVerb::DrawArrays);
|
||||
(void)gPipeInputs.GetRenderStateParameters(); // a filled field of the preceding draw: must not abort
|
||||
@@ -154,15 +231,11 @@ TEST_F(PipeInputsTest, ReadingAnOmittedFieldAbortsNamingTheVerb) {
|
||||
(void)gPipeInputs.GetTextureUnitObject(0); // the sibling field: filled, must not abort
|
||||
(void)gPipeInputs.GetActiveTextureUnit(); // the omitted field: Fatal
|
||||
::_exit(3); // reached only if the poison failed
|
||||
}
|
||||
int status = 0;
|
||||
ASSERT_EQ(::waitpid(pid, &status, 0), pid);
|
||||
ASSERT_TRUE(WIFSIGNALED(status)) << "child exited normally with " << (WIFEXITED(status) ? WEXITSTATUS(status) : -1);
|
||||
EXPECT_EQ(WTERMSIG(status), SIGABRT);
|
||||
const std::string log = ReadLog().substr(before.size());
|
||||
EXPECT_NE(log.find(kOmittedFatal), std::string::npos) << log;
|
||||
EXPECT_EQ(log.find("@DrawArrays"), std::string::npos) << log;
|
||||
EXPECT_EQ(log.find("GetTextureUnitObject@"), std::string::npos) << log;
|
||||
});
|
||||
ASSERT_TRUE(DiedOfAbort(r)) << DescribeStatus(r) << "\n" << r.Log;
|
||||
EXPECT_NE(r.Log.find(kOmittedFatal), std::string::npos) << r.Log;
|
||||
EXPECT_EQ(r.Log.find("@DrawArrays"), std::string::npos) << r.Log;
|
||||
EXPECT_EQ(r.Log.find("GetTextureUnitObject@"), std::string::npos) << r.Log;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -172,24 +245,62 @@ TEST_F(PipeInputsTest, ReadingAFilledFieldCompletes) {
|
||||
GTEST_SKIP() << "no fork() on this platform";
|
||||
#else
|
||||
ASSERT_FALSE(g_logPath.empty()) << "main() did not set MOBILEGL_LOG_FILE_PATH";
|
||||
const std::string before = ReadLog();
|
||||
std::fflush(nullptr);
|
||||
const pid_t pid = ::fork();
|
||||
ASSERT_GE(pid, 0);
|
||||
if (pid == 0) {
|
||||
const ChildResult r = RunInChild([] {
|
||||
MGPipeFillForVerb(MGPipeVerb::DrawArrays);
|
||||
(void)gPipeInputs.GetRenderStateParameters();
|
||||
MGPipeFillForVerb(MGPipeVerb::GenerateMipmap);
|
||||
(void)gPipeInputs.GetTextureUnitObject(0);
|
||||
(void)gPipeInputs.GetActiveTextureUnit();
|
||||
::_exit(0);
|
||||
}
|
||||
int status = 0;
|
||||
ASSERT_EQ(::waitpid(pid, &status, 0), pid);
|
||||
ASSERT_TRUE(WIFEXITED(status)) << "child died on signal " << (WIFSIGNALED(status) ? WTERMSIG(status) : -1);
|
||||
EXPECT_EQ(WEXITSTATUS(status), 0);
|
||||
const std::string log = ReadLog().substr(before.size());
|
||||
EXPECT_EQ(log.find("Fatal{"), std::string::npos) << log;
|
||||
});
|
||||
ASSERT_TRUE(ExitedWith(r, 0)) << DescribeStatus(r) << "\n" << r.Log;
|
||||
EXPECT_EQ(r.Log.find("Fatal{"), 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.
|
||||
TEST_F(PipeInputsTest, PoisonOmitKnobArmsTheOmission) {
|
||||
#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";
|
||||
MGPipeFillForVerb(MGPipeVerb::DrawArrays); // the parent's parse saw an empty knob
|
||||
const ChildResult r = RunInChild([] {
|
||||
MG_Config::Features.PipePoisonOmit = kOmissionKnob;
|
||||
MGPipeFillForVerb(MGPipeVerb::GenerateMipmap);
|
||||
(void)gPipeInputs.GetTextureUnitObject(0); // the sibling field: filled, must not abort
|
||||
(void)gPipeInputs.GetActiveTextureUnit(); // the omitted field: Fatal
|
||||
::_exit(3);
|
||||
});
|
||||
ASSERT_TRUE(DiedOfAbort(r)) << DescribeStatus(r) << "\n" << r.Log;
|
||||
EXPECT_NE(r.Log.find("MGPipe: poison omission armed - GetActiveTextureUnit@GenerateMipmap"), std::string::npos)
|
||||
<< r.Log;
|
||||
EXPECT_NE(r.Log.find(kOmittedFatal), std::string::npos) << r.Log;
|
||||
EXPECT_EQ(r.Log.find("GetTextureUnitObject@"), std::string::npos) << r.Log;
|
||||
#endif
|
||||
}
|
||||
|
||||
// An unknown verb in the knob is Fatal{PipeVerifyBadKnob} naming the knob and the value,
|
||||
// before any stamp is withheld.
|
||||
TEST_F(PipeInputsTest, BadPoisonOmitKnobIsFatalNamingTheKnob) {
|
||||
#if !MGTEST_HAVE_FORK
|
||||
GTEST_SKIP() << "no fork() on this platform";
|
||||
#else
|
||||
ASSERT_FALSE(g_logPath.empty()) << "main() did not set MOBILEGL_LOG_FILE_PATH";
|
||||
const ChildResult r = RunInChild([] {
|
||||
MG_Config::Features.PipePoisonOmit = "NoSuchVerb:GetActiveTextureUnit";
|
||||
MGPipeFillForVerb(MGPipeVerb::GenerateMipmap);
|
||||
::_exit(3);
|
||||
});
|
||||
ASSERT_TRUE(DiedOfAbort(r)) << DescribeStatus(r) << "\n" << r.Log;
|
||||
EXPECT_NE(r.Log.find("Fatal{PipeVerifyBadKnob, \"MOBILEGL_PIPE_POISON_OMIT=NoSuchVerb:GetActiveTextureUnit\": "
|
||||
"no such verb in kMGPipeVerbNames}"),
|
||||
std::string::npos)
|
||||
<< r.Log;
|
||||
EXPECT_EQ(r.Log.find("poison omission armed"), std::string::npos) << r.Log;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -229,6 +340,122 @@ TEST_F(PipeInputsTest, CorruptedSnapshotFieldIsNamedWithItsSerial) {
|
||||
#endif
|
||||
}
|
||||
|
||||
// The compare-at-read arm (P1 brief D8, the arm that is real in P1): a value that changes in
|
||||
// the live context between the verb boundary and the read is Fatal{PipeVerifyDiffer,
|
||||
// "Field@Verb", verb=<serial>, where=read}; the same read before the mutation completes.
|
||||
TEST_F(PipeInputsTest, MutatedFieldIsNamedAtRead) {
|
||||
#if !MOBILEGL_PIPE_VERIFY
|
||||
GTEST_SKIP() << "verify not compiled in (MOBILEGL_PIPE_VERIFY=OFF)";
|
||||
#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";
|
||||
MGPipeFillForVerb(MGPipeVerb::Clear); // the parent armed nothing: Features.PipeVerify is false here
|
||||
const Uint64 serial = gPipeInputs.FilledState().CurrentVerbSerial + 1; // the child's DrawArrays fill
|
||||
const ChildResult r = RunInChild([] {
|
||||
MG_Config::Features.PipeVerify = true;
|
||||
MGPipeFillForVerb(MGPipeVerb::DrawArrays);
|
||||
const Float boundary = gPipeInputs.GetLineWidth(); // boundary == live: completes
|
||||
(void)gPipeInputs.GetRenderStateParameters();
|
||||
MG_State::pGLContext->SetLineWidth(boundary + 1.0f);
|
||||
if (MG_State::pGLContext->GetLineWidth() == boundary) ::_exit(7); // the mutation did not take
|
||||
(void)gPipeInputs.GetLineWidth(); // the stored value is stale against the live one: Fatal
|
||||
::_exit(3);
|
||||
});
|
||||
ASSERT_TRUE(DiedOfAbort(r)) << DescribeStatus(r) << "\n" << r.Log;
|
||||
EXPECT_NE(r.Log.find("MGPipe: verify armed - 63 fields, 69 verbs, fatal=1"), std::string::npos) << r.Log;
|
||||
EXPECT_NE(r.Log.find("MGPipe: verify read of GetLineWidth (index 0, 0) differs from the live context"),
|
||||
std::string::npos)
|
||||
<< r.Log;
|
||||
const std::string expected = "Fatal{PipeVerifyDiffer, \"GetLineWidth@DrawArrays\", verb=" + std::to_string(serial) +
|
||||
", where=read}";
|
||||
EXPECT_NE(r.Log.find(expected), std::string::npos) << "expected " << expected << " in\n" << r.Log;
|
||||
EXPECT_EQ(r.Log.find("where=entry"), std::string::npos) << r.Log;
|
||||
#endif
|
||||
}
|
||||
|
||||
// The MOBILEGL_PIPE_VERIFY_CORRUPT parser through Features: the fill after the child arms it
|
||||
// logs both arming lines and the entry compare names the corrupted field at that fill's
|
||||
// serial, where=entry.
|
||||
TEST_F(PipeInputsTest, VerifyCorruptKnobNamesTheFieldAtEntry) {
|
||||
#if !MOBILEGL_PIPE_VERIFY
|
||||
GTEST_SKIP() << "verify not compiled in (MOBILEGL_PIPE_VERIFY=OFF)";
|
||||
#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";
|
||||
MGPipeFillForVerb(MGPipeVerb::Clear);
|
||||
const Uint64 serial = gPipeInputs.FilledState().CurrentVerbSerial + 1;
|
||||
const ChildResult r = RunInChild([] {
|
||||
MG_Config::Features.PipeVerify = true;
|
||||
MG_Config::Features.PipeVerifyCorrupt = "GetRenderStateParameters";
|
||||
MGPipeFillForVerb(MGPipeVerb::DrawArrays);
|
||||
::_exit(3);
|
||||
});
|
||||
ASSERT_TRUE(DiedOfAbort(r)) << DescribeStatus(r) << "\n" << r.Log;
|
||||
EXPECT_NE(r.Log.find("MGPipe: verify armed - 63 fields, 69 verbs, fatal=1"), std::string::npos) << r.Log;
|
||||
EXPECT_NE(r.Log.find("MGPipe: verify corruption armed - GetRenderStateParameters"), std::string::npos) << r.Log;
|
||||
const std::string expected = "Fatal{PipeVerifyDiffer, \"GetRenderStateParameters@DrawArrays\", verb=" +
|
||||
std::to_string(serial) + ", where=entry}";
|
||||
EXPECT_NE(r.Log.find(expected), std::string::npos) << "expected " << expected << " in\n" << r.Log;
|
||||
#endif
|
||||
}
|
||||
|
||||
// An unknown field name in MOBILEGL_PIPE_VERIFY_CORRUPT is Fatal{PipeVerifyBadKnob} at
|
||||
// arming, before the comparator reports anything.
|
||||
TEST_F(PipeInputsTest, BadVerifyCorruptKnobIsFatalNamingTheKnob) {
|
||||
#if !MOBILEGL_PIPE_VERIFY
|
||||
GTEST_SKIP() << "verify not compiled in (MOBILEGL_PIPE_VERIFY=OFF)";
|
||||
#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";
|
||||
const ChildResult r = RunInChild([] {
|
||||
MG_Config::Features.PipeVerify = true;
|
||||
MG_Config::Features.PipeVerifyCorrupt = "NoSuchField";
|
||||
MGPipeFillForVerb(MGPipeVerb::DrawArrays);
|
||||
::_exit(3);
|
||||
});
|
||||
ASSERT_TRUE(DiedOfAbort(r)) << DescribeStatus(r) << "\n" << r.Log;
|
||||
EXPECT_NE(r.Log.find("Fatal{PipeVerifyBadKnob, \"MOBILEGL_PIPE_VERIFY_CORRUPT=NoSuchField\": "
|
||||
"no such field in kMGPipeInputFieldNames}"),
|
||||
std::string::npos)
|
||||
<< r.Log;
|
||||
EXPECT_EQ(r.Log.find("verify armed"), std::string::npos) << r.Log;
|
||||
EXPECT_EQ(r.Log.find("PipeVerifyDiffer"), std::string::npos) << r.Log;
|
||||
#endif
|
||||
}
|
||||
|
||||
// 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).
|
||||
TEST_F(PipeInputsTest, VerifyFatalOffLogsTheDivergenceAndContinues) {
|
||||
#if !MOBILEGL_PIPE_VERIFY
|
||||
GTEST_SKIP() << "verify not compiled in (MOBILEGL_PIPE_VERIFY=OFF)";
|
||||
#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";
|
||||
MGPipeFillForVerb(MGPipeVerb::Clear);
|
||||
const Uint64 serial = gPipeInputs.FilledState().CurrentVerbSerial + 1;
|
||||
const ChildResult r = RunInChild([] {
|
||||
MG_Config::Features.PipeVerify = true;
|
||||
MG_Config::Features.PipeVerifyFatal = false;
|
||||
MG_Config::Features.PipeVerifyCorrupt = "GetRenderStateParameters";
|
||||
MGPipeFillForVerb(MGPipeVerb::DrawArrays);
|
||||
MGPipeFillForVerb(MGPipeVerb::DrawElements);
|
||||
::_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;
|
||||
const std::string first = "Fatal{PipeVerifyDiffer, \"GetRenderStateParameters@DrawArrays\", verb=" +
|
||||
std::to_string(serial) + ", where=entry}";
|
||||
const std::string second = "Fatal{PipeVerifyDiffer, \"GetRenderStateParameters@DrawElements\", verb=" +
|
||||
std::to_string(serial + 1) + ", where=entry}";
|
||||
EXPECT_NE(r.Log.find(first), std::string::npos) << "expected " << first << " in\n" << r.Log;
|
||||
EXPECT_NE(r.Log.find(second), std::string::npos) << "expected " << second << " in\n" << r.Log;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Every verb fills exactly its class mask: after a fill, a field is fresh iff its bit is set
|
||||
// (sticky fields included, since every class mask carries them).
|
||||
TEST_F(PipeInputsTest, EveryVerbFillsItsClassAndNothingElse) {
|
||||
@@ -254,9 +481,11 @@ TEST_F(PipeInputsTest, EveryVerbFillsItsClassAndNothingElse) {
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
// Before anything logs: MG_Util::Debug::InitFile() reads the variable once, on the first
|
||||
// write, and caches the FILE*.
|
||||
// write, and caches the FILE*. The name carries this process's pid (see the file header),
|
||||
// and the file is removed on the way out; a forked child that aborts leaves it to us.
|
||||
namespace fs = std::filesystem;
|
||||
const fs::path path = fs::temp_directory_path() / "mobilegl-pipeinputs-test.log";
|
||||
const fs::path path =
|
||||
fs::temp_directory_path() / ("mobilegl-pipeinputs-test-" + std::to_string(ProcessId()) + ".log");
|
||||
std::error_code ec;
|
||||
fs::remove(path, ec);
|
||||
g_logPath = path.string();
|
||||
@@ -266,5 +495,7 @@ int main(int argc, char** argv) {
|
||||
setenv("MOBILEGL_LOG_FILE_PATH", g_logPath.c_str(), 1);
|
||||
#endif
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
const int rc = RUN_ALL_TESTS();
|
||||
fs::remove(path, ec);
|
||||
return rc;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user