Skip to content

Commit f1e2425

Browse files
committed
fix: preserve Apple tool cancellation
1 parent f4c646b commit f1e2425

2 files changed

Lines changed: 69 additions & 13 deletions

File tree

src/platform-runtime-apple-tool-host.test.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,37 @@ test('Apple tool host rejects pre-aborted requests before invoking the provider'
4848
});
4949
expect(runCommand).not.toHaveBeenCalled();
5050
});
51+
52+
test('Apple tool host preserves the exact abort reason when the provider rejects differently', async () => {
53+
const reason = new Error('cancelled');
54+
const transportError = new Error('provider transport failed');
55+
const controller = new AbortController();
56+
const runCommand = vi.fn(async () => {
57+
controller.abort(reason);
58+
throw transportError;
59+
});
60+
const provider = createLocalAppleToolProvider({ runCommand });
61+
const host = createAppleToolHost();
62+
63+
await withAppleToolProvider(provider, async () => {
64+
await expect(
65+
host.run({ tool: 'xctrace', args: ['list', 'devices'] }, controller.signal),
66+
).rejects.toBe(reason);
67+
});
68+
});
69+
70+
test('Apple tool availability preserves the exact abort reason when lookup rejects differently', async () => {
71+
const reason = new Error('cancelled');
72+
const transportError = new Error('provider lookup failed');
73+
const controller = new AbortController();
74+
const whichCommand = vi.fn(async () => {
75+
controller.abort(reason);
76+
throw transportError;
77+
});
78+
const provider = createLocalAppleToolProvider({ whichCommand });
79+
const host = createAppleToolHost();
80+
81+
await withAppleToolProvider(provider, async () => {
82+
await expect(host.isXcrunAvailable(controller.signal)).rejects.toBe(reason);
83+
});
84+
});

src/platform-runtime-apple-tool-host.ts

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,30 @@ import type { AppleToolHost } from '@agent-device/contracts/platform';
33
export function createAppleToolHost(): AppleToolHost {
44
return Object.freeze({
55
isXcrunAvailable: async (signal?: AbortSignal) => {
6-
signal?.throwIfAborted();
7-
const { resolveAppleToolProvider } = await import('./platforms/apple/core/tool-provider.ts');
8-
signal?.throwIfAborted();
9-
const available = await resolveAppleToolProvider().whichCommand('xcrun');
10-
signal?.throwIfAborted();
6+
const { resolveAppleToolProvider } = await awaitPreservingAbortReason(
7+
async () => await import('./platforms/apple/core/tool-provider.ts'),
8+
signal,
9+
);
10+
const available = await awaitPreservingAbortReason(
11+
async () => await resolveAppleToolProvider().whichCommand('xcrun'),
12+
signal,
13+
);
1114
return available;
1215
},
1316
run: async (request, signal) => {
14-
signal?.throwIfAborted();
15-
const { runXcrun } = await import('./platforms/apple/core/tool-provider.ts');
16-
signal?.throwIfAborted();
17-
const result = await runXcrun([request.tool, ...request.args], {
18-
allowFailure: request.allowFailure,
17+
const { runXcrun } = await awaitPreservingAbortReason(
18+
async () => await import('./platforms/apple/core/tool-provider.ts'),
19+
signal,
20+
);
21+
const result = await awaitPreservingAbortReason(
22+
async () =>
23+
await runXcrun([request.tool, ...request.args], {
24+
allowFailure: request.allowFailure,
25+
signal,
26+
timeoutMs: request.timeoutMs,
27+
}),
1928
signal,
20-
timeoutMs: request.timeoutMs,
21-
});
22-
signal?.throwIfAborted();
29+
);
2330
return {
2431
stdout: result.stdout,
2532
stderr: result.stderr,
@@ -28,3 +35,18 @@ export function createAppleToolHost(): AppleToolHost {
2835
},
2936
});
3037
}
38+
39+
async function awaitPreservingAbortReason<T>(
40+
operation: () => Promise<T>,
41+
signal?: AbortSignal,
42+
): Promise<T> {
43+
signal?.throwIfAborted();
44+
try {
45+
const result = await operation();
46+
signal?.throwIfAborted();
47+
return result;
48+
} catch (error) {
49+
signal?.throwIfAborted();
50+
throw error;
51+
}
52+
}

0 commit comments

Comments
 (0)