Skip to content

Commit 6a2cef2

Browse files
authored
Merge pull request #563 from nilsreichardt/fix-boot-issue
Fix flaky boot issues by adding a retry parameter
2 parents 759066b + 1833a2e commit 6a2cef2

6 files changed

Lines changed: 143 additions & 64 deletions

File tree

README.md

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,17 @@ or if you really need OS version, leave it on the loose side (e.g.
3636
a device with Apple Developer account, because a Simulator UDID
3737
[can not be used there](https://developer.apple.com/forums/thread/693026).
3838

39-
| Name | Sample values | Description |
40-
| -------------------- | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------- |
41-
| `model` | `iPhone 8` | Model of the device you'd like to launch |
42-
| `os` | `iOS`, `tvOS`, `watchOS` | OS type of the device |
43-
| `os_version` | `>=14.0` | OS version specification in semver format |
44-
| `udid` | `ABCD-EFGH` | Specific UDID you'd like to launch |
45-
| `erase_before_boot` | `true` | Whether the data should be erased from device before boot. Starting from a clean state helps getting a stable environment for tests |
46-
| `wait_for_boot` | `false` | Whether the action must wait for the Simulator to finish booting requested image |
47-
| `shutdown_after_job` | `true` | Whether to shutdown the launched Simulator after the workflow job has been finished |
39+
| Name | Sample values | Description |
40+
| ---------------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
41+
| `model` | `iPhone 8` | Model of the device you'd like to launch |
42+
| `os` | `iOS`, `tvOS`, `watchOS` | OS type of the device |
43+
| `os_version` | `>=14.0` | OS version specification in semver format |
44+
| `udid` | `ABCD-EFGH` | Specific UDID you'd like to launch |
45+
| `erase_before_boot` | `true` | Whether the data should be erased from device before boot. Starting from a clean state helps getting a stable environment for tests |
46+
| `wait_for_boot` | `false` | Whether the action must wait for the Simulator to finish booting requested image |
47+
| `boot_timeout_seconds` | `360` | Maximum number of seconds to wait for the Simulator to finish booting (0 disables the timeout) |
48+
| `boot_retries` | `2` | Number of times to retry booting when waiting for the Simulator to finish booting fails. Setting this to 2 will result in 3 attempts: one normal attempt and two retries. |
49+
| `shutdown_after_job` | `true` | Whether to shutdown the launched Simulator after the workflow job has been finished |
4850

4951
## Outputs
5052

action.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,18 @@ inputs:
2929
image
3030
required: false
3131
default: 'false'
32+
boot_timeout_seconds:
33+
description: >
34+
Maximum number of seconds to wait for the Simulator to finish booting. Use
35+
0 to disable the timeout.
36+
required: false
37+
default: '360'
38+
boot_retries:
39+
description: >
40+
Number of times to retry booting when waiting for the Simulator to finish
41+
booting fails.
42+
required: false
43+
default: '3'
3244
shutdown_after_job:
3345
description: >
3446
Whether the Simulator should be shut down after the job has finished. This

dist/index.js

Lines changed: 79 additions & 48 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/index.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/main.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,31 @@ async function run(): Promise<void> {
8585
await simctl('boot', device.udid)
8686

8787
if (boolean(core.getInput('wait_for_boot'))) {
88+
const bootTimeoutSeconds = Number(core.getInput('boot_timeout_seconds'))
89+
const bootRetries = Number(core.getInput('boot_retries'))
90+
91+
const bootTimeoutMs =
92+
bootTimeoutSeconds > 0 ? bootTimeoutSeconds * 1000 : undefined
93+
8894
core.info(`Waiting for device to finish booting.`)
89-
await simctl('bootstatus', device.udid)
95+
const maxAttempts = bootRetries + 1
96+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
97+
try {
98+
await simctl('bootstatus', device.udid, {timeoutMs: bootTimeoutMs})
99+
break
100+
} catch (error) {
101+
const message = error instanceof Error ? error.message : String(error)
102+
core.warning(
103+
`Bootstatus attempt ${attempt}/${maxAttempts} failed: ${message}`
104+
)
105+
if (attempt === maxAttempts) {
106+
throw error
107+
}
108+
core.warning(`Retrying simulator boot...`)
109+
await simctl('shutdown', device.udid)
110+
await simctl('boot', device.udid)
111+
}
112+
}
90113
}
91114

92115
core.setOutput('udid', device.udid)

src/xcrun.ts

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,17 +55,28 @@ export async function getDevices(): Promise<DeviceInfo[]> {
5555
return allDevices
5656
}
5757

58-
export async function simctl(action: string, udid: string): Promise<void> {
59-
await xcrun(`simctl ${action} ${udid}`)
58+
type ExecOptions = {
59+
timeoutMs?: number
6060
}
6161

62-
async function xcrun(tail: string): Promise<string> {
62+
export async function simctl(
63+
action: string,
64+
udid: string,
65+
options: ExecOptions = {}
66+
): Promise<void> {
67+
await xcrun(`simctl ${action} ${udid}`, options)
68+
}
69+
70+
async function xcrun(tail: string, options: ExecOptions = {}): Promise<string> {
6371
const command = `xcrun ${tail}`
6472
core.info(`$ ${command}`)
65-
73+
const execOptions =
74+
options.timeoutMs === undefined
75+
? {encoding: 'utf8'}
76+
: {timeout: options.timeoutMs, encoding: 'utf8'}
6677
let res: {stdout?: string; stderr?: string} | undefined
6778
try {
68-
res = await execAsync(command)
79+
res = await execAsync(command, execOptions)
6980
return res.stdout || ''
7081
} catch (e) {
7182
res = e as {stdout?: string; stderr?: string}

0 commit comments

Comments
 (0)