mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-07 19:58:32 +09:00
[Fix, Test] (MG_IntegrationTest): the harness is surfaceless by construction - never binds a window system, hardware demand split out of REQUIRE_GPU, pre-flight crashes keep their cores
This commit is contained in:
@@ -169,25 +169,24 @@ endif()
|
||||
option(MOBILEGL_ITEST_REQUIRE_GPU
|
||||
"Fail (rather than skip) the integration scenarios when the headless harness is unusable" OFF)
|
||||
|
||||
# DirectGLES asks the system EGL for a pbuffer config, and on Mesa the default
|
||||
# platform is not X11 unless it is said out loud (run_driver_bench.sh sets the
|
||||
# same variable). Wrong platform here is not a soft failure: eglCreatePbuffer
|
||||
# fails and every scenario skips.
|
||||
if (UNIX AND NOT APPLE AND NOT ANDROID)
|
||||
set(MOBILEGL_ITEST_EGL_PLATFORM "x11" CACHE STRING
|
||||
"EGL_PLATFORM for the integration tests (empty: leave the loader alone)")
|
||||
else()
|
||||
set(MOBILEGL_ITEST_EGL_PLATFORM "" CACHE STRING
|
||||
"EGL_PLATFORM for the integration tests (empty: leave the loader alone)")
|
||||
endif()
|
||||
# No EGL_PLATFORM knob here on purpose. The harness pins EGL_PLATFORM=surfaceless
|
||||
# itself before its first EGL call (HeadlessGL.cpp, EnsureHeadlessPlatform) so a
|
||||
# developer's machine and a CI runner take the SAME path whether or not a window
|
||||
# system happens to be running. This used to inject "x11", which is how the lane
|
||||
# came up green on a workstation with WSLg and died on a runner with no X server.
|
||||
#
|
||||
# A build-system knob would not just be redundant, it would be a trap: `set(...
|
||||
# CACHE ...)` does not rewrite an existing cache, so every build directory
|
||||
# configured before this change would keep injecting EGL_PLATFORM=x11 and go on
|
||||
# binding to a window system - silently, and only on the machines that have one.
|
||||
# Someone reproducing a platform-specific bug sets EGL_PLATFORM in their own
|
||||
# environment, which the harness still honours.
|
||||
|
||||
set(MGL_ITEST_COMMON_ENV "")
|
||||
if (MOBILEGL_ITEST_EGL_VENDOR)
|
||||
list(APPEND MGL_ITEST_COMMON_ENV "__EGL_VENDOR_LIBRARY_FILENAMES=${MOBILEGL_ITEST_EGL_VENDOR}")
|
||||
endif()
|
||||
if (MOBILEGL_ITEST_EGL_PLATFORM)
|
||||
list(APPEND MGL_ITEST_COMMON_ENV "EGL_PLATFORM=${MOBILEGL_ITEST_EGL_PLATFORM}")
|
||||
endif()
|
||||
unset(MOBILEGL_ITEST_EGL_PLATFORM CACHE) # see above: an old cache must not resurrect x11
|
||||
if (MOBILEGL_ITEST_REQUIRE_GPU)
|
||||
list(APPEND MGL_ITEST_COMMON_ENV "MOBILEGL_ITEST_REQUIRE_GPU=1")
|
||||
endif()
|
||||
|
||||
@@ -74,6 +74,40 @@ namespace MGITest {
|
||||
std::string renderer;
|
||||
};
|
||||
|
||||
// The harness is headless BY CONSTRUCTION, on every machine: it must never
|
||||
// reach a window system, not even where one happens to be running. This is
|
||||
// not a CI accommodation - it is what keeps a developer's run and a CI run
|
||||
// the same run. The lane was wired up green on a workstation and immediately
|
||||
// died on the runner precisely because the workstation had a DISPLAY (WSLg)
|
||||
// and took Mesa's x11 platform, while the runner has none; that divergence
|
||||
// is the bug, and pinning the platform here is the fix for it.
|
||||
//
|
||||
// Mesa selects its EGL platform from EGL_PLATFORM at loader time, so this
|
||||
// has to run before the first EGL call in the process (see EnsureHeadless
|
||||
// callers). surfaceless is the platform with no window-system dependency at
|
||||
// all; the surface this file then creates is still a pbuffer, which every
|
||||
// platform supports and which the amendment to this rule requires as the
|
||||
// fallback shape. DISPLAY/WAYLAND_DISPLAY are cleared as well so that a
|
||||
// driver that consults them directly cannot reintroduce the dependency
|
||||
// behind EGL's back. Desktop-only file: MG_IntegrationTest never builds
|
||||
// for Android, so no device path is affected.
|
||||
void EnsureHeadlessPlatform() {
|
||||
#if defined(__linux__) && !defined(__ANDROID__)
|
||||
static bool done = false;
|
||||
if (done) {
|
||||
return;
|
||||
}
|
||||
done = true;
|
||||
// An explicit EGL_PLATFORM from the operator still wins: pinning a
|
||||
// platform is exactly how someone reproduces a platform-specific bug.
|
||||
if (std::getenv("EGL_PLATFORM") == nullptr) {
|
||||
setenv("EGL_PLATFORM", "surfaceless", 1);
|
||||
}
|
||||
unsetenv("DISPLAY");
|
||||
unsetenv("WAYLAND_DISPLAY");
|
||||
#endif
|
||||
}
|
||||
|
||||
// THE bring-up, in one function so the pre-flight child and the parent run
|
||||
// literally the same sequence - a pre-flight that tests something narrower
|
||||
// than what the parent will do is exactly the kind of "predictive" check
|
||||
@@ -82,6 +116,9 @@ namespace MGITest {
|
||||
// Returns 0 on success, or the 1-based index of the step that failed, and
|
||||
// fills outReason either way.
|
||||
int RunEglBringUp(EglBringUp& out, std::string& outReason) {
|
||||
// Belt and braces: the pre-flight child and the parent both enter here,
|
||||
// and neither may be the first to touch EGL without this having run.
|
||||
EnsureHeadlessPlatform();
|
||||
EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
|
||||
if (display == EGL_NO_DISPLAY) {
|
||||
outReason = WithEglError("eglGetDisplay(EGL_DEFAULT_DISPLAY) returned EGL_NO_DISPLAY");
|
||||
@@ -199,11 +236,10 @@ namespace MGITest {
|
||||
}
|
||||
if (child == 0) {
|
||||
close(channel[0]);
|
||||
// The child is EXPECTED to die on a signal on an unusable
|
||||
// platform; that is the measurement. Do not let each such
|
||||
// measurement drop a core file next to the test binary.
|
||||
const rlimit noCore{0, 0};
|
||||
setrlimit(RLIMIT_CORE, &noCore);
|
||||
// No core suppression here, deliberately: when the child dies on a
|
||||
// signal, the core IS the diagnosis (an rlimit that used to sit here
|
||||
// made a CI-only crash undebuggable). Machines that do not want
|
||||
// cores control that with the usual ulimit/core_pattern knobs.
|
||||
std::fprintf(stderr, "[itest] pre-flight child: attempting a full EGL bring-up\n");
|
||||
EglBringUp local;
|
||||
std::string reason;
|
||||
@@ -284,9 +320,19 @@ namespace MGITest {
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
bool EnvFlag(const char* name) {
|
||||
const char* value = std::getenv(name);
|
||||
return value != nullptr && value[0] != '\0' && std::strcmp(value, "0") != 0;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
bool RequireGpu() {
|
||||
const char* value = std::getenv("MOBILEGL_ITEST_REQUIRE_GPU");
|
||||
return value != nullptr && value[0] != '\0' && std::strcmp(value, "0") != 0;
|
||||
return EnvFlag("MOBILEGL_ITEST_REQUIRE_GPU");
|
||||
}
|
||||
|
||||
bool RequireHardwareGpu() {
|
||||
return EnvFlag("MOBILEGL_ITEST_REQUIRE_HARDWARE_GPU");
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const Rgba8& c) {
|
||||
@@ -390,6 +436,10 @@ namespace MGITest {
|
||||
}
|
||||
|
||||
HeadlessGL::HeadlessGL() {
|
||||
// Before anything else in this process can reach EGL, and in particular
|
||||
// before the pre-flight forks - the child must measure the same platform
|
||||
// the parent will use.
|
||||
EnsureHeadlessPlatform();
|
||||
m_backendName = EnvOr("MOBILEGL_BACKEND_TYPE", "<unset>");
|
||||
m_usable = BringUp();
|
||||
}
|
||||
|
||||
@@ -41,6 +41,15 @@ namespace MGITest {
|
||||
// a job that ran everything.
|
||||
bool RequireGpu();
|
||||
|
||||
// True when MOBILEGL_ITEST_REQUIRE_HARDWARE_GPU is set: additionally asserts
|
||||
// that the context did NOT land on a software rasterizer. Deliberately a
|
||||
// SEPARATE switch from RequireGpu - a GPU-less CI runner is a supported and
|
||||
// intended configuration for these scenarios (they pin backend draw logic,
|
||||
// which llvmpipe/lavapipe execute faithfully), so CI wants the falsifiability
|
||||
// of REQUIRE_GPU without the hardware demand. Use this one only where a vendor
|
||||
// pin silently degrading to software would invalidate the measurement.
|
||||
bool RequireHardwareGpu();
|
||||
|
||||
struct Rgba8 {
|
||||
std::uint8_t r = 0, g = 0, b = 0, a = 0;
|
||||
|
||||
|
||||
@@ -45,12 +45,18 @@ namespace MGITest {
|
||||
}
|
||||
GTEST_SKIP() << "no usable GPU/display/ICD for backend " << gl.BackendName() << ": " << gl.SkipReason();
|
||||
}
|
||||
if (RequireGpu() && LooksLikeSoftwareRasterizer(gl.RendererString())) {
|
||||
// "Ran on llvmpipe" must not be able to pass as "ran on the GPU":
|
||||
// a misconfigured vendor pin silently lands on the software
|
||||
// rasterizer, and REQUIRE_GPU exists precisely to make that loud.
|
||||
FAIL() << "MOBILEGL_ITEST_REQUIRE_GPU is set but the context landed on a software rasterizer: "
|
||||
<< gl.RendererString();
|
||||
if (RequireHardwareGpu() && LooksLikeSoftwareRasterizer(gl.RendererString())) {
|
||||
// Only when hardware was asked for BY NAME. REQUIRE_GPU means "an
|
||||
// unusable harness is a failure, not a silent skip" - it is the
|
||||
// falsifiability switch, and CI is exactly where it belongs. But CI
|
||||
// runners have no GPU, so folding "must not be llvmpipe" into the
|
||||
// same switch made the CI lane unpassable by construction: the
|
||||
// scenarios pin backend draw logic, which a software rasterizer
|
||||
// executes just as faithfully. Landing on llvmpipe/lavapipe there is
|
||||
// the intended configuration, not a misconfiguration. A vendor pin
|
||||
// that must not silently degrade sets REQUIRE_HARDWARE_GPU.
|
||||
FAIL() << "MOBILEGL_ITEST_REQUIRE_HARDWARE_GPU is set but the context landed on a software "
|
||||
<< "rasterizer: " << gl.RendererString();
|
||||
}
|
||||
// A scenario starts from a clean slate but shares the context (and so
|
||||
// the renderer's memos) with every other scenario in this process -
|
||||
|
||||
@@ -26,8 +26,14 @@ namespace {
|
||||
const MGITest::HeadlessGL& gl = MGITest::HeadlessGL::Get();
|
||||
std::fprintf(stderr, "MobileGL integration scenarios: backend=%s\n", gl.BackendName().c_str());
|
||||
if (gl.Usable()) {
|
||||
std::fprintf(stderr, " renderer: %s\n surface: %dx%d pbuffer (headless)\n",
|
||||
gl.RendererString().c_str(), gl.Width(), gl.Height());
|
||||
// EGL_PLATFORM is echoed because it is the invariant this harness
|
||||
// rests on: the run is headless on every machine, so a run that
|
||||
// silently bound to a workstation's window system is a different
|
||||
// run from CI's and must be visible as one in the log.
|
||||
const char* eglPlatform = std::getenv("EGL_PLATFORM");
|
||||
std::fprintf(stderr, " renderer: %s\n surface: %dx%d pbuffer (headless, EGL_PLATFORM=%s)\n",
|
||||
gl.RendererString().c_str(), gl.Width(), gl.Height(),
|
||||
eglPlatform != nullptr ? eglPlatform : "<unset>");
|
||||
} else if (MGITest::RequireGpu()) {
|
||||
std::fprintf(stderr,
|
||||
" FAILING every scenario (MOBILEGL_ITEST_REQUIRE_GPU is set): %s\n",
|
||||
|
||||
Reference in New Issue
Block a user