mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[tools, android-plugin, CI] (P7): the device matrix runner selects DirectVulkan, repeats are archived, and the spawn env parse is entry-aware
wave 0 item 5. Three instruments P7's device window needs, and the CI steps that
can make each of them red.
run_tcp_matrix.py --backend DirectVulkan end to end. --backend already existed and
already filtered by each case's ci_backends; what did not exist is a per-backend
GOLDEN. The two backends shared `golden` plus an optional `alternate_golden`, and
alternate_golden is an OR across backends, not a per-backend key - so a
DirectVulkan run that matched the DirectGLES picture passed, which is the "went
green on the wrong arm" shape the exit gate exists to catch. trace_cases.json
gains `backend_overrides.<backend>` ({golden, alternate_golden, ssim_threshold}
only: restating target_call or a crop would stop the two backends retracing the
same frame through the same window). A per-backend golden REPLACES the shared
pair, matching another backend's declared image is refused BY NAME, and a --case
named explicitly that does not run a requested backend is an error instead of one
plan fewer and no message. The CMake emitter and the APK matrix refuse a case that
declares the key rather than silently emitting one golden for both arms.
DirectGLES is unchanged and that is asserted, not asserted-in-prose: a case with no
override resolves to itself, and the key is dropped from the resolved case even for
the backend that overrides nothing - so giving DirectVulkan its own golden does not
move the DirectGLES identity fingerprint and does not invalidate its checkpoints.
Each results.json row now records the golden, alternate golden and threshold it was
scored against.
run_android_retrace_local.py gets --matrix (the CI split subset, 39 cases, each
running only its own ci_backends - not --all, which adds the rd12 workload ID-P7-4
excludes), --repeat N, and --archive-dir. The archive is the point: the result root
is keyed by case and backend and by NOTHING else, so repeat 2 overwrites repeat 1
and the only artefact that survives a three-repeat run is the last one, which makes
"were the three repeats the same picture?" unanswerable after the fact - in the one
place where re-running to find out is expensive. It also refuses --benchmark with a
non-monolith --transport, which has always produced a monolith run under a spawn
label (run_benchmark_case passes args.env alone; p6/README.md records the pitfall).
compare_actuals.py computes the pair result.json cannot: SSIM between two actual
PNGs, plus a case x repeat x ssim-vs-golden x ssim-vs-first-repeat table over an
archive. It is the retrace gate's OWN SSIM - a transcription of ComputeChannelSsim
/ ComputeRgbSsim (trace_replay_core.cpp:630/:671), the only SSIM in the tree - and
the crop comes from each repeat's own result.json, so the two columns describe the
same rectangle. PNG decode is stdlib zlib, because an unavailable Pillow in the
device window would turn "the repeats diverged" into "the tool did not run".
TraceReplayActivity: resolveSpawnServerPath's two decisions were String.contains()
on the whole joined K=V;K=V list and neither was entry-aware. The one that bit:
`--env MOBILEGL_IPC_SERVER_PATH` with no '=' is the documented UNSET spelling
(trace_env_overrides_test.cpp:92) and the only way to ask for "spawn with no image"
and watch LaunchServer refuse by name - it did not match "MOBILEGL_IPC_SERVER_PATH="
so a resolved path was appended AFTER it and, because ApplyEnvOverrides applies in
order with setenv(...,1), the deliberate unset was silently undone. That is exactly
the "silently replacing it would make that knob untestable" the method's own comment
forbids. Two more: the key inside another entry's VALUE flipped both decisions the
wrong way, and the transport match was case-sensitive while ConfigLoader lower-cases
the value before matching (ConfigLoader.cpp:322). The parse moves to
SpawnServerPath, a class with no android.* import, so test_android_lifecycle.py
compiles and runs it with plain javac - the parse had no host-side coverage at all,
and its on-device failure mode is "the knob had no effect".
Only spawn gets a path, deliberately: it is the only transport that execs an image.
Red-once, all four: a stub case whose result names the DirectGLES golden fails a
--backend DirectVulkan run with "matched a golden another backend declares"; a
synthetic PNG pair with a closed-form SSIM (identical = 1.0 exactly, constant
black vs white = C1/(255^2+C1), inverted halves = (C2-2v)/(2v+C2)); the pre-fix
resolveSpawnServerPath body fails eight of SpawnServerPathTest's checks; and the
DirectVulkan TCP proof battery is the DirectGLES one under the other prefix,
because an empty prefix selection raises nothing and reads as a clean pass.
CI: test_run_tcp_matrix.py and test_compare_actuals.py join the R-16 step. The
matrix driver's stub regressions had NO CI step - they were run by hand and
recorded in a note - and they guard the tool that decides whether a device golden
run passed.
Counts in WSL: test_run_tcp_matrix 12 -> 18, tcp_lane_tools 4 -> 6,
test_compare_actuals 12 new, test_android_lifecycle green with the new Java unit.
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
package top.mobilegl.plugin.trace;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* Host-side checks for the split arms' MOBILEGL_IPC_SERVER_PATH resolution.
|
||||
*
|
||||
* <p>The only lane that used to run this parse end to end was an on-device spawn retrace, which
|
||||
* is slow, indirect, and reports a parse bug as "the knob had no effect" - so a defect in it
|
||||
* survived from P6 to P7. Run by tools/trace_replay/test_android_lifecycle.py with plain javac,
|
||||
* beside the TraceReplaySession lifecycle checks.
|
||||
*
|
||||
* <p>Deliberately assert-free, for the same reason trace_env_overrides_test.cpp is: -ea is not
|
||||
* on by default and a check compiled away is a green run that checked nothing.
|
||||
*/
|
||||
public final class SpawnServerPathTest {
|
||||
private static int failures = 0;
|
||||
|
||||
private static final String DIR = "/data/app/~~hash==/top.mobilegl.plugin.trace-hash==/lib/arm64";
|
||||
private static final String RESOLVED =
|
||||
"MOBILEGL_IPC_SERVER_PATH=" + new File(DIR, "libMobileGLServer.so").getAbsolutePath();
|
||||
|
||||
private static void expect(String what, String actual, String expected) {
|
||||
if (expected.equals(actual)) {
|
||||
return;
|
||||
}
|
||||
++failures;
|
||||
System.err.println(what + "\n got: [" + actual + "]\n expected: [" + expected + "]");
|
||||
}
|
||||
|
||||
private static void expectTransport(String entries, String expected) {
|
||||
String actual = SpawnServerPath.effectiveTransport(entries);
|
||||
if (expected == null ? actual == null : expected.equals(actual)) {
|
||||
return;
|
||||
}
|
||||
++failures;
|
||||
System.err.println("effectiveTransport(\"" + entries + "\") gave [" + actual
|
||||
+ "], expected [" + expected + "]");
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
// ---- the shapes the lanes really pass -------------------------------------------------
|
||||
// .github/workflows/apk.yml's spawn acceptance lane, verbatim.
|
||||
String spawnLane = "MOBILEGL_TRANSPORT=spawn;MOBILEGL_IPC_ROLE_SPLIT_STATE=1;"
|
||||
+ "MOBILEGL_IPC_STRICT_ERRORS=1;MOBILEGL_IPC_RUN_AHEAD=1";
|
||||
expect("the spawn acceptance lane still gets its server path appended",
|
||||
SpawnServerPath.resolve(spawnLane, DIR), spawnLane + ";" + RESOLVED);
|
||||
// The inproc lane runs the server role on a thread HERE; a server path would be an unused
|
||||
// variable that made the two arms' environments differ for no reason.
|
||||
String inprocLane = "MOBILEGL_TRANSPORT=inproc;MOBILEGL_IPC_RUN_AHEAD=1";
|
||||
expect("inproc gets no server path", SpawnServerPath.resolve(inprocLane, DIR), inprocLane);
|
||||
expect("monolith (an empty list) gets no server path",
|
||||
SpawnServerPath.resolve("", DIR), "");
|
||||
expect("spawn alone still gets one",
|
||||
SpawnServerPath.resolve("MOBILEGL_TRANSPORT=spawn", DIR),
|
||||
"MOBILEGL_TRANSPORT=spawn;" + RESOLVED);
|
||||
expect("no nativeLibraryDir means nothing to resolve",
|
||||
SpawnServerPath.resolve(spawnLane, ""), spawnLane);
|
||||
|
||||
// ---- an explicit value wins, in BOTH of its spellings ----------------------------------
|
||||
String explicit = "MOBILEGL_TRANSPORT=spawn;MOBILEGL_IPC_SERVER_PATH=/data/local/tmp/mine.so";
|
||||
expect("an explicit path is left alone", SpawnServerPath.resolve(explicit, DIR), explicit);
|
||||
// THE DEFECT THIS FILE EXISTS FOR. `KEY` with no '=' is the documented UNSET
|
||||
// (trace_env_overrides_test.cpp:92) and the only way to ask for "spawn with no image" and
|
||||
// see LaunchServer refuse by name. contains("MOBILEGL_IPC_SERVER_PATH=") did not match
|
||||
// it, so a path was appended after the unset and - last entry wins - undid it.
|
||||
String unset = "MOBILEGL_TRANSPORT=spawn;MOBILEGL_IPC_SERVER_PATH";
|
||||
expect("the unset spelling is honoured, not overwritten",
|
||||
SpawnServerPath.resolve(unset, DIR), unset);
|
||||
String emptied = "MOBILEGL_TRANSPORT=spawn;MOBILEGL_IPC_SERVER_PATH=";
|
||||
expect("an empty value is a value, and is left alone",
|
||||
SpawnServerPath.resolve(emptied, DIR), emptied);
|
||||
|
||||
// ---- entry boundaries ------------------------------------------------------------------
|
||||
// The key inside ANOTHER entry's value said "the caller set it" and suppressed the
|
||||
// injection, so the spawn arm died on an unresolvable image for a log path's sake.
|
||||
String insideValue = "MOBILEGL_TRANSPORT=spawn;"
|
||||
+ "MOBILEGL_LOG_FILE_PATH=/sdcard/MOBILEGL_IPC_SERVER_PATH=x.log";
|
||||
expect("a key inside another entry's value does not suppress the injection",
|
||||
SpawnServerPath.resolve(insideValue, DIR), insideValue + ";" + RESOLVED);
|
||||
// And the mirror image: nothing asked for spawn here.
|
||||
String transportInsideValue =
|
||||
"MOBILEGL_TRACE_NOTE=ran MOBILEGL_TRANSPORT=spawn yesterday";
|
||||
expect("a transport named inside another entry's value does not request spawn",
|
||||
SpawnServerPath.resolve(transportInsideValue, DIR), transportInsideValue);
|
||||
expect("a longer key that merely starts with the transport key is a different key",
|
||||
SpawnServerPath.resolve("MOBILEGL_TRANSPORT_NOTE=spawn", DIR),
|
||||
"MOBILEGL_TRANSPORT_NOTE=spawn");
|
||||
|
||||
// ---- the library's own parsing rules ---------------------------------------------------
|
||||
expectTransport("", null);
|
||||
expectTransport("MOBILEGL_TRANSPORT=spawn", "spawn");
|
||||
// ConfigLoader.cpp:322 lower-cases the value before matching it, so this IS a spawn run
|
||||
// and it used to get no server path at all.
|
||||
expectTransport("MOBILEGL_TRANSPORT=Spawn", "spawn");
|
||||
expect("a capitalised value is still a spawn run",
|
||||
SpawnServerPath.resolve("MOBILEGL_TRANSPORT=Spawn", DIR),
|
||||
"MOBILEGL_TRANSPORT=Spawn;" + RESOLVED);
|
||||
// ApplyEnvOverrides applies entries in order with setenv(..., 1): the last one wins.
|
||||
expectTransport("MOBILEGL_TRANSPORT=inproc;MOBILEGL_TRANSPORT=spawn", "spawn");
|
||||
expectTransport("MOBILEGL_TRANSPORT=spawn;MOBILEGL_TRANSPORT=inproc", "inproc");
|
||||
expect("a later inproc really is the arm, so no server path is added",
|
||||
SpawnServerPath.resolve("MOBILEGL_TRANSPORT=spawn;MOBILEGL_TRANSPORT=inproc", DIR),
|
||||
"MOBILEGL_TRANSPORT=spawn;MOBILEGL_TRANSPORT=inproc");
|
||||
// A bare key is an unset, so the library falls back to its monolith default.
|
||||
expectTransport("MOBILEGL_TRANSPORT=spawn;MOBILEGL_TRANSPORT", null);
|
||||
// Empty entries are dropped and an empty key never reaches setenv.
|
||||
expectTransport(";;MOBILEGL_TRANSPORT=spawn;;", "spawn");
|
||||
expectTransport("=spawn", null);
|
||||
// Only the FIRST '=' separates, so a value may contain one.
|
||||
expectTransport("MOBILEGL_TRANSPORT=spawn=extra", "spawn=extra");
|
||||
|
||||
// LAST, so that running this file against the pre-fix implementation reports the parsing
|
||||
// defects above rather than dying on this one line first.
|
||||
expect("a null list is not an NPE", SpawnServerPath.resolve(null, DIR), "");
|
||||
|
||||
if (failures != 0) {
|
||||
System.err.println("SpawnServerPath: " + failures + " check(s) failed");
|
||||
System.exit(1);
|
||||
}
|
||||
System.out.println("SpawnServerPath: all checks passed");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user