Skip to content

Commit cec1726

Browse files
committed
fix(apple): accept simctl timeout headers after diagnostics
1 parent 50034fe commit cec1726

2 files changed

Lines changed: 23 additions & 34 deletions

File tree

‎packages/platform-apple/src/core/__tests__/screenshot-simctl-protocol.test.ts‎

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,21 @@ test('the typed reason survives a changed human message and an unrelated exit co
115115
assert.equal(shouldRetryIosSimulatorScreenshot(error), true);
116116
});
117117

118+
test.each(['\n', '\r\n'])(
119+
'a PNG diagnostic before the stderr header preserves typed retry with line ending %j',
120+
async (lineEnding) => {
121+
const stderr = [
122+
'Detected file type from extension: PNG',
123+
...protocolTimeout().split('\n'),
124+
].join(lineEnding);
125+
const { error } = await captureScreenshot(() => commandFailed(stderr, 60));
126+
127+
assert.equal(details(error).stderr, stderr);
128+
assert.equal(details(error).reason, SIMCTL_SCREENSHOT_TIMEOUT_REASON);
129+
assert.equal(shouldRetryIosSimulatorScreenshot(error), true);
130+
},
131+
);
132+
118133
test('the adapter accepts a leading protocol header with CRLF line endings', async () => {
119134
const { error } = await captureScreenshot(() =>
120135
commandFailed(protocolTimeout().replaceAll('\n', '\r\n'), 60),
@@ -225,8 +240,8 @@ test('the adapter ignores a header that simctl did not print at the top level',
225240
['nested underlying-error header only', () => commandFailed(nestedOnly, 149)],
226241
['header printed on stdout instead of stderr', () => commandFailed('', 60, stdoutOnly)],
227242
[
228-
'matching header after a diagnostic preamble',
229-
() => commandFailed(`Diagnostic preamble\n${protocolTimeout()}`, 60),
243+
'indented matching header after a diagnostic preamble',
244+
() => commandFailed(`Detected file type from extension: PNG\n ${protocolTimeout()}`, 60),
230245
],
231246
[
232247
'matching header after an unrelated top-level error',

‎packages/platform-apple/src/core/tool-provider.ts‎

Lines changed: 6 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -56,50 +56,24 @@ async function runSimctlCommand(
5656
return await runCommand('xcrun', [...simctlCommand(args)], options);
5757
}
5858

59-
/**
60-
* simctl's own error-protocol header, printed once at the top of stderr ahead of the human
61-
* sentence it renders: `An error was encountered processing the command (domain=…, code=…):`.
62-
* The header line is the protocol; the text after it is CoreSimulator's prose. A nested
63-
* `Underlying error (…)` line is indented and is never this line.
64-
*/
59+
/** The first unindented simctl error header; diagnostic lines can precede it on stderr. */
6560
const SIMCTL_ERROR_HEADER =
66-
/^An error was encountered processing the command \(domain=([^,]+), code=(-?\d+)\):[\t ]*(?:\r?\n|$)/;
61+
/^An error was encountered processing the command \(domain=([^,]+), code=(-?\d+)\):[\t ]*(?:\r?\n|$)/m;
6762

68-
/**
69-
* `NSPOSIXErrorDomain` code 60 is macOS `ETIMEDOUT`: the tool's own answer that the screenshot
70-
* call timed out. This is the only place that reads it, so the verdict becomes a typed reason here
71-
* and nothing downstream re-parses the tool's text. A nested `Underlying error (…)` line is
72-
* indented and is never the header, and a code the header does not state is not this reason.
73-
*/
63+
/** macOS ETIMEDOUT in simctl's protocol, classified only by this local adapter. */
7464
const SIMCTL_SCREENSHOT_TIMEOUT_DOMAIN = 'NSPOSIXErrorDomain';
7565
const SIMCTL_SCREENSHOT_TIMEOUT_CODE = 60;
7666

77-
/**
78-
* `details.reason` this adapter stamps when simctl's screenshot call reports its own protocol
79-
* timeout. Owned beside the parse that mints it; the screenshot retry and runner-fallback decisions
80-
* read the reason and never the tool's wording.
81-
*/
67+
/** The local screenshot timeout reason consumed by retry and runner fallback. */
8268
export const SIMCTL_SCREENSHOT_TIMEOUT_REASON = 'simulator-screenshot-timeout';
8369

84-
/**
85-
* The screenshot subcommand of an otherwise-unscoped simctl argv: `io <udid> screenshot …`. The
86-
* `--set <path>` prefix is stripped first, and the flag fragments simctl's screenshot options add
87-
* (`--mask=`, `--display=`) come after the subcommand, so positions 0 and 2 are the verb pair.
88-
*/
70+
/** Screenshot options follow `io <udid> screenshot`; a simulator-set prefix precedes it. */
8971
function isSimctlScreenshotArgs(args: ScopedSimctlArgs): boolean {
9072
const unscoped = args[0] === '--set' ? args.slice(2) : args;
9173
return unscoped[0] === 'io' && unscoped[2] === 'screenshot';
9274
}
9375

94-
/**
95-
* The local simctl adapter: the one producer that runs `xcrun simctl` itself, so the one place
96-
* allowed to read the tool's error protocol. A failed screenshot call answers with the typed
97-
* {@link SIMCTL_SCREENSHOT_TIMEOUT_CODE} reason and keeps every exit-failure detail the exec layer
98-
* would have produced; other failures, and other subcommands, keep exec's own behaviour.
99-
*
100-
* `reason` is stamped, never host-kit's `timeoutMs`: that field is exec's evidence that *it* killed
101-
* a child at its deadline, and `isCommandTimeoutError` reads it as such.
102-
*/
76+
/** Stamp {@link SIMCTL_SCREENSHOT_TIMEOUT_REASON}, preserving exec's own timeout evidence. */
10377
async function runLocalSimctlCommand(
10478
args: ScopedSimctlArgs,
10579
options?: AppleToolExecOptions,

0 commit comments

Comments
 (0)