fix(adb): list devices adb reports with bare no-permissions text (#8905)

adb prints the no-permissions state as "no permissions[ (reason)]; see [URL]"
and omits the reason when it finds no udev problem to name, for example on a
Linux host without a plugdev group. The state matcher required whitespace after
"no permissions", so that line counted as unrecognized and list_adb_devices
refused the whole listing, hiding every other attached device.

Accept ";" after this one state. Other states still need a whitespace or
end-of-line boundary.
This commit is contained in:
Dragolone authored and GitHub committed 2026-11-06 10:11:24 +08:00
1 parent c363f3e27b
commit b28b1e0ae2
3 files changed
+79 -1

No files matched your search

+41
View File
@@ -168,6 +168,47 @@ describe("ADB device states", () => {
});
});
it.each([
[
"without a reason",
"no permissions; see [http://developer.android.com/tools/device.html]",
],
[
"with a reason",
"no permissions (user alice is not in the plugdev group); see [http://developer.android.com/tools/device.html]",
],
])("parses adb's no-permissions help text %s", (_name, stateText) => {
const { devices, unparsedLines } = parseAdbDevicesOutput(
[
"List of devices attached",
`07ef9c2a ${stateText} usb:1-1 transport_id:4`,
"emulator-5554 device product:sdk model:sdk device:emu transport_id:1",
"",
].join("\n"),
);
expect(unparsedLines).toEqual({ sample: [], total: 0 });
expect(devices.map(({ serial, state }) => [serial, state])).toEqual([
["07ef9c2a", "no permissions"],
["emulator-5554", "device"],
]);
expect(devices[0]).toMatchObject({
transport: "usb",
product: null,
model: null,
device: null,
transport_id: 4,
kind: "physical",
});
});
it("rejects a semicolon after any state other than no permissions", () => {
const { devices, unparsedLines } = parseAdbDevicesOutput(
"serial-bad-device device; see [url] transport_id:1\n",
);
expect(devices).toEqual([]);
expect(unparsedLines.total).toBe(1);
});
it("returns unrecognized lines instead of dropping them", () => {
const output = [
"List of devices attached",
+8 -1
View File
@@ -97,7 +97,14 @@ export const parseAdbDevicesOutput = (
if (remainder === undefined || !remainder.startsWith(candidate))
return false;
const next = remainder[candidate.length];
return next === undefined || /\s/u.test(next);
// adb prints no-permissions as `no permissions[ (reason)]; see [URL]` and
// omits the reason on a host where it finds no udev problem to name,
// such as one without a plugdev group, so `;` can follow it directly.
return (
next === undefined ||
/\s/u.test(next) ||
(candidate === "no permissions" && next === ";")
);
});
if (match === null || state === undefined) {
if (rawLine.trim() !== "") unparsed.add(rawLine);
+30
View File
@@ -239,6 +239,36 @@ fi`,
expect(result.error.message).toContain(malformedLine);
});
it("lists a USB device adb has no permission to open 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 '07ef9c2a no permissions; see [http://developer.android.com/tools/device.html] usb:1-1 transport_id:4\\n'
printf 'emulator-5554 device transport_id:1\\n'
exit 0
fi`,
);
const result = await evidenceResult(providerOver(binary), {
operation: "list_adb_devices",
input: {},
});
expect(result.devices).toEqual([
{
serial: "07ef9c2a",
state: "no permissions",
transport: "usb",
product: null,
model: null,
device: null,
transport_id: 4,
kind: "physical",
kind_basis: "usb_transport",
},
expect.objectContaining({ serial: "emulator-5554", state: "device" }),
]);
});
it("lists rescue and detached transports alongside online devices", async () => {
const { binary } = await writeAdbStub(
() => `if [ "$1" = "version" ]; then ${VERSION_BLOCK}; exit 0; fi