Files
OSGKeyboard/Scripts/cold-start-stress.sh
T
Rocky 9da32b81e9 feat(keyboard): harden clipboard command and add What's New sheet
Stabilize clipboard long-press prepare/resume across paste alerts and cold start, add an in-app release-notes sheet with remote bilingual HTML, localize typing input settings, and bump build to 55.
2026-08-08 00:20:42 +08:00

244 lines
8.3 KiB
Bash
Executable File

#!/usr/bin/env bash
# Cold-start stress on iOS Simulator.
#
# Usage:
# ./Scripts/cold-start-stress.sh [COUNT=100] [DEVICE_NAME=iPhone 17]
#
# Method: terminate → simctl launch (host cold start that auto-arms PiP).
# Keyboard URL wake (osgkeyboard://startflow) is optional once scheme is approved.
#
# Limitations (documented in summary):
# - Simulator VideoCall PiP is typically `unsupported` — not a device signal.
# - Keyboard extension process (KVC.init) is not spawned here.
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
cd "$ROOT"
COUNT="${1:-100}"
DEVICE_NAME="${2:-iPhone 17}"
BUNDLE="com.osgkeyboard.ios"
OUT_DIR="${ROOT}/.tmp/cold-start-stress-$(date +%Y%m%d-%H%M%S)"
mkdir -p "$OUT_DIR"
REPORT="$OUT_DIR/report.jsonl"
SUMMARY="$OUT_DIR/summary.txt"
LOG_FILE="$OUT_DIR/unified.log"
echo "==> Out: $OUT_DIR"
echo "==> Count: $COUNT Device: '$DEVICE_NAME'"
# Exact device name match (avoid "iPhone 17" → "iPhone 17 Pro")
UDID="$(xcrun simctl list devices available | awk -F '[()]' -v n="$DEVICE_NAME" '
{
line=$0
# strip leading spaces
sub(/^[[:space:]]+/, "", line)
name=line
sub(/ \(.*/, "", name)
if (name == n && line ~ /Booted/) { print $2; exit }
}
')"
if [[ -z "$UDID" ]]; then
UDID="$(xcrun simctl list devices available | awk -F '[()]' -v n="$DEVICE_NAME" '
{
line=$0
sub(/^[[:space:]]+/, "", line)
name=line
sub(/ \(.*/, "", name)
if (name == n && line !~ /unavailable/) { print $2; exit }
}
')"
fi
if [[ -z "$UDID" ]]; then
echo "error: exact device '$DEVICE_NAME' not found" >&2
xcrun simctl list devices available | grep -i iphone | head -40 >&2 || true
exit 1
fi
echo "==> UDID: $UDID"
echo "==> Booting simulator"
xcrun simctl boot "$UDID" 2>/dev/null || true
xcrun simctl bootstatus "$UDID" -b
# Prefer signed cold-start DerivedData build, else any existing Debug-iphonesimulator app
APP=""
if [[ -d "$ROOT/.derivedData-cold-start/Build/Products/Debug-iphonesimulator/OSGKeyboard.app" ]]; then
APP="$ROOT/.derivedData-cold-start/Build/Products/Debug-iphonesimulator/OSGKeyboard.app"
else
APP="$(ls -d "$HOME"/Library/Developer/Xcode/DerivedData/OSGKeyboard-*/Build/Products/Debug-iphonesimulator/OSGKeyboard.app 2>/dev/null | head -1 || true)"
fi
if [[ -z "$APP" || ! -d "$APP" ]]; then
echo "error: no built OSGKeyboard.app — build for simulator first" >&2
exit 1
fi
echo "==> Installing $APP"
xcrun simctl install "$UDID" "$APP" >/dev/null
xcrun simctl privacy "$UDID" grant microphone "$BUNDLE" 2>/dev/null || true
seed_onboarding() {
echo "==> Seeding onboarding (App Group + Keychain path)"
xcrun simctl launch "$UDID" "$BUNDLE" >/dev/null || true
sleep 2
xcrun simctl terminate "$UDID" "$BUNDLE" 2>/dev/null || true
sleep 0.5
local group_container prefs
group_container="$(xcrun simctl get_app_container "$UDID" "$BUNDLE" group.com.osgkeyboard.shared 2>/dev/null || true)"
if [[ -z "$group_container" || ! -d "$group_container" ]]; then
echo "warning: App Group container missing" >&2
return 0
fi
prefs="$group_container/Library/Preferences/group.com.osgkeyboard.shared.plist"
mkdir -p "$(dirname "$prefs")"
if [[ -f "$prefs" ]]; then
/usr/libexec/PlistBuddy -c "Set :config.hasCompletedOnboarding true" "$prefs" 2>/dev/null \
|| /usr/libexec/PlistBuddy -c "Add :config.hasCompletedOnboarding bool true" "$prefs" 2>/dev/null || true
else
/usr/libexec/PlistBuddy -c "Add :config.hasCompletedOnboarding bool true" "$prefs" 2>/dev/null || true
fi
echo "==> Seeded hasCompletedOnboarding=$(/usr/libexec/PlistBuddy -c 'Print :config.hasCompletedOnboarding' "$prefs" 2>/dev/null || echo missing)"
# One launch so ProviderConfig mirrors App Group → Keychain
xcrun simctl launch "$UDID" "$BUNDLE" >/dev/null || true
sleep 2
xcrun simctl terminate "$UDID" "$BUNDLE" 2>/dev/null || true
sleep 0.4
}
seed_onboarding
: >"$LOG_FILE"
xcrun simctl spawn "$UDID" log stream \
--style compact \
--level debug \
--predicate 'process == "OSGKeyboard"' \
>"$LOG_FILE" 2>&1 &
LOG_PID=$!
trap 'kill $LOG_PID 2>/dev/null || true' EXIT
sleep 1
logfile_window() {
local offset="$1"
local start=$((offset + 1))
tail -c +"$start" "$LOG_FILE" 2>/dev/null || true
}
# Returns: host|pip
# host: success|fail|unknown
# pip: success|unsupported|fail|permissions|onboarding|unknown
logfile_classify() {
local chunk="$1"
local host="unknown" pip="unknown"
# Use here-string (not pipe) to avoid SIGPIPE under `set -o pipefail`.
if grep -Eq "OSGKeyboardApp\.init" <<<"$chunk"; then
host="success"
fi
if grep -Eq "MainAppRoot\.onAppear skip Flow.*onboarding incomplete" <<<"$chunk"; then
pip="onboarding"
echo "$host|$pip"
return
fi
if grep -Eq "activateOnForeground aborted reason=permissions|startSessionAsync\.blocked.*permissions" <<<"$chunk"; then
pip="permissions"
echo "$host|$pip"
return
fi
if grep -Eq "Flow session started \(PiP keep-alive\)|low-profile PiP active|startSessionAsync\.ready" <<<"$chunk"; then
pip="success"
elif grep -Eq "failure=unsupported|PiP keep-alive failed to start: unsupported" <<<"$chunk"; then
pip="unsupported"
elif grep -Eq "PiP keep-alive failed to start|PiP startAndWait failed|startSessionAsync\.failed.*pipUnavailable" <<<"$chunk"; then
pip="fail"
elif grep -Eq "activateOnForeground|startSession\.request.*autoPiP|startSessionAsync\.begin" <<<"$chunk"; then
pip="seen_no_result"
fi
echo "$host|$pip"
}
HOST_OK=0
HOST_FAIL=0
PIP_SUCCESS=0
PIP_UNSUPPORTED=0
PIP_FAIL=0
PIP_PERMISSIONS=0
PIP_ONBOARDING=0
PIP_OTHER=0
echo "==> Running $COUNT cold starts (terminate → launch)"
for i in $(seq 1 "$COUNT"); do
xcrun simctl terminate "$UDID" "$BUNDLE" 2>/dev/null || true
sleep 0.35
OFFSET=$(wc -c <"$LOG_FILE" | tr -d ' ')
START_TS=$(date +%s)
if ! xcrun simctl launch "$UDID" "$BUNDLE" >/dev/null 2>"$OUT_DIR/launch-$i.err"; then
printf '{"i":%d,"host":"fail","pip":"unknown","elapsed_s":0,"note":"launch_failed"}\n' "$i" >>"$REPORT"
printf "[%3d/%d] host=fail pip=unknown\n" "$i" "$COUNT"
HOST_FAIL=$((HOST_FAIL + 1))
PIP_OTHER=$((PIP_OTHER + 1))
continue
fi
RESULT="unknown|unknown"
for _ in $(seq 1 50); do # ~15s
sleep 0.3
CHUNK="$(logfile_window "$OFFSET")"
RESULT="$(logfile_classify "$CHUNK")"
PIP="${RESULT##*|}"
if [[ "$PIP" == "success" || "$PIP" == "unsupported" || "$PIP" == "fail" || "$PIP" == "permissions" || "$PIP" == "onboarding" ]]; then
break
fi
done
ELAPSED=$(( $(date +%s) - START_TS ))
HOST="${RESULT%%|*}"
PIP="${RESULT##*|}"
case "$HOST" in
success) HOST_OK=$((HOST_OK + 1)) ;;
*) HOST_FAIL=$((HOST_FAIL + 1)) ;;
esac
case "$PIP" in
success) PIP_SUCCESS=$((PIP_SUCCESS + 1)) ;;
unsupported) PIP_UNSUPPORTED=$((PIP_UNSUPPORTED + 1)) ;;
fail) PIP_FAIL=$((PIP_FAIL + 1)) ;;
permissions) PIP_PERMISSIONS=$((PIP_PERMISSIONS + 1)) ;;
onboarding) PIP_ONBOARDING=$((PIP_ONBOARDING + 1)) ;;
*) PIP_OTHER=$((PIP_OTHER + 1)) ;;
esac
printf '{"i":%d,"host":"%s","pip":"%s","elapsed_s":%d}\n' "$i" "$HOST" "$PIP" "$ELAPSED" >>"$REPORT"
printf "[%3d/%d] host=%-8s pip=%-12s %2ds\n" "$i" "$COUNT" "$HOST" "$PIP" "$ELAPSED"
done
{
echo "Cold-start stress summary"
echo "device=$DEVICE_NAME udid=$UDID count=$COUNT"
echo "method=terminate + simctl launch (host cold start / autoPiP path)"
echo
echo "Host cold start (OSGKeyboardApp.init observed):"
echo " success=$HOST_OK fail=$HOST_FAIL"
echo " fail_rate=$(python3 -c "print(f'{$HOST_FAIL/$COUNT*100:.1f}%')")"
echo
echo "PiP keep-alive outcome:"
echo " success=$PIP_SUCCESS"
echo " unsupported=$PIP_UNSUPPORTED (expected on Simulator)"
echo " fail_other=$PIP_FAIL"
echo " permissions=$PIP_PERMISSIONS"
echo " onboarding=$PIP_ONBOARDING"
echo " unknown/other=$PIP_OTHER"
echo " non-unsupported fail_rate=$(python3 -c "print(f'{($PIP_FAIL+$PIP_PERMISSIONS+$PIP_ONBOARDING+$PIP_OTHER)/$COUNT*100:.1f}%')")"
echo
echo "Artifacts: $OUT_DIR"
echo "Caveats:"
echo " - Simulator PiP is typically 'unsupported'; device PiP is the release gate."
echo " - Keyboard extension KVC.init / openHostApp is not covered (needs physical keyboard enablement)."
echo " - Host wake path covered = cold launch of the process the keyboard would open."
} | tee "$SUMMARY"
kill "$LOG_PID" 2>/dev/null || true
trap - EXIT
echo "==> Done"