Files
reverse-engineering-anything/src/adb/AdbShellOutput.test.ts
T
morluto 667ab5ae08 fix(adb): own numeric declaration validation and diagnostics
Parse declarations before selecting values, admit safe integers once, and retain bounded raw numeric issues. Preserve display and package selection policies while distinguishing absent and malformed values.

Keep completed display facts in typed physical-size failures, share the existing diagnostic sample limit, and verify real CLI/MCP contracts with a strict fake ADB.
2026-10-24 06:34:33 +08:00

546 lines
16 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
parseAmStartOutput,
parsePackageChangeOutput,
parseDumpsysPackageOutput,
parseWmDensityOutput,
parseWindowFocusOutput,
} from "./AdbShellOutput.js";
const packageDump = (installerLine: string | undefined): string =>
[
"Packages:",
" Package [com.example] (abcdef):",
" versionName=1.2.3",
...(installerLine === undefined ? [] : [` ${installerLine}`]),
" pkgFlags=[ HAS_CODE ]",
"",
].join("\n");
describe("parseDumpsysPackageOutput", () => {
it.each(["userId", "appId"])(
"retains the printed %s package identifier",
(key) => {
expect(
parseDumpsysPackageOutput(
`Packages:\n Package [com.example] (abcdef):\n ${key}=10234\n`,
).userId,
).toBe(10234);
},
);
it.each(["", " My Build ", " ", "null"])(
"preserves the printed version name %j",
(versionName) => {
const result = parseDumpsysPackageOutput(
` pkg=Package{abcdef com.example}\n versionName=${versionName}\n`,
);
expect(result.versionName).toBe(versionName);
},
);
it("keeps a reported installer package name", () => {
expect(
parseDumpsysPackageOutput(
packageDump("installerPackageName=com.android.vending"),
).installerPackageName,
).toBe("com.android.vending");
});
// Package Manager prints an absent String field as the text "null"; a
// missing or empty declaration means the same absence (morluto/rea#3943).
it.each([
["the literal null sentinel", "installerPackageName=null"],
["an empty value", "installerPackageName="],
["a missing line", undefined],
])("reports an absent installer as null for %s", (_case, line) => {
expect(parseDumpsysPackageOutput(packageDump(line))).toMatchObject({
versionName: "1.2.3",
installerPackageName: null,
});
});
// A manifest may declare the literal string "null" as its version name and
// dumpsys prints it the same way as an absent one, so the text is kept.
it("keeps a version name printed as the text null", () => {
expect(
parseDumpsysPackageOutput(
packageDump("installerPackageName=null").replace(
"versionName=1.2.3",
"versionName=null",
),
).versionName,
).toBe("null");
});
});
describe("parseWindowFocusOutput display contexts", () => {
it.each([false, true])(
"retains both display owners independent of block order: %s",
(reverse) => {
const inactive =
" Display: mDisplayId=3 rootTasks=0\n mCurrentFocus=null\n mFocusedApp=null\n";
const active =
" Display: mDisplayId=0 rootTasks=12\n mCurrentFocus=Window{active}\n mFocusedApp=ActivityRecord{active}\n";
const result = parseWindowFocusOutput(
reverse ? active + inactive : inactive + active,
);
expect(result).toMatchObject({
focused_window: null,
focused_app: null,
focus_summary: "ambiguous",
focus_contexts: expect.arrayContaining([
{
scope: "display",
display_id: "3",
header_line: " Display: mDisplayId=3 rootTasks=0",
declarations: [
{
field: "mCurrentFocus",
value: null,
raw_line: " mCurrentFocus=null",
},
{
field: "mFocusedApp",
value: null,
raw_line: " mFocusedApp=null",
},
],
},
{
scope: "display",
display_id: "0",
header_line: " Display: mDisplayId=0 rootTasks=12",
declarations: [
{
field: "mCurrentFocus",
value: "Window{active}",
raw_line: " mCurrentFocus=Window{active}",
},
{
field: "mFocusedApp",
value: "ActivityRecord{active}",
raw_line: " mFocusedApp=ActivityRecord{active}",
},
],
},
]),
});
},
);
it("does not pair declarations from different displays", () => {
expect(
parseWindowFocusOutput(
"Display: mDisplayId=0\nmCurrentFocus=Window{one}\nDisplay: mDisplayId=1\nmFocusedApp=ActivityRecord{two}\n",
),
).toMatchObject({
focus_summary: "ambiguous",
focused_window: null,
focused_app: null,
focus_contexts: [
{
display_id: "0",
declarations: [{ field: "mCurrentFocus", value: "Window{one}" }],
},
{
display_id: "1",
declarations: [
{ field: "mFocusedApp", value: "ActivityRecord{two}" },
],
},
],
});
});
it("retains an unknown display identity and missing fields without borrowing its neighbor", () => {
expect(
parseWindowFocusOutput(
"Display: vendor-format\nmCurrentFocus=null\nDisplay: mDisplayId=0\n",
),
).toMatchObject({
focus_summary: "ambiguous",
focus_contexts: [
{
scope: "display",
display_id: null,
header_line: "Display: vendor-format",
declarations: [{ field: "mCurrentFocus", value: null }],
},
{ scope: "display", display_id: "0", declarations: [] },
],
});
});
});
describe("parseWindowFocusOutput declaration presence", () => {
it("separates missing declarations from explicit absence and retains source lines", () => {
expect(parseWindowFocusOutput("no declarations\n")).toEqual({
focused_window: null,
focused_app: null,
focus_summary: "unavailable",
focus_contexts: [],
observed_lines: [],
});
expect(
parseWindowFocusOutput(" mCurrentFocus=null\nmFocusedApp=null\n"),
).toEqual({
focused_window: null,
focused_app: null,
focus_summary: "single_context",
focus_contexts: [
{
scope: "unscoped",
display_id: null,
header_line: null,
declarations: [
{
field: "mCurrentFocus",
value: null,
raw_line: " mCurrentFocus=null",
},
{ field: "mFocusedApp", value: null, raw_line: "mFocusedApp=null" },
],
},
],
observed_lines: [" mCurrentFocus=null", "mFocusedApp=null"],
});
});
it("retains repeated declarations without choosing one or erasing explicit absence", () => {
expect(
parseWindowFocusOutput(
"mCurrentFocus=null\nmCurrentFocus=Window{later}\nmFocusedApp=ActivityRecord{current}\n",
),
).toMatchObject({
focus_summary: "ambiguous",
focused_window: null,
focused_app: null,
focus_contexts: [
{
declarations: [
{ field: "mCurrentFocus", value: null },
{ field: "mCurrentFocus", value: "Window{later}" },
{ field: "mFocusedApp", value: "ActivityRecord{current}" },
],
},
],
observed_lines: [
"mCurrentFocus=null",
"mCurrentFocus=Window{later}",
"mFocusedApp=ActivityRecord{current}",
],
});
});
});
describe("parseDumpsysPackageOutput", () => {
it.each(["userId", "appId"])(
"retains the printed %s package identifier",
(key) => {
expect(
parseDumpsysPackageOutput(
`Packages:\n Package [com.example] (abcdef):\n ${key}=10234\n`,
).userId,
).toBe(10234);
},
);
it("reports unknown requested permissions when the package is null", () => {
expect(
parseDumpsysPackageOutput("Package [com.example] (abcdef):\n pkg=null\n")
.requestedPermissions,
).toBeNull();
});
it("reports unknown when package state and the permission section are absent", () => {
expect(
parseDumpsysPackageOutput(
"Package [com.example] (abcdef):\n versionCode=45\n",
).requestedPermissions,
).toBeNull();
});
it("reports zero only for a confirmed loaded package without the section", () => {
expect(
parseDumpsysPackageOutput(
"Package [com.example] (abcdef):\n pkg=Package{abcdef com.example}\n",
).requestedPermissions,
).toBe(0);
});
it("counts listed permissions when the section is available", () => {
expect(
parseDumpsysPackageOutput(
"Package [com.example] (abcdef):\n requested permissions:\n android.permission.INTERNET\n",
).requestedPermissions,
).toBe(1);
});
it("does not count permissions in the hidden system packages section", () => {
expect(
parseDumpsysPackageOutput(
[
"Package [com.example] (abcdef):",
" pkg=Package{abcdef com.example}",
" requested permissions:",
" android.permission.INTERNET",
"Hidden system packages:",
" requested permissions:",
" com.android.hidden.permission",
].join("\n"),
).requestedPermissions,
).toBe(1);
});
it("counts permission names containing hyphens and dollar signs", () => {
expect(
parseDumpsysPackageOutput(
[
"Package [com.example] (abcdef):",
" pkg=Package{abcdef com.example}",
" requested permissions:",
" com.example.permission-Hyphen",
" com.example.permission$Dollar",
].join("\n"),
).requestedPermissions,
).toBe(2);
});
it("counts Unicode permission names without ending the section", () => {
expect(
parseDumpsysPackageOutput(
[
"Package [com.example] (abcdef):",
" pkg=Package{abcdef com.example}",
" requested permissions:",
" com.example.permission-First",
" com.example.permission.ÉCRIRE WITH SPACE",
" android.permission.CAMERA",
" install permissions:",
" android.permission.CAMERA: granted=true",
].join("\n"),
).requestedPermissions,
).toBe(3);
});
it("ends requested permissions at a same-indent field", () => {
expect(
parseDumpsysPackageOutput(
[
"Package [com.example] (abcdef):",
" pkg=Package{abcdef com.example}",
" requested permissions:",
" android.permission.INTERNET",
" gids=[1000]",
" android.permission.CAMERA",
].join("\n"),
).requestedPermissions,
).toBe(1);
});
});
describe("parseWmDensityOutput", () => {
it("reads labeled physical and override densities", () => {
expect(
parseWmDensityOutput("Physical density: 420\nOverride density: 480\n"),
).toEqual({
physical: 420,
override: 480,
numericIssues: { sample: [], total: 0 },
});
expect(parseWmDensityOutput("Physical density: 420\n")).toEqual({
physical: 420,
override: null,
numericIssues: { sample: [], total: 0 },
});
});
// Older builds print the physical density as a bare number
// (morluto/rea#3942); it must not be lost as an unread declaration.
it("reads a bare numeric density as the physical density", () => {
expect(parseWmDensityOutput("420\n")).toEqual({
physical: 420,
override: null,
numericIssues: { sample: [], total: 0 },
});
expect(parseWmDensityOutput("420\r\n")).toEqual({
physical: 420,
override: null,
numericIssues: { sample: [], total: 0 },
});
});
it("prefers a labeled physical density over a bare number", () => {
expect(parseWmDensityOutput("560\nPhysical density: 420\n")).toEqual({
physical: 420,
override: null,
numericIssues: { sample: [], total: 0 },
});
});
it("reports no density for unrecognized output", () => {
expect(parseWmDensityOutput("")).toEqual({
physical: null,
override: null,
numericIssues: { sample: [], total: 0 },
});
expect(parseWmDensityOutput("420dpi\n")).toEqual({
physical: null,
override: null,
numericIssues: { sample: [], total: 0 },
});
});
});
const starting =
"Starting: Intent { act=android.intent.action.MAIN cmp=com.example/.Main }";
describe("parseAmStartOutput", () => {
it("recognizes a CRLF wait-timeout report", () => {
expect(
parseAmStartOutput(
`${starting}\r\nStatus: timeout\r\nActivity: com.example/.Main\r\nComplete\r\n`,
),
).toMatchObject({
status: "refused",
activity: "com.example/.Main",
reportedStatus: "timeout",
reason: "am start reported status: timeout",
});
});
it("treats every non-ok status as refusal, including a later status after ok", () => {
expect(
parseAmStartOutput(`${starting}\nStatus: ok\nStatus: timeout\n`),
).toMatchObject({
status: "refused",
reportedStatus: "timeout",
reason: "am start reported status: timeout",
});
expect(
parseAmStartOutput(`${starting}\nStatus: future-value\n`),
).toMatchObject({
status: "refused",
reportedStatus: "future-value",
});
});
it("gives hard errors in either stream priority over status and attempt", () => {
expect(
parseAmStartOutput(
`${starting}\nStatus: timeout\nActivity: com.example/.Main\n`,
"Exception: Activity class does not exist\n",
),
).toEqual({
status: "refused",
startedActivity: "act=android.intent.action.MAIN cmp=com.example/.Main",
activity: "com.example/.Main",
reportedStatus: "timeout",
reason: "Activity class does not exist",
});
expect(
parseAmStartOutput(
`${starting}\nStatus: ok\nError: Activity class does not exist\n`,
),
).toMatchObject({
status: "refused",
reason: "Activity class does not exist",
});
});
it("refuses an unprefixed Java exception even when Starting is present", () => {
expect(
parseAmStartOutput(
`${starting}\njava.lang.SecurityException: Permission Denial\n`,
),
).toMatchObject({
status: "refused",
reason: "java.lang.SecurityException: Permission Denial",
});
});
it("refuses a message-free Java exception even when Starting is present", () => {
expect(
parseAmStartOutput(`${starting}\njava.lang.NullPointerException\n`),
).toMatchObject({
status: "refused",
reason: "java.lang.NullPointerException",
});
});
it("recognizes a base Java exception type with a message", () => {
expect(
parseAmStartOutput(`${starting}\njava.lang.Exception: failure\n`),
).toMatchObject({
status: "refused",
reason: "java.lang.Exception: failure",
});
});
it("prefers a specific Java exception over generic error headers", () => {
expect(
parseAmStartOutput(
[
starting,
"Error type 3",
"Exception occurred while executing command",
"java.lang.SecurityException: Permission Denial",
].join("\n"),
),
).toMatchObject({
status: "refused",
reason: "java.lang.SecurityException: Permission Denial",
});
expect(
parseAmStartOutput(
[
starting,
"Error: Generic launch failure",
"java.lang.SecurityException: Permission Denial",
].join("\n"),
),
).toMatchObject({
status: "refused",
reason: "java.lang.SecurityException: Permission Denial",
});
});
it("keeps Starting-only output compatible and refuses no-attempt output", () => {
expect(parseAmStartOutput(starting)).toMatchObject({
status: "started",
reportedStatus: null,
activity: null,
});
expect(
parseAmStartOutput("Status: ok\nWarning: Activity not started\n"),
).toMatchObject({
status: "refused",
startedActivity: null,
reportedStatus: "ok",
});
});
});
describe("parsePackageChangeOutput", () => {
it.each(["", "Performing Streamed Install\n", "progress"])(
"keeps an absent package-change verdict unknown: %j",
(text) => {
expect(parsePackageChangeOutput(text)).toEqual({
status: "unknown",
reason: null,
});
},
);
it("retains explicit failure even alongside success text", () => {
expect(parsePackageChangeOutput("Success\nFailure [DENIED]\n")).toEqual({
status: "failure",
reason: "DENIED",
});
expect(parsePackageChangeOutput("Success\n")).toEqual({
status: "success",
reason: null,
});
});
});