mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-17 00:28:31 +09:00
[Merge] (MGPipe, P5): land c1f - the client's missing gates and controls (ID-64)
This commit is contained in:
@@ -1534,7 +1534,7 @@ namespace MobileGL::MG_Pipe {
|
|||||||
// not consulted, and under spawn a silent no-op that leaks every one of those objects.
|
// not consulted, and under spawn a silent no-op that leaks every one of those objects.
|
||||||
// The parameter type is the route's, which is byte-identical to the applier's
|
// The parameter type is the route's, which is byte-identical to the applier's
|
||||||
// (const MGPHandleOnly&, void return), so the fix is `&MGPipeRoute<Name>` at the five call
|
// (const MGPHandleOnly&, void return), so the fix is `&MGPipeRoute<Name>` at the five call
|
||||||
// sites; the grep gate scripts/../p5-c1 redcheck refuses any `&MGPipeApply` under MG_Impl/.
|
// sites; PipeCatalogue.FrontendNeverTakesAnApplierAddress checks MG_Impl in every unit lane.
|
||||||
Bool EmitDeleteIfPublished(MGPipeKind kind, MGPipeHandle handle,
|
Bool EmitDeleteIfPublished(MGPipeKind kind, MGPipeHandle handle,
|
||||||
void (*route)(const MGPHandleOnly&)) {
|
void (*route)(const MGPHandleOnly&)) {
|
||||||
if (!MGPipeHandleIsPublished(kind, handle)) return false;
|
if (!MGPipeHandleIsPublished(kind, handle)) return false;
|
||||||
|
|||||||
@@ -756,6 +756,8 @@ namespace MobileGL::MG_Remote::Client {
|
|||||||
// down session, which is what the "teardown legitimately reaches here" comment
|
// down session, which is what the "teardown legitimately reaches here" comment
|
||||||
// promised would NOT happen. DECLINED is honest - "the verb did not happen" - and the
|
// promised would NOT happen. DECLINED is honest - "the verb did not happen" - and the
|
||||||
// acceptance rows already treat it as `false` / nullptr without aborting.
|
// acceptance rows already treat it as `false` / nullptr without aborting.
|
||||||
|
// ReadPixels intentionally refuses this with Fatal{ReadbackDeclined, "ReadPixels"}:
|
||||||
|
// unlike acceptance rows it cannot return successfully without complete pixels.
|
||||||
if (statusOut != nullptr) *statusOut = Wire::ReplySink::kStatusDeclined;
|
if (statusOut != nullptr) *statusOut = Wire::ReplySink::kStatusDeclined;
|
||||||
MGLOG_E("MG_Remote client: the barrier for %s (seq %llu) woke on a dead doorbell; the "
|
MGLOG_E("MG_Remote client: the barrier for %s (seq %llu) woke on a dead doorbell; the "
|
||||||
"server is gone and this verb did not happen (reported as DECLINED, not ERROR)",
|
"server is gone and this verb did not happen (reported as DECLINED, not ERROR)",
|
||||||
|
|||||||
@@ -38,6 +38,8 @@
|
|||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
|
#include "WireTables.h"
|
||||||
|
|
||||||
namespace MobileGL::MG_Remote::Client {
|
namespace MobileGL::MG_Remote::Client {
|
||||||
|
|
||||||
// The slot arithmetic, asserted rather than commented. GlobalBackendFunctionsTable is
|
// The slot arithmetic, asserted rather than commented. GlobalBackendFunctionsTable is
|
||||||
@@ -117,6 +119,7 @@ namespace MobileGL::MG_Remote::Client {
|
|||||||
// slot is the "split lane ran monolith and went green" shape that every gate in this
|
// slot is the "split lane ran monolith and went green" shape that every gate in this
|
||||||
// phase exists to prevent (R-4).
|
// phase exists to prevent (R-4).
|
||||||
ClientSession& RequireSession(const char* slot) {
|
ClientSession& RequireSession(const char* slot) {
|
||||||
|
RequireClientTablesInstalled(slot);
|
||||||
ClientSession* session = ClientSession::Active();
|
ClientSession* session = ClientSession::Active();
|
||||||
if (session == nullptr) {
|
if (session == nullptr) {
|
||||||
MGLOG_F("MGPipe: Fatal{NoClientSession, \"%s\"} - the remote emit table is "
|
MGLOG_F("MGPipe: Fatal{NoClientSession, \"%s\"} - the remote emit table is "
|
||||||
|
|||||||
@@ -94,15 +94,7 @@ namespace MobileGL::MG_Remote::Client {
|
|||||||
// emit into a ring being freed is a use-after-free. The monolith adapters are put
|
// emit into a ring being freed is a use-after-free. The monolith adapters are put
|
||||||
// back only as the LAST step of teardown, for the at-exit ~BufferObject deletes that
|
// back only as the LAST step of teardown, for the at-exit ~BufferObject deletes that
|
||||||
// legitimately reach a process with no session (see ReinstallMonolithAfterTeardown).
|
// legitimately reach a process with no session (see ReinstallMonolithAfterTeardown).
|
||||||
if (g_clientTablesUninstalled.load(std::memory_order_acquire)) {
|
RequireClientTablesInstalled(row);
|
||||||
MGLOG_F("MGPipe: Fatal{ClientTablesUninstalled, \"%s\"} - a routed call reached the "
|
|
||||||
"client wire tables while ClientSession::Stop was tearing the session "
|
|
||||||
"down. R-4/R-17: the applier is server-exclusive and the rings this emit "
|
|
||||||
"would use are being freed, so the call is refused by name rather than run "
|
|
||||||
"on the caller",
|
|
||||||
row);
|
|
||||||
std::abort();
|
|
||||||
}
|
|
||||||
ClientSession* session = ClientSession::Active();
|
ClientSession* session = ClientSession::Active();
|
||||||
if (session == nullptr) {
|
if (session == nullptr) {
|
||||||
MGLOG_F("MGPipe: Fatal{NoClientSession, \"%s\"} - the client wire tables are "
|
MGLOG_F("MGPipe: Fatal{NoClientSession, \"%s\"} - the client wire tables are "
|
||||||
@@ -511,6 +503,14 @@ namespace MobileGL::MG_Remote::Client {
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
void RequireClientTablesInstalled(const char* row) {
|
||||||
|
if (g_clientTablesUninstalled.load(std::memory_order_acquire)) {
|
||||||
|
MGLOG_F("MGPipe: Fatal{ClientTablesUninstalled, \"%s\"} - the client tables are "
|
||||||
|
"being torn down; refusing before session or ring access", row);
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void InstallClientWireTables() {
|
void InstallClientWireTables() {
|
||||||
using namespace MG_Pipe;
|
using namespace MG_Pipe;
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
// barrier holds (table 3), and installing from the apply thread would publish the table to
|
// barrier holds (table 3), and installing from the apply thread would publish the table to
|
||||||
// the GL thread with no synchronisation at all.
|
// the GL thread with no synchronisation at all.
|
||||||
// `Uninstall()` runs at the TOP of `Stop()`, before the rings go away, so the last thing any
|
// `Uninstall()` runs at the TOP of `Stop()`, before the rings go away, so the last thing any
|
||||||
// straggling GL-thread call reaches is the monolith arm rather than a dangling session.
|
// straggling GL-thread call reaches is a named refusal rather than a dangling session.
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <Includes.h>
|
#include <Includes.h>
|
||||||
@@ -49,6 +49,9 @@ namespace MobileGL::MG_Remote::Client {
|
|||||||
// below, run only once the session's rings are freed. Idempotent.
|
// below, run only once the session's rings are freed. Idempotent.
|
||||||
void UninstallClientWireTables();
|
void UninstallClientWireTables();
|
||||||
|
|
||||||
|
// Shared by routed rows and the five class-B emitters, before session/ring access.
|
||||||
|
void RequireClientTablesInstalled(const char* row);
|
||||||
|
|
||||||
// The LAST step of ClientSession::Stop: after the rings, segments and transport are gone,
|
// The LAST step of ClientSession::Stop: after the rings, segments and transport are gone,
|
||||||
// puts the monolith adapters back and clears the refusal flag, so the at-exit ~BufferObject
|
// puts the monolith adapters back and clears the refusal flag, so the at-exit ~BufferObject
|
||||||
// deletes that reach a process with no session run the applier as they do under monolith.
|
// deletes that reach a process with no session run the applier as they do under monolith.
|
||||||
|
|||||||
@@ -13,6 +13,10 @@
|
|||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
#include <regex>
|
||||||
|
#include <sstream>
|
||||||
#include <iterator>
|
#include <iterator>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
@@ -34,6 +38,30 @@
|
|||||||
using namespace MobileGL;
|
using namespace MobileGL;
|
||||||
using namespace MobileGL::MG_Pipe;
|
using namespace MobileGL::MG_Pipe;
|
||||||
|
|
||||||
|
TEST(PipeCatalogue, FrontendNeverTakesAnApplierAddress) {
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
auto base = fs::current_path();
|
||||||
|
while (!fs::is_directory(base / "MobileGL/MG_Impl") && base != base.root_path())
|
||||||
|
base = base.parent_path();
|
||||||
|
const auto root = base / "MobileGL/MG_Impl";
|
||||||
|
ASSERT_TRUE(fs::is_directory(root)) << "frontend source unavailable: " << root;
|
||||||
|
const std::regex comments(R"(/\*[\s\S]*?\*/|//[^\n]*)");
|
||||||
|
const std::regex address(R"(&\s*MGPipeApply[A-Za-z_0-9]*)");
|
||||||
|
for (const auto& entry : fs::recursive_directory_iterator(root)) {
|
||||||
|
if (!entry.is_regular_file()) continue;
|
||||||
|
const auto ext = entry.path().extension();
|
||||||
|
if (ext != ".cpp" && ext != ".h" && ext != ".inc") continue;
|
||||||
|
std::ifstream input(entry.path());
|
||||||
|
ASSERT_TRUE(input.good()) << entry.path();
|
||||||
|
std::ostringstream bytes;
|
||||||
|
bytes << input.rdbuf();
|
||||||
|
const auto code = std::regex_replace(bytes.str(), comments, "");
|
||||||
|
EXPECT_FALSE(std::regex_search(code, address))
|
||||||
|
<< "FrontendApplierAddress: " << entry.path()
|
||||||
|
<< " must take the route address; routing tables live outside MG_Impl";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
// Counting expansions of the catalogue. The Class parameter is a real enumerator, so a
|
// Counting expansions of the catalogue. The Class parameter is a real enumerator, so a
|
||||||
// per-class count is a constant expression too.
|
// per-class count is a constant expression too.
|
||||||
|
|||||||
@@ -0,0 +1,367 @@
|
|||||||
|
// c1f: exercise installed client producers over a live inproc session.
|
||||||
|
// Peer substitutions supply adversarial INPUTS, never client state being asserted.
|
||||||
|
#if MGTEST_HAVE_FORK
|
||||||
|
namespace {
|
||||||
|
namespace Srv = MobileGL::MG_Remote::Server;
|
||||||
|
namespace Codec = MobileGL::MG_Remote::Wire;
|
||||||
|
|
||||||
|
// Test-only member access for replacing the peer at its existing interfaces. Explicit
|
||||||
|
// instantiations may name private members; no production test knob or ABI change is needed.
|
||||||
|
template<class Tag, typename Tag::Type member> struct PeerAccess {
|
||||||
|
friend typename Tag::Type PeerMember(Tag) { return member; }
|
||||||
|
};
|
||||||
|
struct DecoderTag {
|
||||||
|
using Type = Codec::PipeWireDecoder Srv::PipeApplier::*;
|
||||||
|
friend Type PeerMember(DecoderTag);
|
||||||
|
};
|
||||||
|
template struct PeerAccess<DecoderTag, &Srv::PipeApplier::m_decoder>;
|
||||||
|
struct RepliesTag {
|
||||||
|
using Type = Codec::ReplySink* Codec::PipeWireDecoder::*;
|
||||||
|
friend Type PeerMember(RepliesTag);
|
||||||
|
};
|
||||||
|
template struct PeerAccess<RepliesTag, &Codec::PipeWireDecoder::m_replies>;
|
||||||
|
struct BackendTag {
|
||||||
|
using Type = UniquePtr<MG_Backend::BackendObject> Srv::ServerLoop::*;
|
||||||
|
friend Type PeerMember(BackendTag);
|
||||||
|
};
|
||||||
|
template struct PeerAccess<BackendTag, &Srv::ServerLoop::m_backend>;
|
||||||
|
|
||||||
|
void StartControlSession() {
|
||||||
|
::alarm(15);
|
||||||
|
MG_Config::Transport = MG_Config::TransportMode::InProcess;
|
||||||
|
Srv::ServerSessionInstance().SetCapabilityBits(0);
|
||||||
|
Srv::ServerSessionInstance().SetConsumedSubsystems(kMGPipeSubsystemsMigratedAtP4a);
|
||||||
|
if (ClientSessionInstance().Start(MG_Config::TransportMode::InProcess, {}) != MOBILEGL_OK)
|
||||||
|
::_exit(81);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ReadPeer : Codec::WireVerbSink {
|
||||||
|
Int32 status = 0;
|
||||||
|
Uint64 size = 48;
|
||||||
|
Uint64 observed = 0;
|
||||||
|
Bool OnReadPixels(const MGPReadbackInfo& info, Uint64 seq, Codec::ReplySink* replies) override {
|
||||||
|
observed = info.DstSize;
|
||||||
|
Uint8 pixels[48];
|
||||||
|
std::memset(pixels, 0x37, sizeof(pixels));
|
||||||
|
replies->PostReply(seq, status, pixels, size);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
void Install() {
|
||||||
|
auto rc = Srv::ServerLoopInstance().RunOnApplyThread([](void* self) {
|
||||||
|
auto& decoder = Srv::ServerSessionInstance().Applier().*PeerMember(DecoderTag{});
|
||||||
|
decoder.SetVerbSink(static_cast<ReadPeer*>(self));
|
||||||
|
return MOBILEGL_OK;
|
||||||
|
}, this);
|
||||||
|
if (rc != MOBILEGL_OK) ::_exit(82);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ErrorPeer : Codec::ReplySink {
|
||||||
|
Int32 status = 2;
|
||||||
|
void PostReply(Uint64 seq, Int32, const void*, Uint64) override {
|
||||||
|
// Keep the decoded request's sequence: this is an ERROR answer, not a wrong-slot test.
|
||||||
|
Srv::ServerSessionInstance().Replies().PostReply(seq, status, nullptr, 0);
|
||||||
|
}
|
||||||
|
void Install() {
|
||||||
|
auto rc = Srv::ServerLoopInstance().RunOnApplyThread([](void* self) {
|
||||||
|
auto& decoder = Srv::ServerSessionInstance().Applier().*PeerMember(DecoderTag{});
|
||||||
|
decoder.*PeerMember(RepliesTag{}) = static_cast<ErrorPeer*>(self);
|
||||||
|
return MOBILEGL_OK;
|
||||||
|
}, this);
|
||||||
|
if (rc != MOBILEGL_OK) ::_exit(83);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CapsPeer : MG_Backend::BackendObject {
|
||||||
|
MG_Backend::DynamicBackendParameters dynamic{};
|
||||||
|
MG_Backend::GlobalBackendFunctionsTable table{};
|
||||||
|
RendererInfo renderer{};
|
||||||
|
void Initialize() override {}
|
||||||
|
Bool InitCapabilities() override { return true; }
|
||||||
|
Bool InitWindowSurface() override { return true; }
|
||||||
|
Bool InitPbufferSurface(EGLint, EGLint) override { return true; }
|
||||||
|
const RendererInfo& GetRendererInfo() const override { return renderer; }
|
||||||
|
String GetBackendAPIVersionString() const override { return "4.6"; }
|
||||||
|
const MG_Backend::GlobalBackendFunctionsTable& GetBackendFunctions() const override { return table; }
|
||||||
|
const MG_Backend::DynamicBackendParameters& GetDynamicParameters() const override { return dynamic; }
|
||||||
|
BackendType GetBackendType() const override { return BackendType::DirectGLES; }
|
||||||
|
};
|
||||||
|
|
||||||
|
void ExpectNamedAbort(const ChildResult& child, const char* diagnostic) {
|
||||||
|
EXPECT_TRUE(DiedOfAbort(child)) << DescribeStatus(child) << '\n' << child.Log;
|
||||||
|
EXPECT_NE(child.Log.find(diagnostic), std::string::npos) << child.Log;
|
||||||
|
}
|
||||||
|
void ExpectChildSuccess(const ChildResult& child) {
|
||||||
|
EXPECT_TRUE(WIFEXITED(child.Status) && WEXITSTATUS(child.Status) == 0)
|
||||||
|
<< DescribeStatus(child) << '\n' << child.Log;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#define C1F_TEARDOWN(Name, Call) \
|
||||||
|
TEST(RemoteClientControls, TeardownRefuses##Name##OnALiveSession) { \
|
||||||
|
const auto child = RunInChild([] { \
|
||||||
|
StartControlSession(); \
|
||||||
|
UninstallClientWireTables(); \
|
||||||
|
RemoteEmitTable().Call; \
|
||||||
|
}); \
|
||||||
|
ExpectNamedAbort(child, "Fatal{ClientTablesUninstalled, \"" #Name "\"}"); \
|
||||||
|
}
|
||||||
|
C1F_TEARDOWN(Clear, GL.Clear(0))
|
||||||
|
C1F_TEARDOWN(DrawArrays, GL.DrawArrays(0x0004, 0, 0))
|
||||||
|
C1F_TEARDOWN(ReadPixels, GL.ReadPixels(0, 0, 0, 0, 0x1908, 0x1401, nullptr))
|
||||||
|
C1F_TEARDOWN(BlitFramebuffer, GL.BlitFramebuffer(0,0,0,0,0,0,0,0,0,0x2600))
|
||||||
|
C1F_TEARDOWN(Present, Present())
|
||||||
|
#undef C1F_TEARDOWN
|
||||||
|
|
||||||
|
TEST(RemoteClientControls, ReadPixelsPutsTheTightExtentOnTheWire) {
|
||||||
|
const auto child = RunInChild([] {
|
||||||
|
StartControlSession();
|
||||||
|
ReadPeer peer;
|
||||||
|
peer.Install();
|
||||||
|
Uint8 pixels[48]{};
|
||||||
|
RemoteEmitTable().GL.ReadPixels(0, 0, 4, 3, 0x1908, 0x1401, pixels);
|
||||||
|
if (peer.observed != 48 || pixels[47] != 0x37) ::_exit(84);
|
||||||
|
ClientSessionInstance().Stop();
|
||||||
|
});
|
||||||
|
ExpectChildSuccess(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RemoteClientControls, ShortOkReadPixelsReplyRefusesByName) {
|
||||||
|
const auto child = RunInChild([] {
|
||||||
|
StartControlSession();
|
||||||
|
ReadPeer peer;
|
||||||
|
peer.size = 32;
|
||||||
|
peer.Install();
|
||||||
|
Uint8 pixels[48]{};
|
||||||
|
RemoteEmitTable().GL.ReadPixels(0, 0, 4, 3, 0x1908, 0x1401, pixels);
|
||||||
|
});
|
||||||
|
ExpectNamedAbort(child, "Fatal{ReadbackReplyShort, \"ReadPixels 32 < 48\"}");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RemoteClientControls, ErrorReadPixelsReplyRefusesByName) {
|
||||||
|
const auto child = RunInChild([] {
|
||||||
|
StartControlSession();
|
||||||
|
ReadPeer peer;
|
||||||
|
peer.status = 2;
|
||||||
|
peer.size = 0;
|
||||||
|
peer.Install();
|
||||||
|
Uint8 pixels[48]{};
|
||||||
|
RemoteEmitTable().GL.ReadPixels(0, 0, 4, 3, 0x1908, 0x1401, pixels);
|
||||||
|
});
|
||||||
|
ExpectNamedAbort(child, "Fatal{ReplyError, \"ReadPixels\"}");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RemoteClientControls, ResourceRespecifyErrorIsNotADecline) {
|
||||||
|
const auto child = RunInChild([] {
|
||||||
|
StartControlSession();
|
||||||
|
ErrorPeer peer;
|
||||||
|
peer.Install();
|
||||||
|
MGPResourceDesc desc{};
|
||||||
|
(void)MGPipeRouteResourceRespecify(desc, nullptr);
|
||||||
|
});
|
||||||
|
ExpectNamedAbort(child, "Fatal{ReplyError, \"resource_respecify\"}");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RemoteClientControls, MapPersistentErrorIsNotADecline) {
|
||||||
|
const auto child = RunInChild([] {
|
||||||
|
StartControlSession();
|
||||||
|
ErrorPeer peer;
|
||||||
|
peer.Install();
|
||||||
|
MGPHandleOnly handle{};
|
||||||
|
(void)MGPipeRouteMapPersistent(handle, 64, nullptr);
|
||||||
|
});
|
||||||
|
ExpectNamedAbort(child, "Fatal{ReplyError, \"map_persistent\"}");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RemoteClientControls, RepeatedMakeCurrentAdoptsRepublishedCapsWithoutAPumpOrPresent) {
|
||||||
|
const auto child = RunInChild([] {
|
||||||
|
StartControlSession();
|
||||||
|
if (Srv::ServerLoopInstance().RunOnApplyThread([](void*) {
|
||||||
|
auto backend = MakeUnique<CapsPeer>();
|
||||||
|
backend->dynamic.MaxComputeWorkGroupCount[0] = 113;
|
||||||
|
Srv::ServerSessionInstance().SetBackend(backend.get());
|
||||||
|
Srv::ServerLoopInstance().*PeerMember(BackendTag{}) = Move(backend);
|
||||||
|
return MOBILEGL_OK;
|
||||||
|
}, nullptr) != MOBILEGL_OK) ::_exit(85);
|
||||||
|
BackendObject_Remote remote;
|
||||||
|
auto dpy = reinterpret_cast<EGLDisplay>(1);
|
||||||
|
auto surface = reinterpret_cast<EGLSurface>(2);
|
||||||
|
auto ctx = reinterpret_cast<EGLContext>(3);
|
||||||
|
if (!remote.InitializeEGLDisplay(dpy, nullptr, nullptr) ||
|
||||||
|
!remote.CreateEGLPbufferSurface(surface, 4, 4) ||
|
||||||
|
!remote.MakeEGLCurrent(dpy, surface, surface, ctx)) ::_exit(86);
|
||||||
|
const auto generation = CapsMirrorInstance().Generation();
|
||||||
|
GLint before = 0;
|
||||||
|
remote.GetBackendFunctions().GL.GetIntegeri_v(0x91BE, 0, &before);
|
||||||
|
if (before != 113) ::_exit(87);
|
||||||
|
if (Srv::ServerLoopInstance().RunOnApplyThread([](void*) {
|
||||||
|
static_cast<CapsPeer*>(Srv::ServerLoopInstance().Backend())->dynamic.MaxComputeWorkGroupCount[0] = 227;
|
||||||
|
return MOBILEGL_OK;
|
||||||
|
}, nullptr) != MOBILEGL_OK) ::_exit(88);
|
||||||
|
if (!remote.MakeEGLCurrent(dpy, surface, surface, ctx)) ::_exit(89);
|
||||||
|
GLint after = 0;
|
||||||
|
remote.GetBackendFunctions().GL.GetIntegeri_v(0x91BE, 0, &after);
|
||||||
|
if (CapsMirrorInstance().Generation() <= generation || after != 227) ::_exit(90);
|
||||||
|
ClientSessionInstance().Stop();
|
||||||
|
});
|
||||||
|
ExpectChildSuccess(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RemoteClientControls, BounceReadPixelsShortReplyRefusesBeforeScatter) {
|
||||||
|
const auto child = RunInChild([] {
|
||||||
|
StartControlSession();
|
||||||
|
MG_State::pGLContext = MakeUnique<MG_State::GLState::GLContext>();
|
||||||
|
MG_State::pGLContext->SetPixelStoreParam(PixelStoreParam::PackRowLength, 8);
|
||||||
|
ReadPeer peer;
|
||||||
|
peer.size = 32;
|
||||||
|
peer.Install();
|
||||||
|
Uint8 pixels[128]{};
|
||||||
|
RemoteEmitTable().GL.ReadPixels(0, 0, 4, 3, 0x1908, 0x1401, pixels);
|
||||||
|
});
|
||||||
|
ExpectNamedAbort(child, "Fatal{ReadbackReplyShort, \"ReadPixels 32 < 48\"}");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RemoteClientControls, DeleteTexturesAdvancesTheWireOrdinal) {
|
||||||
|
const auto child = RunInChild([] {
|
||||||
|
CapsPeer backend;
|
||||||
|
Srv::ServerSessionInstance().SetBackend(&backend);
|
||||||
|
StartControlSession();
|
||||||
|
ErrorPeer replies;
|
||||||
|
replies.status = 0; // The peer accepts the resource; the client must publish and delete it.
|
||||||
|
replies.Install();
|
||||||
|
MG_Config::Features.PipePush |= kMGPipeSubsystemResources | kMGPipeSubsystemTextureResources |
|
||||||
|
kMGPipeSubsystemSamplers;
|
||||||
|
MG_State::pGLContext = MakeUnique<MG_State::GLState::GLContext>();
|
||||||
|
GLuint texture = 0;
|
||||||
|
MG_Impl::GLImpl::GenTextures(1, &texture);
|
||||||
|
MG_Impl::GLImpl::BindTexture(0x0DE1, texture);
|
||||||
|
MG_Impl::GLImpl::TexImage2D(0x0DE1, 0, 0x8058, 1, 1, 0, 0x1908, 0x1401, nullptr);
|
||||||
|
const auto before = ClientWireRecordsEmitted();
|
||||||
|
MG_Impl::GLImpl::DeleteTextures(1, &texture);
|
||||||
|
const auto after = ClientWireRecordsEmitted();
|
||||||
|
if (before == 0 || after <= before) ::_exit(91);
|
||||||
|
ClientSessionInstance().Stop();
|
||||||
|
});
|
||||||
|
ExpectChildSuccess(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RemoteClientControls, BoundPackBufferOffsetReadRefusesByName) {
|
||||||
|
const auto child = RunInChild([] {
|
||||||
|
StartControlSession();
|
||||||
|
MG_State::pGLContext = MakeUnique<MG_State::GLState::GLContext>();
|
||||||
|
GLuint buffer = 0;
|
||||||
|
MG_Impl::GLImpl::GenBuffers(1, &buffer);
|
||||||
|
MG_Impl::GLImpl::BindBuffer(0x88EB, buffer);
|
||||||
|
MG_Impl::GLImpl::BufferData(0x88EB, 64, nullptr, 0x88E1);
|
||||||
|
RemoteEmitTable().GL.ReadPixels(0, 0, 1, 1, 0x1908, 0x1401, reinterpret_cast<void*>(16));
|
||||||
|
});
|
||||||
|
ExpectNamedAbort(child, "Fatal{UnmigratedVerb, \"ReadPixels+PACK_BUFFER\"}");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RemoteClientControls, ServerRoleRespecifyDoesNotEmitClientInitialBytes) {
|
||||||
|
const auto child = RunInChild([] {
|
||||||
|
CapsPeer backend;
|
||||||
|
Srv::ServerSessionInstance().SetBackend(&backend);
|
||||||
|
StartControlSession();
|
||||||
|
MG_Config::Features.PipePush |= kMGPipeSubsystemResources;
|
||||||
|
const auto before = ClientWireRecordsEmitted();
|
||||||
|
if (Srv::ServerLoopInstance().RunOnApplyThread([](void*) {
|
||||||
|
MG_State::GLState::BufferObject buffer(701);
|
||||||
|
Uint8 data[64]{};
|
||||||
|
buffer.Respecify(sizeof(data), data);
|
||||||
|
return MOBILEGL_OK;
|
||||||
|
}, nullptr) != MOBILEGL_OK) ::_exit(92);
|
||||||
|
if (ClientWireRecordsEmitted() != before) ::_exit(93);
|
||||||
|
ClientSessionInstance().Stop();
|
||||||
|
});
|
||||||
|
ExpectChildSuccess(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RemoteClientControls, HeaderOnlyDynamicStateCrossesWithoutBlobMissing) {
|
||||||
|
const auto child = RunInChild([] {
|
||||||
|
StartControlSession();
|
||||||
|
MGPDynamicState record{};
|
||||||
|
record.ChunkMask = 0;
|
||||||
|
const auto before = ClientWireRecordsEmitted();
|
||||||
|
gMGPipeContext.SetDynamicState(&record, nullptr, 0);
|
||||||
|
if (ClientWireRecordsEmitted() != before + 1) ::_exit(94);
|
||||||
|
ClientSessionInstance().Stop();
|
||||||
|
});
|
||||||
|
ExpectChildSuccess(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RemoteClientControls, ServerRoleFlushDoesNotRunTheClientSubDataFollowup) {
|
||||||
|
const auto child = RunInChild([] {
|
||||||
|
CapsPeer backend;
|
||||||
|
Srv::ServerSessionInstance().SetBackend(&backend);
|
||||||
|
StartControlSession();
|
||||||
|
MG_Config::Features.PipePush |= kMGPipeSubsystemResources;
|
||||||
|
const auto rc = Srv::ServerLoopInstance().RunOnApplyThread([](void*) {
|
||||||
|
static int subdata = 0, flushes = 0;
|
||||||
|
MGPipeResourceOps ops{};
|
||||||
|
ops.Create = [](MGPipeHandle, const MGPResourceDesc&) {};
|
||||||
|
ops.Respecify = [](MGPipeHandle, const MGPResourceDesc&, const void*) {};
|
||||||
|
ops.SubData = [](MGPipeHandle, const MGPSubData&, const void*) { ++subdata; };
|
||||||
|
ops.FlushRange = [](MGPipeHandle, const MGPFlushRange&, const void*) { ++flushes; };
|
||||||
|
ops.Destroy = [](MGPipeHandle) {};
|
||||||
|
MGPipeSetResourceOps(&ops);
|
||||||
|
{
|
||||||
|
MG_State::GLState::BufferObject buffer(702);
|
||||||
|
buffer.Resize(64);
|
||||||
|
subdata = flushes = 0;
|
||||||
|
MGPipeEmitResourceFlushRange(buffer, 16, 16, 0);
|
||||||
|
if (subdata != 0 || flushes != 1) ::_exit(95);
|
||||||
|
}
|
||||||
|
MGPipeSetResourceOps(nullptr);
|
||||||
|
return MOBILEGL_OK;
|
||||||
|
}, nullptr);
|
||||||
|
if (rc != MOBILEGL_OK) ::_exit(96);
|
||||||
|
ClientSessionInstance().Stop();
|
||||||
|
});
|
||||||
|
ExpectChildSuccess(child);
|
||||||
|
}
|
||||||
|
// Uses the shipped EGL/GL frontend and real DirectGLES driver. Invoked explicitly by the
|
||||||
|
// package gate because a native driver is not a prerequisite of the unit lane.
|
||||||
|
int RunPackGpuControl(bool split) {
|
||||||
|
const auto child = RunInChild([=] {
|
||||||
|
::alarm(45);
|
||||||
|
setenv("EGL_PLATFORM", "surfaceless", 1);
|
||||||
|
setenv("MOBILEGL_BACKEND_TYPE", "DirectGLES", 1);
|
||||||
|
setenv("MOBILEGL_TRANSPORT", split ? "inproc" : "monolith", 1);
|
||||||
|
MobileGL::Initialize();
|
||||||
|
namespace E = MG_Impl::EGLImpl;
|
||||||
|
namespace G = MG_Impl::GLImpl;
|
||||||
|
const auto display = E::GetDisplay(EGL_DEFAULT_DISPLAY);
|
||||||
|
if (!E::Initialize(display, nullptr, nullptr)) ::_exit(101);
|
||||||
|
E::BindAPI(EGL_OPENGL_API);
|
||||||
|
EGLConfig config{};
|
||||||
|
EGLint count = 0;
|
||||||
|
const EGLint configAttribs[] = {EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
|
||||||
|
EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, EGL_NONE};
|
||||||
|
if (!E::ChooseConfig(display, configAttribs, &config, 1, &count) || count < 1) ::_exit(102);
|
||||||
|
const auto context = E::CreateContext(display, config, EGL_NO_CONTEXT, nullptr);
|
||||||
|
const EGLint surfaceAttribs[] = {EGL_WIDTH, 4, EGL_HEIGHT, 4, EGL_NONE};
|
||||||
|
const auto surface = E::CreatePbufferSurface(display, config, surfaceAttribs);
|
||||||
|
if (!E::MakeCurrent(display, surface, surface, context)) ::_exit(103);
|
||||||
|
G::ClearColor(0, 1, 0, 1);
|
||||||
|
G::Clear(GL_COLOR_BUFFER_BIT);
|
||||||
|
GLuint pbo = 0;
|
||||||
|
G::GenBuffers(1, &pbo);
|
||||||
|
G::BindBuffer(GL_PIXEL_PACK_BUFFER, pbo);
|
||||||
|
G::BufferData(GL_PIXEL_PACK_BUFFER, 64, nullptr, GL_STREAM_READ);
|
||||||
|
G::ReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, reinterpret_cast<void*>(16));
|
||||||
|
if (split) ::_exit(104); // Must have refused by name, never returned or dereferenced 16.
|
||||||
|
Uint8 pixels[4]{};
|
||||||
|
G::GetBufferSubData(GL_PIXEL_PACK_BUFFER, 16, 4, pixels);
|
||||||
|
if (pixels[0] != 0 || pixels[1] != 255 || pixels[2] != 0 || pixels[3] != 255) ::_exit(105);
|
||||||
|
std::printf("C1F_PACK_MONOLITH bytes at offset 16: %u %u %u %u\n", pixels[0], pixels[1], pixels[2], pixels[3]);
|
||||||
|
std::fflush(stdout);
|
||||||
|
});
|
||||||
|
const bool ok = split
|
||||||
|
? DiedOfAbort(child) && child.Log.find("Fatal{UnmigratedVerb, \"ReadPixels+PACK_BUFFER\"}") != std::string::npos
|
||||||
|
: WIFEXITED(child.Status) && WEXITSTATUS(child.Status) == 0;
|
||||||
|
std::printf("C1F_PACK_%s: %s (%s)\n%s", split ? "INPROC" : "MONOLITH",
|
||||||
|
ok ? "PASS" : "FAIL", DescribeStatus(child).c_str(), child.Log.c_str());
|
||||||
|
return ok ? 0 : 1;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -6,9 +6,9 @@
|
|||||||
// SPDX-License-Identifier: LGPL-3.0-only
|
// SPDX-License-Identifier: LGPL-3.0-only
|
||||||
// End of Source File Header
|
// End of Source File Header
|
||||||
|
|
||||||
// P5 package c1's suite: the 71-slot emit table, the caps mirror and R-8's liveness gates. No
|
// P5 package c1's suite: the 71-slot emit table, the caps mirror and R-8's liveness gates.
|
||||||
// session, no transport and no thread - s1's SessionTest owns those and w1's PipeWireCodecTest
|
// The helper cases below are supplemented by RemoteClientControls.inc: installed producers
|
||||||
// owns the bytes.
|
// over a live session, with adversarial peer replies and a repeated-make-current control.
|
||||||
//
|
//
|
||||||
// IT LINKS gtest RATHER THAN gtest_main AND CARRIES ITS OWN main(), for PipeWireCodecTest's and
|
// IT LINKS gtest RATHER THAN gtest_main AND CARRIES ITS OWN main(), for PipeWireCodecTest's and
|
||||||
// PipeInputsTest's reason: the Fatal arms report through MGLOG_F + std::abort, and MGLOG_F
|
// PipeInputsTest's reason: the Fatal arms report through MGLOG_F + std::abort, and MGLOG_F
|
||||||
@@ -38,6 +38,19 @@
|
|||||||
#include <MG_Pipe/PipeRoute.h>
|
#include <MG_Pipe/PipeRoute.h>
|
||||||
#include <MG_Remote/Client/EmitTables.h>
|
#include <MG_Remote/Client/EmitTables.h>
|
||||||
#include <MG_Remote/Client/WireTables.h>
|
#include <MG_Remote/Client/WireTables.h>
|
||||||
|
#include <MG_Remote/Client/ClientSession.h>
|
||||||
|
#include <MG_Remote/Client/BackendObject_Remote.h>
|
||||||
|
#include <MG_Remote/Server/ServerLoop.h>
|
||||||
|
#include <MG_State/GLState/Core.h>
|
||||||
|
#include <MG_Impl/GLImpl/Texture/GL_Texture.h>
|
||||||
|
#include <MG_Impl/GLImpl/Buffer/GL_Buffer.h>
|
||||||
|
#include <MG_Pipe/PipeMutation.h>
|
||||||
|
#include <MG_Pipe/PipeApply.h>
|
||||||
|
#include <Init.h>
|
||||||
|
#include <MG_Impl/EGLImpl/EGLImpl.h>
|
||||||
|
#include <MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.h>
|
||||||
|
#include <MG_Impl/GLImpl/Drawing/GL_Drawing.h>
|
||||||
|
#include <MG_Impl/GLImpl/RenderState/GL_RenderState.h>
|
||||||
|
|
||||||
#if !defined(_WIN32)
|
#if !defined(_WIN32)
|
||||||
#include <csignal>
|
#include <csignal>
|
||||||
@@ -713,6 +726,47 @@ TEST(PipeRouting, TheInstalledClientArmIsWireAndNotMonolithAndEveryRoutedRowMove
|
|||||||
<< "InstallClientWireTables did not record the client-wire arm";
|
<< "InstallClientWireTables did not record the client-wire arm";
|
||||||
|
|
||||||
const SizeT movedScreen = CountDifferingCells(gMGPipeScreen, MGPipeMonolithScreen());
|
const SizeT movedScreen = CountDifferingCells(gMGPipeScreen, MGPipeMonolithScreen());
|
||||||
|
#define C1F_MOVED(Table, Row) EXPECT_NE(gMGPipe##Table.Row, MGPipeMonolith##Table().Row) << #Table "." #Row
|
||||||
|
C1F_MOVED(Screen, ResourceCreate);
|
||||||
|
C1F_MOVED(Screen, ResourceDestroy);
|
||||||
|
C1F_MOVED(Screen, UnmapPersistent);
|
||||||
|
C1F_MOVED(Context, CreateRenderState);
|
||||||
|
C1F_MOVED(Context, BindRenderState);
|
||||||
|
C1F_MOVED(Context, DeleteRenderState);
|
||||||
|
C1F_MOVED(Context, CreateVertexElements);
|
||||||
|
C1F_MOVED(Context, BindVertexElements);
|
||||||
|
C1F_MOVED(Context, DeleteVertexElements);
|
||||||
|
C1F_MOVED(Context, CreateSamplerState);
|
||||||
|
C1F_MOVED(Context, DeleteSamplerState);
|
||||||
|
C1F_MOVED(Context, CreateSamplerView);
|
||||||
|
C1F_MOVED(Context, DeleteSamplerView);
|
||||||
|
C1F_MOVED(Context, BindShaderState);
|
||||||
|
C1F_MOVED(Context, DeleteShaderState);
|
||||||
|
C1F_MOVED(Context, SetDrawProgram);
|
||||||
|
C1F_MOVED(Context, SetDispatchProgram);
|
||||||
|
C1F_MOVED(Context, SetDynamicState);
|
||||||
|
C1F_MOVED(Context, SetFramebufferState);
|
||||||
|
C1F_MOVED(Context, SetVertexBuffers);
|
||||||
|
C1F_MOVED(Context, SetIndexBuffer);
|
||||||
|
C1F_MOVED(Context, SetSamplerViews);
|
||||||
|
C1F_MOVED(Context, BindSamplerStates);
|
||||||
|
C1F_MOVED(Context, SetShaderImages);
|
||||||
|
C1F_MOVED(Context, SetGlobalConstants);
|
||||||
|
C1F_MOVED(Context, SetVertexAttribDefaults);
|
||||||
|
C1F_MOVED(Context, SetPixelPackState);
|
||||||
|
C1F_MOVED(Context, SetPatchState);
|
||||||
|
C1F_MOVED(Context, SetResidualValueState);
|
||||||
|
C1F_MOVED(Context, SetTextureParams);
|
||||||
|
C1F_MOVED(Context, ResourceSubData);
|
||||||
|
C1F_MOVED(Context, BufferSubDataResident);
|
||||||
|
C1F_MOVED(Context, ResourceReadback);
|
||||||
|
#undef C1F_MOVED
|
||||||
|
#define C1F_ESCAPE(Row) EXPECT_NE(gMGPipeRouteEscapes.Row, MGPipeMonolithEscapes().Row) << #Row
|
||||||
|
C1F_ESCAPE(ResourceRespecify);
|
||||||
|
C1F_ESCAPE(ResourceFlushRange);
|
||||||
|
C1F_ESCAPE(MapPersistent);
|
||||||
|
C1F_ESCAPE(CreateShaderState);
|
||||||
|
#undef C1F_ESCAPE
|
||||||
const SizeT movedContext = CountDifferingCells(gMGPipeContext, MGPipeMonolithContext());
|
const SizeT movedContext = CountDifferingCells(gMGPipeContext, MGPipeMonolithContext());
|
||||||
EXPECT_EQ(movedScreen + movedContext, 33u)
|
EXPECT_EQ(movedScreen + movedContext, 33u)
|
||||||
<< "exactly the 33 generated routed rows must differ from the monolith adapters; "
|
<< "exactly the 33 generated routed rows must differ from the monolith adapters; "
|
||||||
@@ -792,6 +846,8 @@ TEST(RemoteReadback, AReplyIsScatteredOnlyWhenItIsOkAndExactlyTheReadsExtent) {
|
|||||||
EXPECT_FALSE(ReadbackReplyIsComplete(kOk, 0, tight)) << "an OK reply of zero bytes is not the extent";
|
EXPECT_FALSE(ReadbackReplyIsComplete(kOk, 0, tight)) << "an OK reply of zero bytes is not the extent";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#include "RemoteClientControls.inc"
|
||||||
|
|
||||||
int main(int argc, char** argv) {
|
int main(int argc, char** argv) {
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
const fs::path path =
|
const fs::path path =
|
||||||
@@ -803,6 +859,15 @@ int main(int argc, char** argv) {
|
|||||||
_putenv_s("MOBILEGL_LOG_FILE_PATH", g_logPath.c_str());
|
_putenv_s("MOBILEGL_LOG_FILE_PATH", g_logPath.c_str());
|
||||||
#else
|
#else
|
||||||
setenv("MOBILEGL_LOG_FILE_PATH", g_logPath.c_str(), 1);
|
setenv("MOBILEGL_LOG_FILE_PATH", g_logPath.c_str(), 1);
|
||||||
|
#endif
|
||||||
|
#if MGTEST_HAVE_FORK
|
||||||
|
// Explicit GPU control, kept out of the CPU-only unit label. No silent skip is allowed.
|
||||||
|
if (argc == 2 && std::string(argv[1]).starts_with("--c1f-pack-gpu=")) {
|
||||||
|
const bool split = std::string(argv[1]) == "--c1f-pack-gpu=inproc";
|
||||||
|
const int rc = RunPackGpuControl(split);
|
||||||
|
fs::remove(path, ec);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
::testing::InitGoogleTest(&argc, argv);
|
::testing::InitGoogleTest(&argc, argv);
|
||||||
const int rc = RUN_ALL_TESTS();
|
const int rc = RUN_ALL_TESTS();
|
||||||
|
|||||||
@@ -0,0 +1,159 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""c1f mutation gates. Run from the worktree, links ON, after building split.
|
||||||
|
Every mutation must compile, fail its named test, then restore/build/pass that test.
|
||||||
|
No git operation, server source edit, or unrelated failure is accepted as red.
|
||||||
|
"""
|
||||||
|
import argparse
|
||||||
|
import pathlib
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
ROOT = pathlib.Path(__file__).resolve().parents[3]
|
||||||
|
EMIT = 'MobileGL/MG_Remote/Client/EmitTables.cpp'
|
||||||
|
WIRE = 'MobileGL/MG_Remote/Client/WireTables.cpp'
|
||||||
|
FILL = 'MobileGL/MG_Impl/Pipe/PipeFill.cpp'
|
||||||
|
BACKEND = 'MobileGL/MG_Remote/Client/BackendObject_Remote.cpp'
|
||||||
|
SUITE = 'RemoteClientControls.'
|
||||||
|
|
||||||
|
def replace(old, new, count=1):
|
||||||
|
def change(src):
|
||||||
|
if src.count(old) < count:
|
||||||
|
raise RuntimeError('mutation anchor absent: ' + old)
|
||||||
|
return src.replace(old, new, count)
|
||||||
|
return change
|
||||||
|
|
||||||
|
def escape_error(src):
|
||||||
|
result, count = re.subn(r' if \(status == 2\) \{.*?\n \}', '', src,
|
||||||
|
flags=re.S)
|
||||||
|
if count != 2:
|
||||||
|
raise RuntimeError(f'expected two escape ERROR blocks, got {count}')
|
||||||
|
return result
|
||||||
|
|
||||||
|
def helper_size(src):
|
||||||
|
return replace('ReadbackBytesPerPixel(format, type);\n }',
|
||||||
|
'ReadbackBytesPerPixel(format, type) + 16;\n }')(src)
|
||||||
|
|
||||||
|
def cases():
|
||||||
|
both = 'RequireReadbackReplyComplete(status, replySize, tight);'
|
||||||
|
site = '&MGPipeRouteResourceDestroy);'
|
||||||
|
return [
|
||||||
|
('B1-source', FILL, replace(site, '&MGPipeApplyResourceDestroy);'),
|
||||||
|
['PipeCatalogue.FrontendNeverTakesAnApplierAddress'], 'PipeCatalogueTest'),
|
||||||
|
('B1-wire', FILL, replace(site, '&MGPipeApplyResourceDestroy);'),
|
||||||
|
[SUITE+'DeleteTexturesAdvancesTheWireOrdinal'], 'RemoteClientTest'),
|
||||||
|
('N2-five-verbs', EMIT, replace(' RequireClientTablesInstalled(slot);', ''),
|
||||||
|
[SUITE+'TeardownRefuses'+n+'OnALiveSession' for n in
|
||||||
|
['Clear','DrawArrays','ReadPixels','BlitFramebuffer','Present']], 'RemoteClientTest'),
|
||||||
|
('M2-production-deleted', EMIT, replace(both, '', 2),
|
||||||
|
[SUITE+'ShortOkReadPixelsReplyRefusesByName', SUITE+'BounceReadPixelsShortReplyRefusesBeforeScatter',
|
||||||
|
SUITE+'ErrorReadPixelsReplyRefusesByName'], 'RemoteClientTest'),
|
||||||
|
('M2-fast-deleted', EMIT, replace(both, ''),
|
||||||
|
[SUITE+'ShortOkReadPixelsReplyRefusesByName'], 'RemoteClientTest'),
|
||||||
|
('M2-bounce-deleted', EMIT, replace(' '+both+'\n Scatter', ' Scatter'),
|
||||||
|
[SUITE+'BounceReadPixelsShortReplyRefusesBeforeScatter'], 'RemoteClientTest'),
|
||||||
|
('M3-production-plus16', EMIT, replace('info.DstSize = tight;', 'info.DstSize = tight + 16;'),
|
||||||
|
[SUITE+'ReadPixelsPutsTheTightExtentOnTheWire'], 'RemoteClientTest'),
|
||||||
|
('M3-shared-size', EMIT, helper_size,
|
||||||
|
[SUITE+'ReadPixelsPutsTheTightExtentOnTheWire'], 'RemoteClientTest'),
|
||||||
|
('M4-escape-errors-deleted', WIRE, escape_error,
|
||||||
|
[SUITE+'ResourceRespecifyErrorIsNotADecline', SUITE+'MapPersistentErrorIsNotADecline'], 'RemoteClientTest'),
|
||||||
|
('M5-server-role-guards-deleted', FILL,
|
||||||
|
replace('!MG_Remote::Client::RunsAsTheServerRole() && ', '', 2),
|
||||||
|
[SUITE+'ServerRoleRespecifyDoesNotEmitClientInitialBytes'], 'RemoteClientTest'),
|
||||||
|
('M5-server-flush-guard-deleted', FILL,
|
||||||
|
replace('!MG_Remote::Client::RunsAsTheServerRole() && size != 0', 'size != 0'),
|
||||||
|
[SUITE+'ServerRoleFlushDoesNotRunTheClientSubDataFollowup'], 'RemoteClientTest'),
|
||||||
|
('M8-real-pack-binding', EMIT,
|
||||||
|
replace('if (MG_State::pGLContext != nullptr &&', 'if (false && MG_State::pGLContext != nullptr &&'),
|
||||||
|
[SUITE+'BoundPackBufferOffsetReadRefusesByName'], 'RemoteClientTest'),
|
||||||
|
('codex12-repeat-skip', BACKEND,
|
||||||
|
replace('if (draw != EGL_NO_SURFACE && ctx != EGL_NO_CONTEXT) {', 'if (false) {'),
|
||||||
|
[SUITE+'RepeatedMakeCurrentAdoptsRepublishedCapsWithoutAPumpOrPresent'], 'RemoteClientTest'),
|
||||||
|
('BlobMissing-optional-to-required', WIRE,
|
||||||
|
replace('record.Blob = StageOptional(session, blobBytes, blobByteCount);',
|
||||||
|
'record.Blob = StageRequired(session, "SetDynamicState", blobBytes, blobByteCount);'),
|
||||||
|
[SUITE+'HeaderOnlyDynamicStateCrossesWithoutBlobMissing'], 'RemoteClientTest'),
|
||||||
|
('N6-compensating-wrong-row', WIRE,
|
||||||
|
replace('gMGPipeScreen.ResourceDestroy = &Wire_ResourceDestroy;',
|
||||||
|
'gMGPipeScreen.FenceCreate = &Wire_ResourceDestroy;'),
|
||||||
|
['PipeRouting.TheInstalledClientArmIsWireAndNotMonolithAndEveryRoutedRowMoved'], 'RemoteClientTest'),
|
||||||
|
]
|
||||||
|
|
||||||
|
def command(args):
|
||||||
|
p = subprocess.run(args, cwd=ROOT, text=True, stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.STDOUT, timeout=180)
|
||||||
|
return p.returncode, p.stdout
|
||||||
|
|
||||||
|
def verdict(build_rc, run_rc, output, names):
|
||||||
|
if build_rc:
|
||||||
|
return 'HARNESS-FAIL'
|
||||||
|
if run_rc and all('[ FAILED ] '+name+' (' in output for name in names):
|
||||||
|
return 'RED'
|
||||||
|
return 'WRONG-VERDICT'
|
||||||
|
|
||||||
|
def self_test():
|
||||||
|
names = ['Own.Test']
|
||||||
|
probes = [(1, 1, '[ FAILED ] Own.Test (0 ms)', 'HARNESS-FAIL'),
|
||||||
|
(0, -11, 'UNRELATED_FAILURE\nSegmentation fault', 'WRONG-VERDICT'),
|
||||||
|
(0, 0, '[ PASSED ] 1 test.', 'WRONG-VERDICT'),
|
||||||
|
(0, 0, '[ FAILED ] Own.Test (0 ms)', 'WRONG-VERDICT'),
|
||||||
|
(0, 1, '[ FAILED ] Own.Test (0 ms)', 'RED')]
|
||||||
|
for b, rc, out, expected in probes:
|
||||||
|
assert verdict(b, rc, out, names) == expected
|
||||||
|
print('RUNNER_META 5/5: unrelated crash, all green, build failure, false status, own failure')
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument('--self-test', action='store_true')
|
||||||
|
parser.add_argument('--case', default='')
|
||||||
|
args = parser.parse_args()
|
||||||
|
self_test()
|
||||||
|
if args.self_test:
|
||||||
|
return 0
|
||||||
|
failures = []
|
||||||
|
selected = [case for case in cases() if args.case in case[0]]
|
||||||
|
if not selected:
|
||||||
|
print('HARNESS-FAIL: no matching mutation')
|
||||||
|
return 1
|
||||||
|
for label, file, mutate, names, target in selected:
|
||||||
|
path = ROOT / file
|
||||||
|
original = path.read_bytes()
|
||||||
|
binary = ROOT / 'build-split/MobileGL/MG_Test' / ('Pipe' if target == 'PipeCatalogueTest' else 'Wire') / target
|
||||||
|
build = ['cmake', '--build', 'build-split', '-j', '24', '--target', target]
|
||||||
|
run = [str(binary), '--gtest_filter='+':'.join(names)]
|
||||||
|
print('\n=== '+label+' ===', flush=True)
|
||||||
|
try:
|
||||||
|
brc, out = command(build)
|
||||||
|
if brc:
|
||||||
|
raise RuntimeError('baseline build failed\n'+out)
|
||||||
|
rc, out = command(run)
|
||||||
|
if rc or any('[ OK ] '+name+' (' not in out for name in names):
|
||||||
|
raise RuntimeError('baseline not green\n'+out)
|
||||||
|
path.write_text(mutate(original.decode()))
|
||||||
|
brc, out = command(build)
|
||||||
|
rc = 0
|
||||||
|
if not brc:
|
||||||
|
rc, out = command(run)
|
||||||
|
result = verdict(brc, rc, out, names)
|
||||||
|
print(out, end='')
|
||||||
|
print(f'{label}: {result} build={brc} run={rc}', flush=True)
|
||||||
|
if result != 'RED':
|
||||||
|
failures.append(label)
|
||||||
|
except Exception as exc:
|
||||||
|
print('HARNESS-FAIL', exc, flush=True)
|
||||||
|
failures.append(label)
|
||||||
|
finally:
|
||||||
|
path.write_bytes(original)
|
||||||
|
brc, out = command(build)
|
||||||
|
rc, out = command(run) if brc == 0 else (brc, out)
|
||||||
|
if rc or any('[ OK ] '+name+' (' not in out for name in names):
|
||||||
|
print('RESTORE-FAIL\n'+out, flush=True)
|
||||||
|
failures.append(label+' restore')
|
||||||
|
else:
|
||||||
|
print('RESTORED GREEN: '+', '.join(names), flush=True)
|
||||||
|
print('FAILED_CONTROLS='+repr(failures), flush=True)
|
||||||
|
return bool(failures)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.exit(main())
|
||||||
Reference in New Issue
Block a user