From b0277a780e09f560a74a6ac540ba7736217ac814 Mon Sep 17 00:00:00 2001 From: Adam Rowe Date: Fri, 16 Oct 2026 14:36:13 -0400 Subject: [PATCH 1/2] fix(android): ignore emulator log lines in -list-avds output `emulator -list-avds` can print its crashdata INFO banner on stdout with exit code 0. The inventory treated every non-empty stdout line as an AVD, so a machine with zero AVDs listed a phantom emulator named after the banner. Accept only lines matching avdmanager's AVD-name grammar. Fixes #3716 --- .../platform-android/src/inventory-parsers.ts | 5 ++- .../platform-android/src/inventory.test.ts | 31 +++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/packages/platform-android/src/inventory-parsers.ts b/packages/platform-android/src/inventory-parsers.ts index cf65fae510..5ee5f9b874 100644 --- a/packages/platform-android/src/inventory-parsers.ts +++ b/packages/platform-android/src/inventory-parsers.ts @@ -39,11 +39,14 @@ export function parseAndroidDeviceDescription(rawOutput: string): { }; } +/** avdmanager restricts AVD names to these characters; other stdout lines are emulator logging. */ +const ANDROID_AVD_NAME = /^[A-Za-z0-9._-]+$/; + export function parseAndroidAvdList(rawOutput: string): string[] { return rawOutput .split('\n') .map((line) => line.trim()) - .filter((line) => line.length > 0); + .filter((line) => ANDROID_AVD_NAME.test(line)); } export function parseAndroidEmulatorAvdNameOutput(rawOutput: string): string | undefined { diff --git a/packages/platform-android/src/inventory.test.ts b/packages/platform-android/src/inventory.test.ts index 2c3f4f50f6..6d48b980f9 100644 --- a/packages/platform-android/src/inventory.test.ts +++ b/packages/platform-android/src/inventory.test.ts @@ -77,6 +77,37 @@ test.each([ }, ); +const EMULATOR_CRASHDATA_BANNER = + 'INFO | Storing crashdata in: /tmp/user/emu-crash-34.2.16.db, detection is enabled for process: 98765'; + +test.each([ + ['only the crashdata banner', `${EMULATOR_CRASHDATA_BANNER}\n`, []], + [ + 'the crashdata banner beside AVD names', + `${EMULATOR_CRASHDATA_BANNER}\r\nPixel_9_Pro_XL\r\nwear-os.36\r\n`, + ['Pixel_9_Pro_XL', 'wear-os.36'], + ], +] as const)( + 'Android inventory lists only AVD names when emulator -list-avds prints %s', + async (_case, listAvdsStdout, expectedAvds) => { + const host = createHost(async (request) => { + if (request.executable === 'emulator') return result(listAvdsStdout); + if (request.args.join('\0') === 'devices\0-l') return result('List of devices attached\n'); + throw new Error(`Unexpected command: ${request.executable} ${request.args.join(' ')}`); + }); + + const devices = await createAndroidInventory(host).discover( + { androidAvdSelection: 'include-stopped' }, + scope, + ); + + assert.deepEqual( + devices.map((device) => device.id), + expectedAvds, + ); + }, +); + test.each([ ['exits non-zero', async () => result('', 'error: closed', 1)], [ From 39478b4a56b1732e5bda6d0a0671af2488195eed Mon Sep 17 00:00:00 2001 From: Adam Rowe Date: Fri, 16 Oct 2026 20:20:54 -0400 Subject: [PATCH 2/2] test(android): pin AVD-name filtering in parseAndroidAvdList --- .../platform-android/src/inventory-parsers.test.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/platform-android/src/inventory-parsers.test.ts b/packages/platform-android/src/inventory-parsers.test.ts index 5f8f383c42..3ef8aed364 100644 --- a/packages/platform-android/src/inventory-parsers.test.ts +++ b/packages/platform-android/src/inventory-parsers.test.ts @@ -35,6 +35,16 @@ test('Android inventory parsers preserve device, AVD-name, and TV detection sema assert.equal(parseAndroidFeatureListForTv('feature:android.hardware.type.television'), true); }); +test('Android AVD list parser keeps AVD names and drops emulator log lines', () => { + assert.deepEqual( + parseAndroidAvdList( + 'INFO | Storing crashdata in: /tmp/user/emu-crash-34.2.16.db, detection is enabled for process: 98765\r\n' + + 'Pixel_9_Pro_XL\r\nwear-os.36\r\nWARNING | unrelated log line\r\n', + ), + ['Pixel_9_Pro_XL', 'wear-os.36'], + ); +}); + test('Android description parser reads the model and OS version lines and skips unset props', () => { assert.deepEqual(parseAndroidDeviceDescription('sdk_gphone16k_arm64\r\n17\r\n'), { model: 'sdk_gphone16k_arm64',