Skip to content

Commit 650356a

Browse files
committed
fix(snapshot): Handle teardown shutdown races
Treat an already-shut-down simulator as successful cleanup and align compiler-failure fixtures with the emitted result shape.
1 parent 50ffcd0 commit 650356a

4 files changed

Lines changed: 43 additions & 15 deletions

File tree

src/snapshot-tests/__fixtures__/cli/json/device/test--error-compiler.json

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,11 @@
2020
"summary": {
2121
"status": "FAILED",
2222
"durationMs": 1234,
23-
"counts": {
24-
"passed": 0,
25-
"failed": 0,
26-
"skipped": 0
27-
},
2823
"target": "device"
2924
},
3025
"artifacts": {
3126
"deviceId": "<UUID>",
32-
"buildLogPath": "~/Library/Developer/XcodeBuildMCP/workspaces/XcodeBuildMCP-<HASH>/logs/test_device_<TIMESTAMP>_pid<PID>.log",
33-
"xcresultPath": "~/Library/Developer/XcodeBuildMCP/workspaces/XcodeBuildMCP-<HASH>/result-bundles/test_device_<TIMESTAMP>_pid<PID>.xcresult"
27+
"buildLogPath": "~/Library/Developer/XcodeBuildMCP/workspaces/XcodeBuildMCP-<HASH>/logs/test_device_<TIMESTAMP>_pid<PID>.log"
3428
},
3529
"tests": {
3630
"selected": [

src/snapshot-tests/__fixtures__/mcp/json/device/test--error-compiler.json

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,11 @@
77
"summary": {
88
"status": "FAILED",
99
"durationMs": 1234,
10-
"counts": {
11-
"passed": 0,
12-
"failed": 0,
13-
"skipped": 0
14-
},
1510
"target": "device"
1611
},
1712
"artifacts": {
1813
"deviceId": "<UUID>",
19-
"buildLogPath": "~/Library/Developer/XcodeBuildMCP/workspaces/XcodeBuildMCP-<HASH>/logs/test_device_<TIMESTAMP>_pid<PID>.log",
20-
"xcresultPath": "~/Library/Developer/XcodeBuildMCP/workspaces/XcodeBuildMCP-<HASH>/result-bundles/test_device_<TIMESTAMP>_pid<PID>.xcresult"
14+
"buildLogPath": "~/Library/Developer/XcodeBuildMCP/workspaces/XcodeBuildMCP-<HASH>/logs/test_device_<TIMESTAMP>_pid<PID>.log"
2115
},
2216
"tests": {
2317
"selected": [

src/snapshot-tests/preflight/__tests__/simulator.test.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,40 @@ describe('simulator app preflight ownership', () => {
100100
]);
101101
});
102102

103+
it('tolerates a cleanup shutdown that loses a race', async () => {
104+
const cleanup = new CleanupStack();
105+
const runner = vi
106+
.fn<ExternalCommandRunner>()
107+
.mockResolvedValueOnce(
108+
commandResult(
109+
JSON.stringify({
110+
devices: { runtime: [{ udid: 'SIM-1', name: 'iPhone 17', state: 'Shutdown' }] },
111+
}),
112+
),
113+
)
114+
.mockResolvedValueOnce(commandResult())
115+
.mockResolvedValueOnce(commandResult())
116+
.mockResolvedValueOnce(commandResult('', { exitCode: 2, stderr: 'Already shutdown' }))
117+
.mockResolvedValueOnce(
118+
commandResult(
119+
JSON.stringify({
120+
devices: { runtime: [{ udid: 'SIM-1', name: 'iPhone 17', state: 'Shutdown' }] },
121+
}),
122+
),
123+
);
124+
125+
await ensureSimulatorBooted('SIM-1', cleanup, runner);
126+
await expect(cleanup.cleanup()).resolves.toBeUndefined();
127+
128+
expect(runner.mock.calls.map(([, args]) => args)).toEqual([
129+
['simctl', 'list', 'devices', 'available', '--json'],
130+
['simctl', 'boot', 'SIM-1'],
131+
['simctl', 'bootstatus', 'SIM-1', '-b'],
132+
['simctl', 'shutdown', 'SIM-1'],
133+
['simctl', 'list', 'devices', 'available', '--json'],
134+
]);
135+
});
136+
103137
it('replaces and cleans up a pre-existing fixture app', async () => {
104138
const cleanup = new CleanupStack();
105139
const runner = vi

src/snapshot-tests/preflight/simulator.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,13 @@ export async function ensureSimulatorBooted(
146146
if (options.shutdownOnCleanup !== false) {
147147
cleanup?.defer(`shut down simulator ${simulatorId}`, async () => {
148148
const result = await runner('xcrun', ['simctl', 'shutdown', simulatorId]);
149-
assertExternalCommandSucceeded(result, `Shut down simulator ${simulatorId}`);
149+
if (result.exitCode !== 0 || result.signal !== null || result.timedOut || result.spawnError) {
150+
const currentState = (await readSimulator(simulatorId, runner)).state;
151+
if (currentState === 'Shutdown') {
152+
return;
153+
}
154+
assertExternalCommandSucceeded(result, `Shut down simulator ${simulatorId}`);
155+
}
150156
await waitForSimulatorState(simulatorId, 'Shutdown', runner);
151157
});
152158
}

0 commit comments

Comments
 (0)