diff --git a/lib/clean/dev.sh b/lib/clean/dev.sh index 4e8f3623..cfa750fd 100644 --- a/lib/clean/dev.sh +++ b/lib/clean/dev.sh @@ -854,6 +854,8 @@ clean_dev_mobile() { local unavailable_udid="" # Check if simctl is accessible and working; timeout prevents hang when CLT-only. + # CoreSimulatorService may need >2s to warm up on cold boot, so we retry once + # with a longer timeout. See #890. local simctl_available=true local simctl_probe_ok=false if declare -F xcrun > /dev/null 2>&1; then @@ -861,8 +863,16 @@ clean_dev_mobile() { simctl_probe_ok=true fi else - if run_with_timeout 2 xcrun simctl list devices > /dev/null 2>&1; then + if run_with_timeout 5 xcrun simctl list devices > /dev/null 2>&1; then simctl_probe_ok=true + else + sleep 1 + if run_with_timeout 8 xcrun simctl list devices > /dev/null 2>&1; then + simctl_probe_ok=true + debug_log "simctl probe succeeded on retry (CoreSimulatorService warmup)" + else + debug_log "simctl probe failed after retry (5s + 8s timeouts)" + fi fi fi if [[ "$simctl_probe_ok" != "true" ]]; then diff --git a/tests/dev_extended.bats b/tests/dev_extended.bats index 5efeda26..24354bfc 100644 --- a/tests/dev_extended.bats +++ b/tests/dev_extended.bats @@ -554,3 +554,58 @@ EOF [[ "$output" == *"DEVICE_SUPPORT:iOS DeviceSupport"* ]] [[ "$output" == *"SAFE_CLEAN:Android SDK cache"* ]] } + +@test "clean_dev_mobile retries simctl probe on cold-boot timeout (#890)" { + # Exercises the timeout-retry branch (the only path the #890 fix touches). + # Strategy: + # - put a real `xcrun` shim on PATH so `command -v xcrun` succeeds AND + # `declare -F xcrun` returns false → function falls into the else branch. + # - stub `run_with_timeout` so the first probe returns 124 (timeout) and + # the second returns 0, mirroring a cold-boot CoreSimulatorService + # warmup. + # - the shim itself returns empty for the post-probe + # `xcrun simctl list devices unavailable` call so we take the + # "already clean" branch and don't try to delete anything. + local tmp_bin + tmp_bin="$(mktemp -d)" + cat > "$tmp_bin/xcrun" <<'XEOF' +#!/bin/bash +exit 0 +XEOF + chmod +x "$tmp_bin/xcrun" + + run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" TMP_XCRUN_BIN="$tmp_bin" DRY_RUN=false bash --noprofile --norc <<'EOF' +set -euo pipefail +PATH="$TMP_XCRUN_BIN:$PATH" +source "$PROJECT_ROOT/lib/core/common.sh" +source "$PROJECT_ROOT/lib/clean/dev.sh" + +check_android_ndk() { :; } +clean_xcode_documentation_cache() { :; } +clean_xcode_simulator_runtime_volumes() { :; } +clean_xcode_device_support() { :; } +safe_clean() { :; } +note_activity() { :; } +debug_log() { echo "debug: $*"; } +sleep() { :; } # skip the 1s pause between probes for fast tests + +# First call (5s timeout) simulates cold-boot warmup → return 124. +# Second call (8s timeout) succeeds. +__rwt_count=0 +run_with_timeout() { + __rwt_count=$((__rwt_count + 1)) + if [[ $__rwt_count -eq 1 ]]; then + return 124 + fi + return 0 +} + +clean_dev_mobile +EOF + + rm -rf "$tmp_bin" + + [ "$status" -eq 0 ] + [[ "$output" == *"simctl probe succeeded on retry"* ]] || return 1 + [[ "$output" != *"simctl not available"* ]] || return 1 +}