ci: iOS 冒烟支持冷启动模式,修复无服务端时构建流水线失败 CI 没有 StarServer,iOS 冒烟以 --smoke 启动后必须登录并心跳成功,连接被拒即写入 ok=false,使 build-mobile 一直失败。 iOS 代理新增 --start 冷启动验证:启动代理约 3 秒后写出 agent-start.json(started/server/error),只要求实例起来,不要求连上服务端;run-ios.sh 增加 MODE=smoke|start 切换,构建流水线改用 MODE=start,与 Android 的 -Start 一致。大石头 authored at 2026-10-24 19:23:07
diff --git a/.github/workflows/build-mobile.yml b/.github/workflows/build-mobile.yml
index f19863b..282e5b3 100644
--- a/.github/workflows/build-mobile.yml
+++ b/.github/workflows/build-mobile.yml
@@ -111,11 +111,12 @@ jobs:
- name: Build iOS
run: dotnet build StarAgent.iOS/StarAgent.iOS.csproj -c Release -f net10.0-ios -p:RuntimeIdentifier=iossimulator-arm64 --nologo
- # NewLife.Core 12.0.2026.1024 已修复 Host 退出钩子注册异常,iOS 冒烟随构建一并阻塞
+ # CI 没有可用 StarServer,-Smoke 需要真实登录,改用 start 模式校验冷启动(与 Android 的 -Start 一致)
- name: Run iOS smoke
run: bash StarAgent.iOS/run-ios.sh
env:
SKIP_BUILD: "1"
+ MODE: start
- uses: actions/upload-artifact@v4
if: always()
diff --git a/StarAgent.iOS/AgentRuntime.cs b/StarAgent.iOS/AgentRuntime.cs
index 3917350..332896a 100644
--- a/StarAgent.iOS/AgentRuntime.cs
+++ b/StarAgent.iOS/AgentRuntime.cs
@@ -19,6 +19,9 @@ public static class AgentRuntime
/// <summary>是否由冒烟测试启动</summary>
public static Boolean SmokeRequested { get; private set; }
+ /// <summary>是否由冷启动验证启动。只验证代理能起来,不要求连上服务端</summary>
+ public static Boolean StartRequested { get; private set; }
+
/// <summary>设置界面日志接收器</summary>
/// <param name="sink">日志回调</param>
public static void SetLogSink(Action<String>? sink) => _logSink = sink;
@@ -40,7 +43,9 @@ public static class AgentRuntime
{
try
{
- SmokeRequested = NSProcessInfo.ProcessInfo.Arguments.Any(e => e.Equals("--smoke", StringComparison.OrdinalIgnoreCase));
+ var args = NSProcessInfo.ProcessInfo.Arguments;
+ SmokeRequested = args.Any(e => e.Equals("--smoke", StringComparison.OrdinalIgnoreCase));
+ StartRequested = args.Any(e => e.Equals("--start", StringComparison.OrdinalIgnoreCase));
if (_agent == null)
{
@@ -59,11 +64,18 @@ public static class AgentRuntime
_agent.Start();
if (SmokeRequested) _ = RunSmokeAsync();
+ else if (StartRequested) _ = RunStartAsync();
}
catch (Exception ex)
{
XTrace.WriteException(ex);
Log("启动失败:" + ex.Message);
+
+ if (StartRequested)
+ {
+ WriteResult("agent-start.json", "started", false, ex.Message);
+ Log("=== Agent start fail ===");
+ }
}
}
}
@@ -96,21 +108,35 @@ public static class AgentRuntime
}
var ok = await PingOnceAsync().ConfigureAwait(false);
- var server = _agent?.Setting.Server ?? "";
- var error = _agent?.LastError ?? "";
+ WriteResult("agent-smoke.json", "ok", ok, _agent?.LastError ?? "");
+
+ Log(ok ? "=== Agent ready ===" : "=== Agent fail ===");
+ }
+
+ /// <summary>冷启动验证。CI 没有 StarServer,只确认代理实例已启动,不要求登录成功</summary>
+ private static async Task RunStartAsync()
+ {
+ // 留出登录尝试时间,便于区分"起来了但连不上服务端"与"根本没起来"
+ await Task.Delay(3_000).ConfigureAwait(false);
+ var started = _agent != null;
+ WriteResult("agent-start.json", "started", started, _agent?.LastError ?? "");
+
+ Log(started ? "=== Agent start ===" : "=== Agent start fail ===");
+ }
+
+ private static void WriteResult(String fileName, String flagName, Boolean ok, String error)
+ {
try
{
var dir = _platform?.GetDataPath();
- if (!dir.IsNullOrEmpty())
- {
- var json = $"{{\"ok\":{(ok ? "true" : "false")},\"server\":\"{Escape(server)}\",\"error\":\"{Escape(error)}\",\"time\":\"{DateTime.Now:yyyy-MM-dd HH:mm:ss}\"}}";
- File.WriteAllText(Path.Combine(dir, "agent-smoke.json"), json);
- }
+ if (dir.IsNullOrEmpty()) return;
+
+ var server = _agent?.Setting.Server ?? "";
+ var json = $"{{\"{flagName}\":{(ok ? "true" : "false")},\"server\":\"{Escape(server)}\",\"error\":\"{Escape(error)}\",\"time\":\"{DateTime.Now:yyyy-MM-dd HH:mm:ss}\"}}";
+ File.WriteAllText(Path.Combine(dir, fileName), json);
}
catch { }
-
- Log(ok ? "=== Agent ready ===" : "=== Agent fail ===");
}
private static String Escape(String value) => (value ?? "").Replace("\\", "\\\\").Replace("\"", "\\\"");
diff --git a/StarAgent.iOS/run-ios.sh b/StarAgent.iOS/run-ios.sh
index 4c3314c..b776560 100644
--- a/StarAgent.iOS/run-ios.sh
+++ b/StarAgent.iOS/run-ios.sh
@@ -7,8 +7,29 @@ CONFIGURATION="${CONFIGURATION:-Release}"
RUNTIME="${RUNTIME:-iossimulator-arm64}"
BUNDLE_ID="com.newlife.staragent"
TIMEOUT="${TIMEOUT:-180}"
+# smoke:等待登录并心跳成功,需要可用的 StarServer;start:只验证冷启动,CI 无服务端时使用
+MODE="${MODE:-smoke}"
ARTIFACTS="$ROOT/Bin/Agent/mobile/ios/artifacts"
+case "$MODE" in
+ smoke)
+ APP_ARG="--smoke"
+ RESULT_NAME="agent-smoke.json"
+ OK_PATTERN='"ok":true'
+ PASS_LABEL="iOS smoke passed."
+ ;;
+ start)
+ APP_ARG="--start"
+ RESULT_NAME="agent-start.json"
+ OK_PATTERN='"started":true'
+ PASS_LABEL="iOS start smoke passed."
+ ;;
+ *)
+ echo "Unknown MODE: $MODE (expected smoke|start)"
+ exit 2
+ ;;
+esac
+
mkdir -p "$ARTIFACTS"
if [ "${SKIP_BUILD:-0}" != "1" ]; then
@@ -27,19 +48,19 @@ if [ -z "$UDID" ]; then
exit 1
fi
-echo "Using simulator $UDID"
+echo "Using simulator $UDID (mode=$MODE)"
xcrun simctl boot "$UDID" 2>/dev/null || true
xcrun simctl bootstatus "$UDID" -b
xcrun simctl install "$UDID" "$APP"
xcrun simctl terminate "$UDID" "$BUNDLE_ID" 2>/dev/null || true
-xcrun simctl launch "$UDID" "$BUNDLE_ID" --smoke
+xcrun simctl launch "$UDID" "$BUNDLE_ID" "$APP_ARG"
RESULT=""
DEADLINE=$((SECONDS + TIMEOUT))
while [ "$SECONDS" -lt "$DEADLINE" ]; do
DATA_DIR="$(xcrun simctl get_app_container "$UDID" "$BUNDLE_ID" data 2>/dev/null || true)"
if [ -n "$DATA_DIR" ]; then
- RESULT="$(find "$DATA_DIR" -name agent-smoke.json -print -quit 2>/dev/null || true)"
+ RESULT="$(find "$DATA_DIR" -name "$RESULT_NAME" -print -quit 2>/dev/null || true)"
if [ -n "$RESULT" ]; then
break
fi
@@ -48,18 +69,22 @@ while [ "$SECONDS" -lt "$DEADLINE" ]; do
done
if [ -z "$RESULT" ]; then
- echo "agent-smoke.json not found within ${TIMEOUT}s. Recent app log:"
+ echo "$RESULT_NAME not found within ${TIMEOUT}s. Recent app log:"
xcrun simctl spawn "$UDID" log show --last 2m --style compact --predicate 'process == "StarAgent.iOS"' || true
exit 1
fi
-cp "$RESULT" "$ARTIFACTS/ios-smoke.json"
+cp "$RESULT" "$ARTIFACTS/ios-$MODE.json"
cat "$RESULT"
if command -v jq >/dev/null 2>&1; then
- jq -e '.ok == true' "$RESULT" >/dev/null
+ if [ "$MODE" = "start" ]; then
+ jq -e '.started == true' "$RESULT" >/dev/null
+ else
+ jq -e '.ok == true' "$RESULT" >/dev/null
+ fi
else
- grep -q '"ok":true' "$RESULT"
+ grep -q "$OK_PATTERN" "$RESULT"
fi
-echo "iOS smoke passed."
+echo "$PASS_LABEL"
\ No newline at end of file