[Test] (IntegrationTest): take the shader-compiler and viewport quirks from the environment instead of internal symbols

Android links this module against the SHIPPING libMobileGL.so on purpose, so the
on-device run validates the real artifact - and that library is -fvisibility=hidden.
Six symbols the scenarios reached for were therefore undefined and the executable
could not be linked at all: MG_Config::Features, SetAsyncShaderCompileSuspended,
ShaderCompilePool::Get/GetThreadCount/SetMaxConcurrency and
AsyncShaderCompileEnabled. All six are gone rather than exported.

CompilerThreadScope now restores the pool with glMaxShaderCompilerThreadsKHR
(0xFFFFFFFF), which MaxShaderCompilerThreadsKHR_State defines as exactly the two
steps it used to perform by hand, and AsyncAndSyncProgramsRenderIdenticalFrames
picks its two modes with the same entry point - a zero count compiles inline, a
nonzero one lifts that - so the switching is now itself under test.

ExtensionStringMatchesTheConfiguration stops deriving its expectation from
AsyncShaderCompileEnabled(), the very function the backends gate the extension
string on: it was asserting the implementation against itself and would have passed
however wrong both halves were. The expectation is now MOBILEGL_ASYNC_SHADER_COMPILE
as the process inherited it, and the case skips where that is unset because the
built-in default is a value only the implementation knows.

Both-mode coverage in one ctest run is preserved by registration rather than by
in-process forcing, and is wider than before. New entries, each APPENDING to the
common/Vulkan environment so the EGL-vendor and ICD pinning is not lost - a ctest
ENVIRONMENT property replaces the job environment rather than adding to it:
DirectGLES./DirectVulkan.AsyncOn. run all of AsyncCompileScenario with
MOBILEGL_ASYNC_SHADER_COMPILE=1; .AsyncOff. run the extension case with =0, which
asserts the withdrawn side that nothing covered before;
.OptimisticShaderStatus. run the Iris-shaped case with
MOBILEGL_ASYNC_OPTIMISTIC_SHADER_STATUS=1 (its own entries because the quirk is not
neutral for the rest of the scenario); DirectGLES.NoViewportArrayEmulation. runs the
emulation control with MOBILEGL_FORCE_VIEWPORT_ARRAY_EMULATION=0. Run from a device
shell with nothing set, the ambient configuration runs and the rest skip cleanly.

1836 -> 1853 ctest entries, all green.
This commit is contained in:
2026-08-25 04:52:14 -04:00
parent b12ef4d717
commit 7f2ca68615
4 changed files with 321 additions and 79 deletions
@@ -21,12 +21,49 @@
#pragma once
#include <cctype>
#include <cstdlib>
#include <string>
#include <gtest/gtest.h>
#include "HeadlessGL.h"
namespace MGITest {
// How a MOBILEGL_* quirk variable reads in THIS process's environment.
//
// A scenario that needs a non-default configuration takes it from here and skips
// when the process it was launched into is not in that configuration, rather than
// writing MG_Config::Features itself. Two reasons, and the second one decides it:
//
// - the feature table is an internal symbol. On Android this module links against
// the SHIPPING libMobileGL.so - deliberately, so the on-device run validates the
// real artifact - and that library is built -fvisibility=hidden, so nothing
// internal is reachable from here at all.
// - a quirk poked in-process is already too late for everything latched at
// initialization: the compile pool and its threads, and the backend's advertised
// extension list, which is built once from the configuration in force at first
// use. The process-wide variable is the only spelling that covers the whole
// configuration instead of the half of it that is still mutable afterwards.
//
// The reading rule is MG_ConfigLoader's, character for character (ConfigLoader.cpp,
// QueryEnvQuirkOverride / IsTruthyValue): unset is Auto - device auto-detection or a
// built-in default, i.e. a value only the implementation knows - a truthy value is
// On, and anything else that IS set ("0", "false", "") is Off.
enum class AmbientQuirk { Auto, On, Off };
inline AmbientQuirk AmbientQuirkFromEnvironment(const char* name) {
const char* value = std::getenv(name);
if (value == nullptr) return AmbientQuirk::Auto;
std::string lowered(value);
for (char& c : lowered) {
c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
}
if (lowered.empty() || lowered == "0" || lowered == "false") return AmbientQuirk::Off;
return AmbientQuirk::On;
}
class ScenarioTest : public ::testing::Test {
protected:
void SetUp() override {