Skip to content

Commit c03332e

Browse files
committed
fix(ios): keep the confirming boot listing inside the startup budget
After bootstatus, the listing that confirms the Booted state ran on its own 15-second timeout and the success path never re-checked the deadline, so a bootstatus that used nearly the whole budget could still return success past it. The listing now gets the remaining budget (capped at its own 15s), and a confirmation that lands after the deadline is reported as boot_timeout.
1 parent bc1df93 commit c03332e

2 files changed

Lines changed: 42 additions & 3 deletions

File tree

packages/platform-apple/src/readiness/runtime.test.ts

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,31 @@ test('a startup deadline is one budget shared by simctl boot and bootstatus, and
170170
}
171171
});
172172

173+
test('a boot confirmed only after the deadline is a boot_timeout, and the confirming listing runs inside the budget', async () => {
174+
vi.useFakeTimers();
175+
try {
176+
const startedAtMs = 1_000_000;
177+
vi.setSystemTime(startedAtMs);
178+
const { host, calls } = coldSimulatorHost({
179+
onBoot: () => vi.setSystemTime(startedAtMs + 2_000),
180+
onBootstatus: () => vi.setSystemTime(startedAtMs + 22_000),
181+
onBootedList: () => vi.setSystemTime(startedAtMs + 31_000),
182+
});
183+
184+
await expect(
185+
ensureAppleReady(host, simulator(), new AbortController().signal, {
186+
deadlineAtMs: startedAtMs + 30_000,
187+
}),
188+
).rejects.toMatchObject({ details: { reason: 'boot_timeout', deviceId: 'sim-1' } });
189+
190+
const listings = calls.filter((call) => call.args.includes('list'));
191+
expect(listings.at(-1)?.timeoutMs).toBe(8_000);
192+
expect(calls.some((call) => call.args.includes('shutdown'))).toBe(false);
193+
} finally {
194+
vi.useRealTimers();
195+
}
196+
});
197+
173198
test('without a startup deadline the boot wait keeps its default budget', async () => {
174199
const { host, calls } = coldSimulatorHost({});
175200

@@ -193,13 +218,18 @@ test('physical readiness forwards the request signal to the focused host port',
193218
expect(ensureConnected).toHaveBeenCalledWith(expect.anything(), controller.signal);
194219
});
195220

196-
/** A Shutdown Simulator whose boot and bootstatus calls run the given hooks before succeeding. */
197-
function coldSimulatorHost(hooks: { onBoot?: () => void; onBootstatus?: () => void }) {
221+
/** A Shutdown Simulator whose boot, bootstatus, and post-boot listing run the given hooks first. */
222+
function coldSimulatorHost(hooks: {
223+
onBoot?: () => void;
224+
onBootstatus?: () => void;
225+
onBootedList?: () => void;
226+
}) {
198227
const calls: Array<{ args: string[]; timeoutMs?: number }> = [];
199228
let state = 'Shutdown';
200229
const run: PlatformRuntimeHost['appleTools']['run'] = vi.fn(async (request) => {
201230
calls.push({ args: [...request.args], timeoutMs: request.timeoutMs });
202231
if (request.args.includes('list')) {
232+
if (state === 'Booted') hooks.onBootedList?.();
203233
return {
204234
stdout: JSON.stringify({ devices: { ios: [{ udid: 'sim-1', state }] } }),
205235
stderr: '',

packages/platform-apple/src/readiness/runtime.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,9 +144,18 @@ async function waitForSimulatorBoot(
144144
exitCode: status.exitCode,
145145
});
146146
}
147-
if ((await simulatorState(host, device, signal)) !== 'Booted') {
147+
// The confirming listing runs inside the same budget, and a confirmation that lands after the
148+
// deadline is still a timeout: the caller's budget is the contract, not the boot's outcome.
149+
const state = await getSimulatorState(
150+
host.appleTools,
151+
device,
152+
signal,
153+
Math.min(LIST_TIMEOUT_MS, remainingBootBudgetMs(deadlineAtMs, device)),
154+
);
155+
if (state !== 'Booted') {
148156
throw new AppError('COMMAND_FAILED', 'Simulator is still booting', { deviceId: device.id });
149157
}
158+
if (Date.now() >= deadlineAtMs) throw bootDeadlineError(device);
150159
}
151160

152161
function remainingBootBudgetMs(deadlineAtMs: number, device: DeviceInfo): number {

0 commit comments

Comments
 (0)