Skip to content

Commit 76e5101

Browse files
committed
test(apple-runner): move journaled runner-code classification to the recovery test
The two new cases landed in runner-command-retry.test.ts, which was already over the 1,000-line test-file tripwire, so the size ratchet refused its growth. They assert runnerStatusFailureError's reading of the lifecycle journal, so their home is runner-command-recovery.test.ts, which mirrors that module and drives recovery through the real stack against a scripted fake runner.
1 parent c1e7351 commit 76e5101

2 files changed

Lines changed: 55 additions & 52 deletions

File tree

packages/platform-apple/src/runner/__tests__/runner-command-recovery.test.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,3 +149,58 @@ test('a completed read-only command without a retained response rethrows without
149149
await assert.rejects(result, (error: unknown) => error === transportError);
150150
assert.equal(invalidate.mock.calls.length, 0);
151151
});
152+
153+
/**
154+
* #2484 follow-up: the journal's code means exactly what the same code means on a live response.
155+
* `RUNNER_BUSY` is diagnostic-only — it stays `COMMAND_FAILED` and survives as
156+
* `details.runnerErrorCode` — and the retriability it carries is what a polling `wait` rides out
157+
* rather than surrendering its budget to a condition that clears on its own.
158+
*/
159+
test('a journaled RUNNER_BUSY is classified exactly like a live one', async () => {
160+
const { result, invalidate } = await runRecovery({
161+
script: [
162+
{
163+
kind: 'ok',
164+
data: {
165+
lifecycleState: 'failed',
166+
lifecycleErrorCode: 'RUNNER_BUSY',
167+
lifecycleErrorMessage: 'The iOS runner is still finishing a previous command.',
168+
lifecycleErrorHint: 'Wait a few seconds and retry.',
169+
},
170+
},
171+
],
172+
});
173+
174+
await assert.rejects(result, (error: unknown) => {
175+
assert.ok(error instanceof AppError);
176+
assert.equal(error.code, 'COMMAND_FAILED');
177+
assert.equal(error.details?.runnerErrorCode, 'RUNNER_BUSY');
178+
assert.equal(error.details?.retriable, true);
179+
assert.equal(error.details?.recovery, 'runner_reported_failure');
180+
return true;
181+
});
182+
assert.equal(invalidate.mock.calls.length, 0);
183+
});
184+
185+
test('a journaled RUNNER_WEDGED keeps its fatal code and stays unretriable', async () => {
186+
const { result } = await runRecovery({
187+
script: [
188+
{
189+
kind: 'ok',
190+
data: {
191+
lifecycleState: 'failed',
192+
lifecycleErrorCode: 'RUNNER_WEDGED',
193+
lifecycleErrorMessage: 'The iOS runner main thread has been stuck.',
194+
},
195+
},
196+
],
197+
});
198+
199+
await assert.rejects(result, (error: unknown) => {
200+
assert.ok(error instanceof AppError);
201+
assert.equal(error.code, 'RUNNER_WEDGED');
202+
assert.equal(error.details?.runnerErrorCode, 'RUNNER_WEDGED');
203+
assert.equal(error.details?.retriable, undefined);
204+
return true;
205+
});
206+
});

packages/platform-apple/src/runner/__tests__/runner-command-retry.test.ts

Lines changed: 0 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -795,58 +795,6 @@ test('mutating commands preserve runner failure details from status recovery', a
795795
});
796796
});
797797

798-
test('status recovery classifies a journaled RUNNER_BUSY exactly like a live one', async () => {
799-
const session = makeRunnerSession({ port: 8100, ready: true });
800-
801-
mockEnsureRunnerSession.mockResolvedValueOnce(session);
802-
mockExecuteRunnerCommandWithSession
803-
.mockRejectedValueOnce(new AppError('COMMAND_FAILED', 'fetch failed'))
804-
.mockResolvedValueOnce({
805-
lifecycleState: 'failed',
806-
lifecycleErrorCode: 'RUNNER_BUSY',
807-
lifecycleErrorMessage: 'The iOS runner is still finishing a previous command.',
808-
lifecycleErrorHint: 'Wait a few seconds and retry.',
809-
});
810-
811-
await assert.rejects(
812-
() => runAppleRunnerCommand(IOS_SIMULATOR, { command: 'tap', x: 120, y: 240 }),
813-
(error: unknown) => {
814-
assert.ok(error instanceof AppError);
815-
// Not RUNNER_BUSY on the wire: the code is diagnostic-only, exactly as on the live-response
816-
// path, and the retriability it carries is what a polling `wait` rides out (#2484 follow-up).
817-
assert.equal(error.code, 'COMMAND_FAILED');
818-
assert.equal(error.details?.runnerErrorCode, 'RUNNER_BUSY');
819-
assert.equal(error.details?.retriable, true);
820-
assert.equal(error.details?.recovery, 'runner_reported_failure');
821-
return true;
822-
},
823-
);
824-
});
825-
826-
test('status recovery keeps RUNNER_WEDGED fatal and unretriable', async () => {
827-
const session = makeRunnerSession({ port: 8100, ready: true });
828-
829-
mockEnsureRunnerSession.mockResolvedValueOnce(session);
830-
mockExecuteRunnerCommandWithSession
831-
.mockRejectedValueOnce(new AppError('COMMAND_FAILED', 'fetch failed'))
832-
.mockResolvedValueOnce({
833-
lifecycleState: 'failed',
834-
lifecycleErrorCode: 'RUNNER_WEDGED',
835-
lifecycleErrorMessage: 'The iOS runner main thread has been stuck.',
836-
});
837-
838-
await assert.rejects(
839-
() => runAppleRunnerCommand(IOS_SIMULATOR, { command: 'tap', x: 120, y: 240 }),
840-
(error: unknown) => {
841-
assert.ok(error instanceof AppError);
842-
assert.equal(error.code, 'RUNNER_WEDGED');
843-
assert.equal(error.details?.runnerErrorCode, 'RUNNER_WEDGED');
844-
assert.equal(error.details?.retriable, undefined);
845-
return true;
846-
},
847-
);
848-
});
849-
850798
test('mutating commands use recovery guidance when failed status has no runner hint', async () => {
851799
const session = makeRunnerSession({ port: 8100, ready: true });
852800

0 commit comments

Comments
 (0)