diff --git a/packages/app/e2e/mobile/workspace-memory/README.md b/packages/app/e2e/mobile/workspace-memory/README.md new file mode 100644 index 0000000000..32b272c8c1 --- /dev/null +++ b/packages/app/e2e/mobile/workspace-memory/README.md @@ -0,0 +1,21 @@ +# Android workspace memory diagnostic + +Supplemental reproduction for [#4408](https://github.com/getpaseo/paseo/issues/4408), using the production 0.11.2 APK on an Android 16 emulator. This tests an adapted failure: opening the sidebar with 800 projects, each containing one workspace, produced a `LOW_MEMORY` exit after 66–90 seconds. It does not reproduce the reported 18-second cold-start ANR or establish its cause. + +Before running: + +- Use a dedicated emulator with the production APK installed and notifications permission settled. Bind the agent-device session to that emulator before running. +- Pair the app with an isolated daemon containing 800 active workspaces, each in its own project. Use Project grouping and leave projects expanded. The observed fixture used existing directories inside a Git checkout, with workspace titles permuted by `(index * 317) % 800`, no agents, and no terminals. +- Record the daemon revision, emulator RAM and Java heap settings, and the project/workspace distribution. Device memory pressure controls whether Android kills the app. + +Run from the repository root with the agent-device session, Android serial, and an artifact directory: + +```sh +node packages/app/e2e/mobile/workspace-memory/android.mjs workspace-memory emulator-5556 .dev/workspace-memory +``` + +The test cold-starts the paired app, opens its sidebar, samples memory and the process ID every five seconds for two minutes, and verifies that closing the sidebar responds. A process exit or restart fails the assertion. Memory, exit records, and logs stay in the supplied artifact directory. + +Control: repeat with 800 workspaces grouped into one project. That distribution survived the two-minute automated check and responded to closing the sidebar, with total PSS near 300 MB. The project-per-workspace fixture reached about 2.5 GB PSS before Android recorded `LOW_MEMORY`; its Java heap rose and fell rather than showing the reporter's full-heap ANR. Neither result proves that shared collators would fix the report. + +This diagnostic is deliberately outside CI and contains no production change. diff --git a/packages/app/e2e/mobile/workspace-memory/android.mjs b/packages/app/e2e/mobile/workspace-memory/android.mjs new file mode 100644 index 0000000000..2c5449259a --- /dev/null +++ b/packages/app/e2e/mobile/workspace-memory/android.mjs @@ -0,0 +1,65 @@ +// Requires a paired production app and the workspace fixture described in README.md. +import assert from "node:assert/strict"; +import { execFileSync } from "node:child_process"; +import { mkdirSync, writeFileSync } from "node:fs"; +import { join } from "node:path"; +import { setTimeout as delay } from "node:timers/promises"; + +const [, , session, serial, artifacts] = process.argv; +assert(session && serial && artifacts, "Pass session, Android serial, and artifact directory"); +mkdirSync(artifacts, { recursive: true }); + +function ad(...args) { + return execFileSync("agent-device", [...args, "--session", session], { + encoding: "utf8", + timeout: 60_000, + }); +} +function adb(...args) { + return execFileSync("adb", ["-s", serial, ...args], { + encoding: "utf8", + timeout: 15_000, + }); +} +function pid() { + try { + return adb("shell", "pidof", "sh.paseo").trim(); + } catch { + return ""; + } +} + +ad("open", "sh.paseo", "--relaunch", "--platform", "android"); +ad("wait", "text", "Open menu", "15000"); +assert.equal( + JSON.parse(ad("perf", "memory", "sample", "--json")).data.deviceId, + serial, + "Session and adb serial must address the same device", +); +const initialPid = pid(); +assert(initialPid, "App must have a process before opening the sidebar"); +ad("press", 'label="Open menu"', "--settle"); +const startedAt = Date.now(); +const samples = []; +try { + while (Date.now() - startedAt < 120_000) { + const currentPid = pid(); + samples.push({ + elapsedMs: Date.now() - startedAt, + pid: currentPid, + meminfo: currentPid ? adb("shell", "dumpsys", "meminfo", "sh.paseo") : "", + }); + writeFileSync(join(artifacts, "samples.json"), JSON.stringify(samples, null, 2)); + assert.equal(currentPid, initialPid, "Opening the workspace sidebar must keep the app alive"); + await delay(5_000); + } + ad("press", 'label="Close sidebar"', "--settle"); + ad("wait", "text", "Open menu", "10000"); + console.log("PASS: app survived two minutes with the sidebar open and responded to Close"); +} finally { + writeFileSync( + join(artifacts, "exit-info.txt"), + adb("shell", "dumpsys", "activity", "exit-info", "sh.paseo"), + ); + writeFileSync(join(artifacts, "logcat.txt"), adb("logcat", "-d", "-t", "4000")); +}