mirror of
https://github.com/morluto/rea.git
synced 2026-10-26 17:35:11 +00:00
fix(adb): admit canonical device states and preserve malformed output
This commit is contained in:
1 parent
18d56f5321
commit
e1cd292aa6
4 files changed
+100
-28
No files matched your search
@@ -84,6 +84,24 @@ describe("parseAdbDevicesOutput", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("accepts complete multiword states and rejects state prefixes", () => {
|
||||
const output = [
|
||||
"serial-no-permission no permissions usb:1-1",
|
||||
"serial-bad-device deviceXYZ ",
|
||||
"serial-bad-offline offline-ish",
|
||||
"serial-bad-auth unauthorizedXYZ",
|
||||
].join("\n");
|
||||
const { devices, unparsedLines } = parseAdbDevicesOutput(output);
|
||||
expect(devices.map(({ serial, state }) => [serial, state])).toEqual([
|
||||
["serial-no-permission", "no permissions"],
|
||||
]);
|
||||
expect(unparsedLines).toEqual([
|
||||
"serial-bad-device deviceXYZ ",
|
||||
"serial-bad-offline offline-ish",
|
||||
"serial-bad-auth unauthorizedXYZ",
|
||||
]);
|
||||
});
|
||||
|
||||
it("returns unrecognized lines instead of dropping them", () => {
|
||||
const output = [
|
||||
"List of devices attached",
|
||||
@@ -194,10 +212,10 @@ describe("parsePackageListOutput", () => {
|
||||
|
||||
it("keeps unrecognized lines for partial coverage reporting", () => {
|
||||
const { packages, unparsedLines } = parsePackageListOutput(
|
||||
"package:/data/app/x/base.apk=com.ok\ngarbage line\n",
|
||||
"package:/data/app/x/base.apk=com.ok\n garbage line \n",
|
||||
);
|
||||
expect(packages).toHaveLength(1);
|
||||
expect(unparsedLines).toEqual(["garbage line"]);
|
||||
expect(unparsedLines).toEqual([" garbage line "]);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
+14
-25
@@ -10,11 +10,15 @@ import {
|
||||
adbHostFileName,
|
||||
type AdbHostFileNameReason,
|
||||
} from "./AdbHostFileName.js";
|
||||
import {
|
||||
ADB_DEVICE_STATES,
|
||||
type AdbDeviceState,
|
||||
} from "../domain/adb/adbDeviceAnalysis.js";
|
||||
|
||||
/** One device exactly as `adb devices -l` reports it. */
|
||||
export interface AdbDeviceEntry {
|
||||
readonly serial: string;
|
||||
readonly state: string;
|
||||
readonly state: AdbDeviceState;
|
||||
readonly transport: "usb" | "tcp" | "unknown";
|
||||
readonly product: string | null;
|
||||
readonly model: string | null;
|
||||
@@ -24,25 +28,6 @@ export interface AdbDeviceEntry {
|
||||
readonly kind_basis: "emulator_serial_prefix" | "usb_transport" | "none";
|
||||
}
|
||||
|
||||
/**
|
||||
* Device states adb prints after the serial, longest first so two-word
|
||||
* states match before their single-word prefixes. A line whose remainder
|
||||
* starts with none of them is not a device line.
|
||||
*/
|
||||
const DEVICE_STATES = [
|
||||
"no permissions",
|
||||
"unauthorized",
|
||||
"authorizing",
|
||||
"offline",
|
||||
"recovery",
|
||||
"sideload",
|
||||
"connecting",
|
||||
"bootloader",
|
||||
"device",
|
||||
"unknown",
|
||||
"host",
|
||||
] as const;
|
||||
|
||||
const DEVICE_LINE = /^(\S+)\s+(.*)$/u;
|
||||
|
||||
const metadataValue = (remainder: string, key: string): string | null => {
|
||||
@@ -73,11 +58,15 @@ export const parseAdbDevicesOutput = (
|
||||
continue;
|
||||
}
|
||||
const match = DEVICE_LINE.exec(line);
|
||||
const state = DEVICE_STATES.find((candidate) =>
|
||||
match?.[2]?.startsWith(candidate),
|
||||
);
|
||||
const state = ADB_DEVICE_STATES.find((candidate) => {
|
||||
const remainder = match?.[2];
|
||||
if (remainder === undefined || !remainder.startsWith(candidate))
|
||||
return false;
|
||||
const next = remainder[candidate.length];
|
||||
return next === undefined || /\s/u.test(next);
|
||||
});
|
||||
if (match === null || state === undefined) {
|
||||
if (line.trim() !== "") unparsedLines.push(line);
|
||||
if (rawLine.trim() !== "") unparsedLines.push(rawLine);
|
||||
continue;
|
||||
}
|
||||
const serial = match[1]!;
|
||||
@@ -192,7 +181,7 @@ export const parsePackageListOutput = (
|
||||
if (line === "") continue;
|
||||
const match = PACKAGE_LINE.exec(line);
|
||||
if (match === null) {
|
||||
unparsedLines.push(line);
|
||||
unparsedLines.push(rawLine);
|
||||
continue;
|
||||
}
|
||||
packages.push({
|
||||
|
||||
@@ -121,6 +121,52 @@ fi`,
|
||||
]);
|
||||
});
|
||||
|
||||
it("refuses device listings with an invalid state token", async () => {
|
||||
const malformedLine = "serial deviceXYZ ";
|
||||
const { binary } = await writeAdbStub(
|
||||
() => `if [ "$1" = "version" ]; then ${VERSION_BLOCK}; exit 0; fi
|
||||
if [ "$1" = "devices" ]; then
|
||||
printf 'List of devices attached\\n${malformedLine}\\n'
|
||||
exit 0
|
||||
fi`,
|
||||
);
|
||||
const result = await providerOver(binary).execute({
|
||||
operation: "list_adb_devices",
|
||||
input: {},
|
||||
});
|
||||
expect(result.ok).toBe(false);
|
||||
if (result.ok) return;
|
||||
expect(result.error._tag).toBe("AnalysisProtocolError");
|
||||
expect(result.error.message).toContain(malformedLine);
|
||||
});
|
||||
|
||||
it("lists rescue and detached transports alongside online devices", async () => {
|
||||
const { binary } = await writeAdbStub(
|
||||
() => `if [ "$1" = "version" ]; then ${VERSION_BLOCK}; exit 0; fi
|
||||
if [ "$1" = "devices" ]; then
|
||||
printf 'List of devices attached\\n'
|
||||
printf 'serial-online device transport_id:1\\n'
|
||||
printf 'serial-rescue rescue transport_id:2\\n'
|
||||
printf 'serial-detached detached transport_id:3\\n'
|
||||
exit 0
|
||||
fi`,
|
||||
);
|
||||
const result = await evidenceResult(providerOver(binary), {
|
||||
operation: "list_adb_devices",
|
||||
input: {},
|
||||
});
|
||||
expect(result.devices).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ serial: "serial-online", state: "device" }),
|
||||
expect.objectContaining({ serial: "serial-rescue", state: "rescue" }),
|
||||
expect.objectContaining({
|
||||
serial: "serial-detached",
|
||||
state: "detached",
|
||||
}),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it("inspects a device through the getprop whitelist", async () => {
|
||||
const { binary } = await writeAdbStub(
|
||||
() => `if [ "$1" = "version" ]; then ${VERSION_BLOCK}; exit 0; fi
|
||||
|
||||
@@ -30,6 +30,25 @@ const packageNameSchema = z
|
||||
"Package name must be dot-separated Java identifiers",
|
||||
);
|
||||
|
||||
/** Text states emitted by adb's ConnectionState formatter. */
|
||||
export const ADB_DEVICE_STATES = [
|
||||
"no permissions",
|
||||
"unauthorized",
|
||||
"authorizing",
|
||||
"offline",
|
||||
"recovery",
|
||||
"sideload",
|
||||
"connecting",
|
||||
"bootloader",
|
||||
"device",
|
||||
"unknown",
|
||||
"host",
|
||||
"rescue",
|
||||
"detached",
|
||||
] as const;
|
||||
export const adbDeviceStateSchema = z.enum(ADB_DEVICE_STATES);
|
||||
export type AdbDeviceState = (typeof ADB_DEVICE_STATES)[number];
|
||||
|
||||
const keyStringSchema = z.string().min(1).max(256);
|
||||
const keyPattern = /^[A-Za-z0-9._\x2d]+$/u;
|
||||
|
||||
@@ -412,7 +431,7 @@ const client = z.strictObject({
|
||||
|
||||
const device = z.strictObject({
|
||||
serial: z.string().min(1),
|
||||
state: z.string().min(1),
|
||||
state: adbDeviceStateSchema,
|
||||
transport: z.enum(["usb", "tcp", "unknown"]),
|
||||
product: z.string().nullable(),
|
||||
model: z.string().nullable(),
|
||||
|
||||
Reference in new issue
Block a user