Skip to content

Commit c7af6cd

Browse files
authored
refactor(ios): drop the transport seam usbmux-first made dead (#1540)
The physical-device control exposed resolveRunnerTransport returning either a network tunnel or usbmux, from before the route resolver decided transports. Since #1517 the resolver returns a usbmux route for XCTest devices and for any attached CoreDevice one, and only reaches the control after usbmux has reported the device unattached — so the control is asked exclusively for a tunnel. That left the usbmux arm with no live producer or consumer: the branch handling it in the resolver was unreachable, and the XCTest implementation returning it was called only by a test. Collapse the union to the one shape that is resolved, rename the seam to say what it does, delete the unreachable branch, and let XCTest reject a tunnel lookup the way it already rejects app inventory and process lookup — it has no CoreDevice tunnel, which is why the resolver never asks it for one.
1 parent 5aba93f commit c7af6cd

3 files changed

Lines changed: 32 additions & 20 deletions

File tree

src/platforms/apple/core/__tests__/physical-device-control.test.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import path from 'node:path';
55
import { test } from 'vitest';
66
import { IOS_DEVICE as SHARED_IOS_DEVICE } from '../../../../__tests__/test-utils/index.ts';
77
import type { DeviceInfo } from '@agent-device/kernel/device';
8+
import type { AppError } from '@agent-device/kernel/errors';
89
import { createAppleInteractor } from '../../interactor.ts';
910
import { installIosApp } from '../app-install.ts';
1011
import { closeIosApp, openIosApp } from '../app-launch.ts';
@@ -47,11 +48,15 @@ test('XCTest readiness uses xcdevice instead of devicectl', async () => {
4748
args: ['xcdevice', 'wait', '--both', '--timeout=15', XCTEST_IOS_DEVICE.id],
4849
},
4950
]);
50-
assert.deepEqual(
51-
await resolveIosPhysicalDeviceControl(XCTEST_IOS_DEVICE).resolveRunnerTransport(
52-
XCTEST_IOS_DEVICE,
53-
),
54-
{ kind: 'usbmux' },
51+
// An XCTest-backed device has no CoreDevice tunnel, and the route resolver
52+
// never asks it for one: it returns a usbmux route before reaching this.
53+
await assert.rejects(
54+
async () =>
55+
await resolveIosPhysicalDeviceControl(XCTEST_IOS_DEVICE).resolveTunnel(XCTEST_IOS_DEVICE),
56+
(error: unknown) => {
57+
assert.equal((error as AppError).code, 'UNSUPPORTED_OPERATION');
58+
return true;
59+
},
5560
);
5661
});
5762

src/platforms/apple/core/physical-device-control.ts

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,12 @@ import {
3232
import { runXcrun } from './tool-provider.ts';
3333

3434
export type IosPhysicalDeviceBackend = 'coredevice' | 'xctest';
35-
export type IosPhysicalDeviceRunnerTransport =
36-
| { kind: 'network'; tunnelIp: string | null }
37-
| { kind: 'usbmux' };
35+
/**
36+
* Only the CoreDevice tunnel address is resolved here. Which transport a runner
37+
* command uses is decided by the route resolver, which reaches this at all only
38+
* after usbmux has reported the device unattached.
39+
*/
40+
export type IosPhysicalDeviceTunnel = { tunnelIp: string | null };
3841

3942
type IosPhysicalDeviceLaunchOptions = {
4043
payloadUrl?: string;
@@ -75,10 +78,7 @@ export type IosPhysicalDeviceControl = {
7578
outPath: string,
7679
timeoutMs?: number,
7780
): Promise<void>;
78-
resolveRunnerTransport(
79-
device: DeviceInfo,
80-
timeoutBudgetMs?: number,
81-
): Promise<IosPhysicalDeviceRunnerTransport>;
81+
resolveTunnel(device: DeviceInfo, timeoutBudgetMs?: number): Promise<IosPhysicalDeviceTunnel>;
8282
};
8383

8484
const CONTROLS: Record<IosPhysicalDeviceBackend, IosPhysicalDeviceControl> = {
@@ -94,8 +94,7 @@ const CONTROLS: Record<IosPhysicalDeviceBackend, IosPhysicalDeviceControl> = {
9494
resolveAppProcesses: resolveCoreDeviceAppProcesses,
9595
captureScreenshot: captureCoreDeviceScreenshot,
9696
copyRunnerFile: copyCoreDeviceRunnerFile,
97-
resolveRunnerTransport: async (device, timeoutBudgetMs) => ({
98-
kind: 'network',
97+
resolveTunnel: async (device, timeoutBudgetMs) => ({
9998
tunnelIp: await resolveCoreDeviceTunnelIp(device, timeoutBudgetMs),
10099
}),
101100
},
@@ -111,7 +110,7 @@ const CONTROLS: Record<IosPhysicalDeviceBackend, IosPhysicalDeviceControl> = {
111110
resolveAppProcesses: rejectXctestProcessLookup,
112111
captureScreenshot: captureXctestDeviceScreenshot,
113112
copyRunnerFile: rejectXctestRunnerFileCopy,
114-
resolveRunnerTransport: async () => ({ kind: 'usbmux' }),
113+
resolveTunnel: rejectXctestTunnelLookup,
115114
},
116115
};
117116

@@ -159,6 +158,18 @@ async function rejectXctestProcessLookup(device: DeviceInfo): Promise<never> {
159158
);
160159
}
161160

161+
async function rejectXctestTunnelLookup(device: DeviceInfo): Promise<never> {
162+
throw new AppError(
163+
'UNSUPPORTED_OPERATION',
164+
'XCTest-backed physical iOS devices have no CoreDevice tunnel.',
165+
{
166+
deviceId: device.id,
167+
backend: 'xctest',
168+
hint: 'Connect the device by cable so it is reachable through usbmux.',
169+
},
170+
);
171+
}
172+
162173
async function rejectXctestRunnerFileCopy(device: DeviceInfo): Promise<never> {
163174
throw new AppError(
164175
'UNSUPPORTED_OPERATION',

src/platforms/apple/core/runner/runner-command-route.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,7 @@ export function createRunnerCommandRouteResolver(device: DeviceInfo, port: numbe
6161
return buildNetworkRoute(device, port, requestTunnelIp, false);
6262
}
6363
}
64-
const transport = await control.resolveRunnerTransport(device, timeoutBudgetMs);
65-
if (transport.kind === 'usbmux') {
66-
return buildUsbmuxRoute(device, port);
67-
}
68-
const tunnelIp = transport.tunnelIp;
64+
const { tunnelIp } = await control.resolveTunnel(device, timeoutBudgetMs);
6965
requestTunnelIp = tunnelIp;
7066
if (tunnelIp) writeDeviceTunnelIpCache(device.id, tunnelIp);
7167
return buildNetworkRoute(device, port, tunnelIp, false);

0 commit comments

Comments
 (0)