fix(adb): parse successful daemon notices from both output streams
Recognize exact current and legacy ADB startup notices without treating stderr as device inventory or accepting error lookalikes. Fixes #3051. Reviewed-PR: #3073 Co-authored-by: Vinícius DS Andrade <95101505+viniciusdsandrade@users.noreply.github.com>
This commit is contained in:
1 parent
187f113f79
commit
57bb7ec5ca
4 files changed
+115
-4
No files matched your search
@@ -56,7 +56,9 @@ describe("parseAdbDevicesOutput", () => {
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("ADB daemon startup notices", () => {
|
||||
it("recognizes daemon startup notices instead of failing the listing", () => {
|
||||
const output = [
|
||||
"* daemon not running; starting now at tcp:5037",
|
||||
@@ -70,6 +72,67 @@ describe("parseAdbDevicesOutput", () => {
|
||||
expect(devices).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("recognizes daemon startup notices emitted on stderr", () => {
|
||||
const stdout = [
|
||||
"List of devices attached",
|
||||
"emulator-5554 device transport_id:1",
|
||||
"",
|
||||
].join("\n");
|
||||
const stderr = [
|
||||
"* daemon not running; starting now at tcp:5037",
|
||||
"* daemon started successfully",
|
||||
"",
|
||||
].join("\n");
|
||||
const { devices, daemonStarted, unparsedLines } = parseAdbDevicesOutput(
|
||||
stdout,
|
||||
stderr,
|
||||
);
|
||||
expect(daemonStarted).toBe(true);
|
||||
expect(unparsedLines).toEqual([]);
|
||||
expect(devices).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("recognizes the legacy daemon notice with its trailing marker and CRLF", () => {
|
||||
const { daemonStarted, unparsedLines } = parseAdbDevicesOutput(
|
||||
"List of devices attached\r\n",
|
||||
"* daemon started successfully *\r\n",
|
||||
);
|
||||
expect(daemonStarted).toBe(true);
|
||||
expect(unparsedLines).toEqual([]);
|
||||
});
|
||||
|
||||
it("does not treat a daemon startup error containing the success phrase as success", () => {
|
||||
const { daemonStarted, unparsedLines } = parseAdbDevicesOutput(
|
||||
"List of devices attached\r\n",
|
||||
"* daemon not started successfully\r\n",
|
||||
);
|
||||
expect(daemonStarted).toBe(false);
|
||||
expect(unparsedLines).toEqual([]);
|
||||
});
|
||||
|
||||
it("ignores non-startup lines and device strings on stderr", () => {
|
||||
const stdout = [
|
||||
"List of devices attached",
|
||||
"emulator-5554 device transport_id:1",
|
||||
"",
|
||||
].join("\n");
|
||||
const stderr = [
|
||||
"adb: warning: some transport warning",
|
||||
"emulator-5556 device transport_id:2",
|
||||
"",
|
||||
].join("\n");
|
||||
const { devices, daemonStarted, unparsedLines } = parseAdbDevicesOutput(
|
||||
stdout,
|
||||
stderr,
|
||||
);
|
||||
expect(daemonStarted).toBe(false);
|
||||
expect(unparsedLines).toEqual([]);
|
||||
expect(devices).toHaveLength(1);
|
||||
expect(devices[0]?.serial).toBe("emulator-5554");
|
||||
});
|
||||
});
|
||||
|
||||
describe("ADB device states", () => {
|
||||
it("keeps non-device states such as unauthorized and offline", () => {
|
||||
const output = [
|
||||
"List of devices attached",
|
||||
|
||||
@@ -30,6 +30,12 @@ export interface AdbDeviceEntry {
|
||||
|
||||
const DEVICE_LINE = /^(\S+)\s+(.*)$/u;
|
||||
|
||||
const normalizeAdbOutputLine = (rawLine: string): string => rawLine.trimEnd();
|
||||
|
||||
const isSuccessfulDaemonStartNotice = (line: string): boolean =>
|
||||
line === "* daemon started successfully" ||
|
||||
line === "* daemon started successfully *";
|
||||
|
||||
const metadataValue = (remainder: string, key: string): string | null => {
|
||||
const match = new RegExp(`(?:^|\\s)${key}:(\\S+)`, "u").exec(remainder);
|
||||
return match === null ? null : match[1]!;
|
||||
@@ -42,6 +48,7 @@ const metadataValue = (remainder: string, key: string): string | null => {
|
||||
*/
|
||||
export const parseAdbDevicesOutput = (
|
||||
stdout: string,
|
||||
stderr = "",
|
||||
): {
|
||||
devices: readonly AdbDeviceEntry[];
|
||||
unparsedLines: readonly string[];
|
||||
@@ -51,12 +58,13 @@ export const parseAdbDevicesOutput = (
|
||||
const unparsedLines: string[] = [];
|
||||
let daemonStarted = false;
|
||||
for (const rawLine of stdout.split(/\r?\n/u)) {
|
||||
const line = rawLine.trimEnd();
|
||||
if (line === "" || line.startsWith("List of devices attached")) continue;
|
||||
if (line.startsWith("* daemon")) {
|
||||
if (line.includes("started successfully")) daemonStarted = true;
|
||||
const line = normalizeAdbOutputLine(rawLine);
|
||||
if (isSuccessfulDaemonStartNotice(line)) {
|
||||
daemonStarted = true;
|
||||
continue;
|
||||
}
|
||||
if (line === "" || line.startsWith("List of devices attached")) continue;
|
||||
if (line.startsWith("* daemon")) continue;
|
||||
const match = DEVICE_LINE.exec(line);
|
||||
const state = ADB_DEVICE_STATES.find((candidate) => {
|
||||
const remainder = match?.[2];
|
||||
@@ -105,6 +113,15 @@ export const parseAdbDevicesOutput = (
|
||||
: "none",
|
||||
});
|
||||
}
|
||||
if (!daemonStarted && stderr !== "") {
|
||||
for (const rawLine of stderr.split(/\r?\n/u)) {
|
||||
const line = normalizeAdbOutputLine(rawLine);
|
||||
if (isSuccessfulDaemonStartNotice(line)) {
|
||||
daemonStarted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return { devices, unparsedLines, daemonStarted };
|
||||
};
|
||||
|
||||
|
||||
@@ -121,6 +121,36 @@ fi`,
|
||||
]);
|
||||
});
|
||||
|
||||
it("observes daemon startup when notices are printed to stderr", async () => {
|
||||
const { binary } = await writeAdbStub(
|
||||
() => `if [ "$1" = "version" ]; then ${VERSION_BLOCK}; exit 0; fi
|
||||
if [ "$1" = "devices" ]; then
|
||||
printf '* daemon not running; starting now at tcp:5037\\n* daemon started successfully\\n' >&2
|
||||
printf 'List of devices attached\\n'
|
||||
printf 'emulator-5554 device product:sdk_gphone64_x86_64 model:sdk_gphone64_x86_64 device:emu64xa transport_id:1\\n'
|
||||
exit 0
|
||||
fi`,
|
||||
);
|
||||
const result = await evidenceResult(providerOver(binary), {
|
||||
operation: "list_adb_devices",
|
||||
input: {},
|
||||
});
|
||||
expect(result.may_have_started_adb_server).toBe(true);
|
||||
expect(result.devices).toEqual([
|
||||
{
|
||||
serial: "emulator-5554",
|
||||
state: "device",
|
||||
transport: "tcp",
|
||||
product: "sdk_gphone64_x86_64",
|
||||
model: "sdk_gphone64_x86_64",
|
||||
device: "emu64xa",
|
||||
transport_id: 1,
|
||||
kind: "emulator",
|
||||
kind_basis: "emulator_serial_prefix",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("refuses device listings with an invalid state token", async () => {
|
||||
const malformedLine = "serial deviceXYZ ";
|
||||
const { binary } = await writeAdbStub(
|
||||
|
||||
@@ -755,6 +755,7 @@ export class AdbProvider {
|
||||
if (!ran.ok) return ran;
|
||||
const { devices, unparsedLines, daemonStarted } = parseAdbDevicesOutput(
|
||||
ran.value.stdout,
|
||||
ran.value.stderr,
|
||||
);
|
||||
if (unparsedLines.length > 0)
|
||||
return err(
|
||||
|
||||
Reference in new issue
Block a user