diff --git a/src/main/emulator/android/android-avd-boot-identity.test.ts b/src/main/emulator/android/android-avd-boot-identity.test.ts new file mode 100644 index 00000000000..5be85f3b337 --- /dev/null +++ b/src/main/emulator/android/android-avd-boot-identity.test.ts @@ -0,0 +1,55 @@ +import { describe, expect, it, vi } from 'vitest' +import { bootAndroidDevice } from './android-avd-boot' +import type { AndroidSdkPaths } from './android-sdk-discovery' + +vi.mock('node:child_process', () => ({ + spawn: () => ({ on: vi.fn(), unref: vi.fn() }) +})) + +const sdk: AndroidSdkPaths = { + sdkRoot: 'sdk', + adb: 'adb', + emulator: 'emulator', + avdmanager: 'avdmanager' +} +type Device = { serial: string; name: string; booted: boolean } +const other: Device = { serial: 'emulator-5556', name: 'Other', booted: true } +const target: Device = { + serial: 'emulator-5558', + name: 'Wanted', + booted: true +} + +function bootWithInventory(snapshots: Device[][]) { + let reads = 0 + let current: Device[] = [] + const runner = vi.fn(async (binary: string, args: readonly string[]) => { + let stdout = '' + if (binary === sdk.emulator && args[0] === '-list-avds') { + stdout = 'Wanted\nOther\n' + } else if (args[0] === 'devices') { + current = snapshots[Math.min(reads++, snapshots.length - 1)] + stdout = `List of devices attached\n${current.map((d) => `${d.serial}\tdevice`).join('\n')}` + } else { + const device = current.find((d) => d.serial === args[1]) + stdout = args[2] === 'emu' ? `${device?.name ?? ''}\nOK\n` : device?.booted ? '1' : '0' + } + return { code: 0, stdout, stderr: '' } + }) + return bootAndroidDevice(runner, sdk, 'Wanted', { + bootTimeoutMs: 30, + pollIntervalMs: 10, + sleep: async () => {} + }) +} + +describe('Android AVD boot identity', () => { + it('waits for the requested AVD when another emulator boots first', async () => { + await expect( + bootWithInventory([[], [other], [other, { ...target, booted: false }], [other, target]]) + ).resolves.toBe(target.serial) + await expect(bootWithInventory([[], [other]])).rejects.toThrow( + 'AVD "Wanted" did not finish booting in time.' + ) + }) +}) diff --git a/src/main/emulator/android/android-avd-boot.ts b/src/main/emulator/android/android-avd-boot.ts index 81aeff06588..ecfcb90e1aa 100644 --- a/src/main/emulator/android/android-avd-boot.ts +++ b/src/main/emulator/android/android-avd-boot.ts @@ -64,10 +64,11 @@ async function waitForNewBootedSerial( const fresh = (await listRunningAdbDevices(runner, sdk)).filter( (device) => device.isEmulator && !known.has(device.serial) ) - for (const device of fresh) { - const booted = await runner(sdk.adb, bootCompletedArgs(device.serial)) + const serial = await findRunningAvdSerial(runner, sdk, avdName, fresh) + if (serial) { + const booted = await runner(sdk.adb, bootCompletedArgs(serial)) if (isBootCompleted(booted.stdout)) { - return device.serial + return serial } } await options.sleep(options.pollIntervalMs) diff --git a/src/main/emulator/backends/android-emulator-backend.test.ts b/src/main/emulator/backends/android-emulator-backend.test.ts index eae156af015..2d2b6adb03c 100644 --- a/src/main/emulator/backends/android-emulator-backend.test.ts +++ b/src/main/emulator/backends/android-emulator-backend.test.ts @@ -235,6 +235,9 @@ describe('AndroidEmulatorBackend', () => { if (binary === SDK.adb && a === '-s emulator-5556 shell getprop sys.boot_completed') { return ok('1') } + if (binary === SDK.adb && a === '-s emulator-5556 emu avd name') { + return ok('Pixel_Tablet\nOK\n') + } if (binary === SDK.emulator && a === '-list-avds') { return ok('Pixel_Tablet') }