Take the picker test off the API 37 gating leg, and give the SystemUI disable root
The gating `E2E API 37` leg failed three of the last ten status_check runs. Four runs were read logcat-first -- 34006456986, 34001744574, 34001377499 and the green 34002313300 -- and each carries exactly two `hasReadColorBufferDma` aborts before the suite (surfaceflinger, during boot and the SystemUI disable) and exactly one during it: `system_server`, thread `TaskSnapshotPer`, always inside `pickingAFileThroughTheSystemPickerFillsInTheFileCard`'s window. Nothing else in the gating set reaches the mapper. So that test kills the framework on this image whether it passes or not, and whether the leg goes red is luck: 34001377499 passed it and lost the leg anyway with `failed: 0`, 34002313300 passed it 0.6 s after the abort and went green. That is #108. The test now carries `@FailsOnEmulatorApi37` and the baseline goes 3 -> 4; the marker's own wording widens from "does not pass on this image" to "cannot be run on this image", because this carrier passes about half the time. `docs/api-37-emulator-crash.md` had counted those aborts on 2026-08-24, put them in its table, and then read the pass/fail column alone. The correction is recorded beside the original rather than replacing it. Probed on the same image and recorded there too: there is no shell knob for task snapshots -- not in `getprop`, `settings`, `device_config` or `cmd window` -- so the marker is the available answer rather than the lazy one. Two separate defects came out of the same logcats. `disable_region_sampling` has never restarted the framework on CI. `adb shell stop` and `start` are root-only and every API 37 leg has printed `Must be root` for both, so SystemUI stayed up for the whole run -- visible directly as `WindowManagerShell ... app=com.android.systemui` minutes after "final state: SystemUI disabled". Both waits also printed their own exhaustion as an elapsed time, so "system_server down after ~40 s" is what a stop that did nothing looks like. Measured on the local android-37.0 AVD, same fingerprint as CI: `adb root` makes `stop` return 0 with `pidof system_server` empty. Root is dropped again before Gradle runs, and both waits now say whether they observed anything. And when the picker test does fail, the abort is the coda rather than the cause: `InputDispatcher: No new touched window at (539.0, 525.0)` is in both reds and absent from the green, so the tap on the root is discarded, the picker is never navigated, and all four back presses land on an activity WindowManager says has not added a window yet. `forceStopThePicker` goes around input entirely so `pickTheFixture`'s whole-picker retry -- which exists for exactly this -- becomes reachable. That one is a fix on every API level, not just 37. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
package org.libremediaconverter
|
||||
|
||||
/**
|
||||
* Marks an instrumented test that does not pass on the `android-37.x` **emulator** system images.
|
||||
* Marks an instrumented test that cannot be run on the `android-37.x` **emulator** system images.
|
||||
*
|
||||
* This is a marker, not a skip. Nothing reads it except CI, and CI reads it twice — once with
|
||||
* `notAnnotation` to build the gating API 37 leg, and once with `annotation` to build the advisory
|
||||
@@ -9,6 +9,15 @@ package org.libremediaconverter
|
||||
* That is the whole reason there is one annotation rather than a pair of test lists: two lists
|
||||
* drift, and the drift is silent in both directions (a test that runs nowhere reads as green).
|
||||
*
|
||||
* **"Cannot be run" covers two things, and it said only the first until 2026-09-05.** Three of the
|
||||
* four carriers simply fail: two Media3 transcodes die in the image's own `c2.goldfish.h264
|
||||
* .decoder`, and the SAF rotation test takes the framework down with it. The fourth —
|
||||
* `SafPickerRoundTripTest.pickingAFileThroughTheSystemPickerFillsInTheFileCard` — **passes about
|
||||
* half the time and aborts `system_server` every time**, which is worse for a gating leg than an
|
||||
* honest failure: it fails the leg from the teardown, with no failing test to point at (#108).
|
||||
* The wording was widened rather than the test excused; that test's own KDoc has the four-run
|
||||
* measurement.
|
||||
*
|
||||
* It says only what has been measured: **on the emulator, at API 37.** The same tests pass on a
|
||||
* physical Pixel 10 Pro XL at API 37 and at API 33–36 on the same runner under the same renderer,
|
||||
* so this must never be read as "this test is allowed to fail at API 37" — only as "the API 37
|
||||
@@ -17,7 +26,7 @@ package org.libremediaconverter
|
||||
*
|
||||
* Removing it is the goal, and the trigger is written down: a new API 37.x system image, or an
|
||||
* ATD image for 37. Delete the annotation from the tests, and the advisory job goes empty and
|
||||
* the gating one grows by two.
|
||||
* the gating one grows by four.
|
||||
*
|
||||
* **How many tests carry it is committed below**, as [FAILS_ON_EMULATOR_API37_BASELINE], and the
|
||||
* advisory job checks the run against it. Adding or removing a marker means changing that number
|
||||
@@ -37,11 +46,19 @@ annotation class FailsOnEmulatorApi37
|
||||
* keep printing with nothing to compare to, so it announces that it could not read the baseline
|
||||
* rather than falling quiet. If you see that notice, this line is what it means.
|
||||
*
|
||||
* **One number, both checks, and that is what the marker means.** A test carrying it cannot pass
|
||||
* **One number, both checks, and that is what the marker means.** A test carrying it cannot be run
|
||||
* on this image, so the count is simultaneously how many the advisory leg runs and how many fail.
|
||||
* A *smaller* failure count is the interesting direction: it means one of them now passes, which
|
||||
* is the trigger the KDoc above names for deleting the annotation.
|
||||
*
|
||||
* **The fourth carrier is the one to read that sentence carefully for.**
|
||||
* `pickingAFileThroughTheSystemPickerFillsInTheFileCard` was marked on 2026-09-05 for aborting
|
||||
* `system_server` rather than for failing (#108), and on the gating leg it passed two runs of
|
||||
* four. On the advisory leg it runs after the rotation test has already taken the framework down,
|
||||
* which is why the count still holds there — measured, not assumed, and the measurement is the
|
||||
* reason this line did not have to become two numbers. If it ever starts reporting three failures
|
||||
* out of four, read that as this test having got lucky rather than as an image that improved.
|
||||
*
|
||||
* So: adding or removing a [FailsOnEmulatorApi37] means changing this number, in this file, in
|
||||
* the same diff. The report says so on the run itself if you forget — it prints the tree's own
|
||||
* `grep` count beside this one.
|
||||
@@ -52,4 +69,4 @@ annotation class FailsOnEmulatorApi37
|
||||
* `INSTRUMENTATION_ABORTED`, so the count is a number taken from a partial run. The report
|
||||
* records the truncation next to the counts for that reason.
|
||||
*/
|
||||
const val FAILS_ON_EMULATOR_API37_BASELINE = 3
|
||||
const val FAILS_ON_EMULATOR_API37_BASELINE = 4
|
||||
|
||||
@@ -296,7 +296,35 @@ class SafPickerRoundTripTest {
|
||||
device.waitForIdle()
|
||||
}
|
||||
|
||||
/**
|
||||
* **Marked for API 37 because of what it does to the image, not because it fails there.**
|
||||
*
|
||||
* This is the one place the marker's KDoc phrase "cannot pass on this image" does not fit, and
|
||||
* the distinction is worth keeping rather than smoothing over. Across the four gating API 37
|
||||
* runs whose logcats were read on 2026-09-05 — 34006456986, 34001744574, 34001377499 and the
|
||||
* green 34002313300 — the leg carries exactly two `hasReadColorBufferDma` aborts before the
|
||||
* suite starts (both `surfaceflinger`, during boot and the SystemUI disable) and then exactly
|
||||
* **one** during it. Every time, that one is `system_server` on the `TaskSnapshotPer` thread,
|
||||
* and every time it lands inside this test's window. No other test in the gating set reaches
|
||||
* the mapper at all.
|
||||
*
|
||||
* So this test kills the framework on that image whether it passes or not, and whether the leg
|
||||
* goes red is luck: 34001377499 passed it and lost the leg anyway (`failed: 0`, teardown
|
||||
* broken), 34002313300 passed it 0.6 s after the abort and went green. That is #108, and it is
|
||||
* why the leg was failing on unrelated PRs.
|
||||
*
|
||||
* `docs/api-37-emulator-crash.md` measured this test on 2026-08-24, recorded "passes, 4 aborts
|
||||
* in the window", and concluded that a rotation reaches the mapper where starting DocumentsUI
|
||||
* does not. The aborts were seen; what was not drawn out is that they are this test's own and
|
||||
* are not intermittent.
|
||||
*
|
||||
* The marker is what routes it off the gating leg and into the advisory job beside its
|
||||
* rotation sibling. **It is not a statement about the picker**: the same test passes on API
|
||||
* 33–36 on the same runner and on the Pixel 10 Pro XL, which is where API 37's answer comes
|
||||
* from.
|
||||
*/
|
||||
@Test
|
||||
@FailsOnEmulatorApi37
|
||||
fun pickingAFileThroughTheSystemPickerFillsInTheFileCard() {
|
||||
pickTheFixture()
|
||||
|
||||
@@ -602,6 +630,9 @@ class SafPickerRoundTripTest {
|
||||
* It is also why this counts backs rather than pressing a fixed number of them. One back is
|
||||
* enough from Recent and two are needed from inside the root, but a third from Recent would
|
||||
* finish `MainActivity` and take the rest of the test with it.
|
||||
*
|
||||
* **[forceStopThePicker] is the escalation after the presses, and it exists because a back
|
||||
* press is not always deliverable.** See its own KDoc for the measurement.
|
||||
*/
|
||||
private fun dismissThePicker() {
|
||||
repeat(BACK_PRESSES) {
|
||||
@@ -616,15 +647,50 @@ class SafPickerRoundTripTest {
|
||||
// The check after the last press, and not a spare one: `repeat` presses on its final
|
||||
// iteration too, so without this a dismissal that worked on the last press would still be
|
||||
// reported as a failure to close.
|
||||
if (awaitAppFocus()) return
|
||||
forceStopThePicker()
|
||||
if (!awaitAppFocus()) {
|
||||
throw AssertionError(
|
||||
"the system picker would not close: after $BACK_PRESSES back presses the app " +
|
||||
"still does not have the window focus, and ${device.currentPackageName} is " +
|
||||
"in front. What could be seen: " + describeWindows(),
|
||||
"the system picker would not close: after $BACK_PRESSES back presses and a " +
|
||||
"force-stop of $DOCUMENTS_UI_PACKAGE the app still does not have the window " +
|
||||
"focus, and ${device.currentPackageName} is in front. What could be seen: " +
|
||||
describeWindows(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Kills the picker's process, for when no back press can reach it.
|
||||
*
|
||||
* **The failure this exists for cannot be answered with input, and that is the whole point.**
|
||||
* Measured on the gating API 37 legs of runs 34006456986 and 34001744574, which fail this way
|
||||
* and whose logcats say the same thing in the same order. `UiObject2.click()` on the fixture's
|
||||
* root is injected at the node's centre and the framework discards it —
|
||||
* `InputDispatcher: No new touched window at (539.0, 525.0) in display 0` — because
|
||||
* `PickActivity` has published accessibility nodes but has no touchable window there yet.
|
||||
* `click()` cannot see that and returns normally, so the walk goes on to wait out
|
||||
* [PICKER_TIMEOUT_MS] for a fixture that was never navigated to. By the time this function's
|
||||
* caller starts pressing back, WindowManager is still saying
|
||||
* `no window has focus but ...PickActivity may eventually add a window when it finishes
|
||||
* starting up` — and goes on saying it for another 63 s. Every one of the four presses is
|
||||
* dropped, and DocumentsUI ANRs on `Input dispatching timed out`.
|
||||
*
|
||||
* So the picker is in front, unreachable by key or by touch, and [pickTheFixture]'s whole
|
||||
* point — that a second `PickActivity` rebuilds every window and list in it — is unreachable
|
||||
* with it. `am force-stop` goes around input entirely: `UiAutomation` runs shell commands as
|
||||
* uid 2000, which holds `FORCE_STOP_PACKAGES`, so the picker's process is killed, its
|
||||
* activity leaves the task it was launched into, and `MainActivity` — the activity below it in
|
||||
* that same task — is resumed with the focus.
|
||||
*
|
||||
* **Only on the failure path**, after every back press has been spent, so a picker that closes
|
||||
* the ordinary way never reaches this and is not altered by it. If the framework itself is
|
||||
* gone, this cannot help either, and the caller still reports what it could see.
|
||||
*/
|
||||
private fun forceStopThePicker() {
|
||||
device.executeShellCommand("am force-stop $DOCUMENTS_UI_PACKAGE")
|
||||
device.waitForIdle()
|
||||
}
|
||||
|
||||
/** True once [MainActivity] has the window focus, false if it does not take it in time. */
|
||||
private fun awaitAppFocus(): Boolean = try {
|
||||
composeRule.waitUntil("the app has the window focus back", FOCUS_TIMEOUT_MS) {
|
||||
|
||||
Reference in New Issue
Block a user