mirror of
https://github.com/callstack/agent-device.git
synced 2026-09-14 20:06:34 +08:00
03c3984066
* perf(contracts): granularize entry surfaces so hub importers stop evaluating the facade clump
`@agent-device/contracts/platform` unions 32 vocabulary modules and
`/interaction` another 18. A file that value-imports either evaluates the whole
union to reach one function, and because permanent hubs sat behind them —
`command-descriptor/registry.ts`, `core/capabilities.ts`,
`interactors/register-builtins.ts`, `command-descriptor/platform-execution-entry.ts` —
that union rode into roughly half the unit suite's test graphs.
Give every vocabulary module its own entry subpath and move all value-importers
onto the module that owns the symbol. Type-only importers are left alone: `import
type` is erased, so it already evaluated nothing.
Measured with the #1950 eager-import-closure walker over all 974 unit-core test
files, against base e5bfde3d1:
aggregate eager module evaluations 143,248 -> 129,738 (-9.4%)
facades/platform.ts carried by 466 -> 1 test graphs
facades/interaction.ts carried by 451 -> 0 test graphs
registry.ts 105 -> 66
capabilities.ts 113 -> 76
register-builtins.ts 111 -> 73
platform-execution-entry.ts 43 -> 3
dispatch.ts 134 -> 100
Three gate adjustments the split forces:
- R11's pinned contracts subpath list grows to the new entries, and the resolver
test's "must not resolve" example moves to `./clipboard`, since `./gesture-plan`
is now a real entry.
- R16 anchored the record-runtime join on the literal `contracts/platform`
specifier. It now accepts any contracts entry — the assertion's provenance is
what the rule pins, not which subpath carried it.
- `gesture-plan.ts` became an entry target, and the no-bare-star rule rejects the
`export * from './gesture-plan-types.ts'` it carried. Its one internal consumer
now imports the owning module directly.
Both facades keep their type re-exports for the ~490 type-only importers, so
every symbol on them now reads as value-unused; one fallow entry records that
and names retiring them as the follow-up.
Closes #1959
* test(contracts): text-filter the facade scan before parsing
The repo-wide scan parsed all ~3000 sources, which the coverage lane's
instrumentation pushed past both the 5s test timeout and the 2.5s slow-test
budget. A file that never names the specifier cannot import it, so filter on the
text first and parse only the ~490 candidates.
Non-vacuity moves with it: instead of counting narrow imports across every file,
require that the surviving type-only importers were seen and classified as
erased — which an empty scan cannot satisfy.
286 lines
9.6 KiB
TypeScript
286 lines
9.6 KiB
TypeScript
import type {
|
|
DeviceInventoryRequest,
|
|
ProviderDeviceInventorySource,
|
|
} from '@agent-device/contracts/device';
|
|
import {
|
|
type DeviceInventorySource,
|
|
type InventoryPlatformModule,
|
|
createPlatformModuleRegistry,
|
|
} from '@agent-device/contracts/platform-module';
|
|
import type {
|
|
DeviceInventoryHost,
|
|
PlatformRequestScope,
|
|
} from '@agent-device/contracts/platform-runtime-host';
|
|
import { PLATFORMS, type DeviceInfo, type Platform } from '@agent-device/kernel/device';
|
|
import { describe, expect, test, vi } from 'vitest';
|
|
import { createComposedDeviceInventoryGateways } from './platform-runtime-device-inventory.ts';
|
|
|
|
const scope: PlatformRequestScope = Object.freeze({
|
|
signal: new AbortController().signal,
|
|
diagnostics: Object.freeze({ emit: () => {} }),
|
|
progress: Object.freeze({ report: () => {} }),
|
|
});
|
|
|
|
const host: DeviceInventoryHost = Object.freeze({
|
|
commands: Object.freeze({
|
|
which: async () => undefined,
|
|
run: async () => ({ stdout: '', stderr: '', exitCode: 0 }),
|
|
}),
|
|
appleTools: Object.freeze({
|
|
isXcrunAvailable: async () => false,
|
|
run: async () => ({ stdout: '', stderr: '', exitCode: 0 }),
|
|
}),
|
|
toolchains: Object.freeze({ prepare: async () => {} }),
|
|
files: Object.freeze({
|
|
isExecutable: async () => false,
|
|
createTemporaryTextFile: async () => {
|
|
throw new Error('unused');
|
|
},
|
|
}),
|
|
hostOs: 'darwin',
|
|
hostName: 'test-host',
|
|
homeDirectory: '/tmp/test-home',
|
|
observations: Object.freeze({ deviceBooted: async () => {} }),
|
|
});
|
|
|
|
describe('composed device inventory gateway', () => {
|
|
test('an authoritative provider inventory, including empty, loads no local host or source', async () => {
|
|
for (const devices of [[], [device('android', 'remote')]]) {
|
|
const local = inventoryWorld();
|
|
const provider: ProviderDeviceInventorySource = {
|
|
discover: async () => ({ kind: 'inventory', devices }),
|
|
};
|
|
const gateways = createComposedDeviceInventoryGateways({
|
|
registry: local.registry,
|
|
loadHost: local.loadHost,
|
|
provider,
|
|
});
|
|
|
|
expect(await gateways.providerFirst.discover({ platform: 'android' }, scope)).toEqual(
|
|
devices,
|
|
);
|
|
expect(local.hostLoads()).toBe(0);
|
|
expect(local.sourceLoads()).toEqual([]);
|
|
}
|
|
});
|
|
|
|
test('declined providers fall through, while malformed outcomes fail closed', async () => {
|
|
const local = inventoryWorld();
|
|
const declined = createComposedDeviceInventoryGateways({
|
|
registry: local.registry,
|
|
loadHost: local.loadHost,
|
|
provider: { discover: async () => ({ kind: 'declined' }) },
|
|
});
|
|
expect(await declined.providerFirst.discover({ platform: 'android' }, scope)).toEqual([
|
|
device('android'),
|
|
]);
|
|
|
|
const malformed = createComposedDeviceInventoryGateways({
|
|
registry: local.registry,
|
|
loadHost: local.loadHost,
|
|
provider: { discover: async () => undefined } as unknown as ProviderDeviceInventorySource,
|
|
});
|
|
await expect(
|
|
malformed.providerFirst.discover({ platform: 'android' }, scope),
|
|
).rejects.toMatchObject({ details: { reason: 'provider_inventory_contract_violation' } });
|
|
});
|
|
|
|
test('forwards the request signal into an in-flight provider source', async () => {
|
|
const controller = new AbortController();
|
|
const requestScope = { ...scope, signal: controller.signal };
|
|
const observed = vi.fn();
|
|
const provider: ProviderDeviceInventorySource = {
|
|
discover: async (_request, signal) => {
|
|
observed(signal);
|
|
return await new Promise((_resolve, reject) => {
|
|
signal.addEventListener('abort', () => reject(signal.reason), { once: true });
|
|
});
|
|
},
|
|
};
|
|
const local = inventoryWorld();
|
|
const gateways = createComposedDeviceInventoryGateways({
|
|
registry: local.registry,
|
|
loadHost: local.loadHost,
|
|
provider,
|
|
});
|
|
const pending = gateways.providerFirst.discover({ platform: 'android' }, requestScope);
|
|
controller.abort(new Error('cancelled'));
|
|
|
|
await expect(pending).rejects.toThrow('cancelled');
|
|
expect(observed).toHaveBeenCalledWith(controller.signal);
|
|
expect(local.hostLoads()).toBe(0);
|
|
});
|
|
|
|
test('keeps local projection policy out of the provider request', async () => {
|
|
const received: unknown[] = [];
|
|
const provider: ProviderDeviceInventorySource = {
|
|
discover: async (request) => {
|
|
received.push(request);
|
|
return { kind: 'inventory', devices: [] };
|
|
},
|
|
};
|
|
const local = inventoryWorld();
|
|
const gateways = createComposedDeviceInventoryGateways({
|
|
registry: local.registry,
|
|
loadHost: local.loadHost,
|
|
provider,
|
|
});
|
|
|
|
await gateways.providerFirst.discover(
|
|
{
|
|
platform: 'ios',
|
|
kind: 'simulator',
|
|
booted: true,
|
|
iosSimulatorSetPath: '/tmp/isolated-simulators',
|
|
androidSerialAllowlist: ['emulator-5554'],
|
|
},
|
|
scope,
|
|
);
|
|
|
|
expect(received).toEqual([
|
|
{
|
|
platform: 'ios',
|
|
iosSimulatorSetPath: '/tmp/isolated-simulators',
|
|
androidSerialAllowlist: ['emulator-5554'],
|
|
},
|
|
]);
|
|
expect(local.hostLoads()).toBe(0);
|
|
});
|
|
|
|
test('applies public Apple aliases and all internal projection filters centrally', async () => {
|
|
const devices = [
|
|
{ ...device('apple', 'ios-default'), appleOs: 'ios' as const, target: undefined },
|
|
{ ...device('apple', 'mac'), appleOs: 'macos' as const, target: 'desktop' as const },
|
|
{ ...device('apple', 'shutdown'), appleOs: 'ios' as const, booted: false },
|
|
device('android', 'other-family'),
|
|
];
|
|
const provider: ProviderDeviceInventorySource = {
|
|
discover: async () => ({ kind: 'inventory', devices }),
|
|
};
|
|
const local = inventoryWorld();
|
|
const gateways = createComposedDeviceInventoryGateways({
|
|
registry: local.registry,
|
|
loadHost: local.loadHost,
|
|
provider,
|
|
});
|
|
|
|
const projected = await gateways.providerFirst.discover(
|
|
{ platform: 'ios', target: 'mobile', kind: 'device', booted: true },
|
|
scope,
|
|
);
|
|
expect(projected.map(({ id }) => id)).toEqual(['ios-default']);
|
|
});
|
|
|
|
test('loads only the selected family and retries failed host and source loads', async () => {
|
|
let hostAttempts = 0;
|
|
let androidAttempts = 0;
|
|
const local = inventoryWorld({
|
|
android: async () => {
|
|
androidAttempts += 1;
|
|
if (androidAttempts === 1) throw new Error('source load failed');
|
|
return source([device('android')]);
|
|
},
|
|
});
|
|
const gateways = createComposedDeviceInventoryGateways({
|
|
registry: local.registry,
|
|
loadHost: async () => {
|
|
hostAttempts += 1;
|
|
if (hostAttempts === 1) throw new Error('host load failed');
|
|
return host;
|
|
},
|
|
});
|
|
|
|
await expect(gateways.localOnly.discover({ platform: 'android' }, scope)).rejects.toThrow(
|
|
'host load failed',
|
|
);
|
|
await expect(gateways.localOnly.discover({ platform: 'android' }, scope)).rejects.toThrow(
|
|
'source load failed',
|
|
);
|
|
expect(await gateways.localOnly.discover({ platform: 'android' }, scope)).toEqual([
|
|
device('android'),
|
|
]);
|
|
expect(hostAttempts).toBe(2);
|
|
expect(androidAttempts).toBe(2);
|
|
expect(local.sourceLoads()).toEqual(['android', 'android']);
|
|
});
|
|
|
|
test('unfiltered local discovery is fail-soft and preserves selector order', async () => {
|
|
const local = inventoryWorld({
|
|
harmonyos: async () => source([], new Error('hdc unavailable')),
|
|
});
|
|
const gateways = createComposedDeviceInventoryGateways({
|
|
registry: local.registry,
|
|
loadHost: local.loadHost,
|
|
});
|
|
|
|
const devices = await gateways.localOnly.discover({}, scope);
|
|
expect(devices.map(({ platform }) => platform)).toEqual(['android', 'apple', 'vega', 'linux']);
|
|
expect(local.sourceLoads()).toEqual(['android', 'harmonyos', 'apple', 'vega', 'linux']);
|
|
});
|
|
|
|
test('explicit local failures propagate and family sources cannot return another family', async () => {
|
|
const failing = inventoryWorld({ android: async () => source([], new Error('adb failed')) });
|
|
await expect(
|
|
createComposedDeviceInventoryGateways({
|
|
registry: failing.registry,
|
|
loadHost: failing.loadHost,
|
|
}).localOnly.discover({ platform: 'android' }, scope),
|
|
).rejects.toThrow('adb failed');
|
|
|
|
const lying = inventoryWorld({
|
|
android: async () => source([device('linux', 'wrong-family')]),
|
|
});
|
|
await expect(
|
|
createComposedDeviceInventoryGateways({
|
|
registry: lying.registry,
|
|
loadHost: lying.loadHost,
|
|
}).localOnly.discover({ platform: 'android' }, scope),
|
|
).rejects.toMatchObject({
|
|
details: { expectedFamily: 'android', actualFamily: 'linux' },
|
|
});
|
|
});
|
|
});
|
|
|
|
function inventoryWorld(
|
|
overrides: Partial<Record<Platform, () => Promise<DeviceInventorySource>>> = {},
|
|
) {
|
|
const loaded: Platform[] = [];
|
|
let hosts = 0;
|
|
const modules: InventoryPlatformModule[] = PLATFORMS.map((family) => ({
|
|
family,
|
|
loadInventory: async () => {
|
|
loaded.push(family);
|
|
return await (overrides[family]?.() ?? Promise.resolve(source([device(family)])));
|
|
},
|
|
}));
|
|
return {
|
|
registry: createPlatformModuleRegistry(modules),
|
|
loadHost: async () => {
|
|
hosts += 1;
|
|
return host;
|
|
},
|
|
hostLoads: () => hosts,
|
|
sourceLoads: () => [...loaded],
|
|
};
|
|
}
|
|
|
|
function source(devices: readonly DeviceInfo[], failure?: Error): DeviceInventorySource {
|
|
return {
|
|
discover: async (_request: Readonly<DeviceInventoryRequest>) => {
|
|
if (failure) throw failure;
|
|
return devices;
|
|
},
|
|
};
|
|
}
|
|
|
|
function device(platform: Platform, id = `${platform}-device`): DeviceInfo {
|
|
return {
|
|
platform,
|
|
id,
|
|
name: id,
|
|
kind: 'device',
|
|
target: platform === 'vega' ? 'tv' : 'mobile',
|
|
booted: true,
|
|
};
|
|
}
|