Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 78 additions & 3 deletions src/core/platform-inventory.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,29 @@ import assert from 'node:assert/strict';
import { test, vi } from 'vitest';
import type { DeviceInfo } from '@agent-device/kernel/device';

const { listVegaDevices } = vi.hoisted(() => ({
listVegaDevices: vi.fn(),
}));
const { listVegaDevices, listAndroidDevices, listAppleDevices, listLinuxDevices } = vi.hoisted(
() => ({
listVegaDevices: vi.fn(),
listAndroidDevices: vi.fn(),
listAppleDevices: vi.fn(),
listLinuxDevices: vi.fn(),
}),
);

vi.mock('../platforms/vega/devices.ts', () => ({
listVegaDevices,
}));
vi.mock('../platforms/android/devices.ts', () => ({
listAndroidDevices,
}));
vi.mock('../platforms/apple/core/devices.ts', () => ({
listAppleDevices,
}));
vi.mock('../platforms/linux/devices.ts', () => ({
listLinuxDevices,
}));

import { LOCAL_DEVICE_INVENTORY_PLATFORM_SELECTORS } from '@agent-device/contracts/device';
import { listLocalDeviceInventory } from './platform-inventory.ts';

const VEGA_EMULATOR: DeviceInfo = {
Expand All @@ -33,3 +48,63 @@ test('explicit Vega inventory delegates to the Vega device module', async () =>
assert.deepEqual(result, [VEGA_EMULATOR]);
assert.deepEqual(listVegaDevices.mock.calls[0], []);
});

test('probes every platform concurrently and keeps selector order in the result', async () => {
// Sequential awaits made an unfiltered lookup cost the sum of every
// toolchain probe. Each stub records when it starts and resolves only once
// all four have started, so this deadlocks unless they genuinely overlap.
const PLATFORM_COUNT = LOCAL_DEVICE_INVENTORY_PLATFORM_SELECTORS.length;
let started = 0;
let allStarted!: () => void;
const everyProbeStarted = new Promise<void>((resolve) => {
allStarted = resolve;
});
const gate = async () => {
started += 1;
if (started === PLATFORM_COUNT) allStarted();
await everyProbeStarted;
};

listAndroidDevices.mockImplementation(async () => {
await gate();
return [device('android', 'android-1')];
});
listAppleDevices.mockImplementation(async () => {
await gate();
return [device('apple', 'apple-1')];
});
listVegaDevices.mockImplementation(async () => {
await gate();
return [device('vega', 'vega-1')];
});
listLinuxDevices.mockImplementation(async () => {
await gate();
return [device('linux', 'linux-1')];
});

const result = await listLocalDeviceInventory({});

assert.equal(started, PLATFORM_COUNT);
assert.deepEqual(
result.map((entry) => entry.id),
['android-1', 'apple-1', 'vega-1', 'linux-1'],
);
});

test('a failing platform probe does not drop the devices found by the others', async () => {
listAndroidDevices.mockRejectedValue(new Error('adb exploded'));
listAppleDevices.mockResolvedValue([device('apple', 'apple-1')]);
listVegaDevices.mockRejectedValue(new Error('vega exploded'));
listLinuxDevices.mockResolvedValue([device('linux', 'linux-1')]);

const result = await listLocalDeviceInventory({});

assert.deepEqual(
result.map((entry) => entry.id),
['apple-1', 'linux-1'],
);
});

function device(platform: DeviceInfo['platform'], id: string): DeviceInfo {
return { platform, id, name: id, kind: 'device', booted: true };
}
30 changes: 21 additions & 9 deletions src/core/platform-inventory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,27 @@ export async function listLocalDeviceInventory(
});
}

const devices: DeviceInfo[] = [];
// Linux local device is appended last so it does not displace
// connected Android/Apple devices in implicit auto-selection.
for (const platform of LOCAL_DEVICE_INVENTORY_PLATFORM_SELECTORS) {
try {
devices.push(...(await listLocalDeviceInventory({ ...request, platform })));
} catch {}
}
return devices;
// Probed concurrently: each platform shells out to its own toolchain, and
// awaiting them in turn made an unfiltered lookup cost their sum — measured
// at 6.7s on a host with the Apple, Android and Vega toolchains installed,
// most of it spent enumerating platforms the request could not target.
//
// Results are still concatenated in selector order, so the Linux local device
// stays last and does not displace connected Android/Apple devices in
// implicit auto-selection.
const perPlatform = await Promise.all(
LOCAL_DEVICE_INVENTORY_PLATFORM_SELECTORS.map(async (platform) => {
try {
const listed = await listLocalDeviceInventory({ ...request, platform });
// A platform that answers with anything but a list contributes nothing,
// exactly as before: spreading a non-array used to throw into the catch.
return Array.isArray(listed) ? listed : [];
} catch {
return [];
}
}),
);
return perPlatform.flat();
}

export function resolveAndroidDiscoverySerialAllowlist(
Expand Down
Loading