Repository navigation
feat(swift): iOS 示例应用与模拟器 e2e(本地)——HealthKit 授权归属应用、实例、前后台;修复监听器与通道的生命周期缺陷(#389) - #509
Merged
Merged
Conversation
…389) - swift/examples/HealthApp:Xcode 项目,SDK 作本地包;health(HealthKit)与 device 插件;业务层前台 start、进入后台 stop;连接参数取自启动参数 - tests/e2e/tests/ios_app.rs(--ignored):两台新建模拟器各自拨入 rutis-host,两个实例、各自的 Python 行用自己应用的 device;进入后台行停下、回到前台恢复,另一台不受影响;HealthKit 未决定时插件的 HealthAccessDenied(code not-determined)传到 Python 调用方;请求授权时"健康"授权页显示应用名与用途说明(截图经 Vision 读出) - tools/ios-selftest.sh:RutisSelfTest 为 iOS 模拟器构建、simctl spawn 运行 - CI:job ios(完整运行、改动示例或其测试的 PR);承诺 swift/app-access-belongs-to-the-app;指南 §8 示例与真机人工清单;设计 §17.5
iOS 模拟器上的 SDK 自测偶发 "Attempted to read an unowned reference but object was already destroyed":emit 排进监听器队列的调用在测试关闭、运行时释放之后才执行,LocalListener 的 unowned runtime 已失效。监听器改为持有运行时(撤回监听器——每次卸载都会——解开引用);会话的 dispatch / arrive 闭包改为 weak,运行时不在时以错误答复。RutisTest 的 BusEntry 与 harness 同样处理。
- LocalListener、RutisTest 的 BusEntry 对运行时 / 测试宿主改为 weak(排队的调用在它们释放后什么也不启动),会话的 dispatch / arrive 闭包为 weak; - LineChannel:关闭后(两次读取之间)receive 返回 nil、send 报错,shutdown 在锁内进行、释放在写完之后——之前关闭的通道会读走或关掉同号的新 socket,新会话收不到问候而挂住; - SelfTest 加 LifetimeTests(macOS 与 iOS 共用同一路径,runtimes-swift 的 swift run RutisSelfTest 即覆盖):旧代码分别崩溃(unowned)与挂住(描述符复用),新代码 macOS 连跑 30 次全过 - ios_app e2e:授权在应用已在前台后由 <id>.authorize 触发(iOS 27 上应用未激活时 HealthKit 授权会超时)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
#389(Swift A1:应用托管)的第二部分,接 #503:iOS 示例应用、HealthKit 插件、模拟器上的 e2e,SDK 自己的测试在 iOS 模拟器上运行(都只在本地),以及由此发现的两个 SDK 缺陷的修复与回归测试。基于 #503 的分支,#503 合入后 diff 只剩本 PR 的提交。
示例应用
swift/examples/HealthApp../../..,Info.plist 由构建设置生成:显示名、NSHealthShareUsageDescription/NSHealthUpdateUsageDescription、NSLocalNetworkUsageDescription;HealthKit entitlement)。真机只需在 Signing 里选 team。provides(开发者无感;拨入连接里没声明的服务由 fix(loader): 连进来的运行时没有声明就提供的服务,本实例里的行用不到 #506 起能被同一实例的行使用):health(HealthKit:status、authorize、stepsToday,未授权时抛HealthAccessDenied,带code)、device(id、model、system——rutis 不建模设备,由插件提供)。scenePhase为.active时start(),.background时在后台任务里stop();界面显示rutis.state;连接参数来自启动参数(-RutisConnect、-RutisID、-RutisToken、-RutisCA)。模拟器 e2e:
cargo test -p rutis-e2e --test ios_app -- --ignored(只在本地)用
xcodebuild构建示例,新建两台模拟器(sim-a、sim-b),rutis-host run带实例分组(phone:health、phone:device与一个 Python 行coach),检查:rutis: connected to … as sim-a),coach在各自实例里运行coach读到的device.id()是自己那台stop后使用者停下、start后恢复stop(),sim-a 的coach停下、sim-b 的照常;切回前台:start(),coach恢复;应用日志app: state stopped/connectedstepsToday()在 Python 调用方失败:HealthAccessDenied: step counts are not shared with this app (not-determined),name、code都传到tests/e2e/ios/text.swift);应用结束后等待中的调用结束本地运行(rebase 到 main 之后):Xcode 26.5、iOS 26.5 模拟器,
ios_app通过(约 100 秒),bash tools/ios-selftest.sh34 项全过。此前在 runner 的 iOS 27.1 模拟器上也通过过一次。iOS 27 上应用尚未激活时请求 HealthKit 授权会超时(Authorization session timed out),所以 e2e 改为应用在前台之后才触发请求。按用户的决定,需要模拟器、设备或系统授权提示的测试不在 CI 里跑:本 PR 不加 CI job,
docs/ci.md§12「本地测试」加 iOS 一行(bash tools/ios-selftest.sh、cargo test -p rutis-e2e --test ios_app -- --ignored)。模拟器上做不到的:点击授权页(
simctl privacy没有 HealthKit 服务,Xcode 26.5)。所以"拒绝"(code: denied)、"授权后返回步数"以及一切真机项写进指南 §8 的人工清单。手动在模拟器上看过授权页(中文系统):标题"访问健康数据"、"Rutis Health 想要访问并更新你的健康数据"、App 说明为NSHealthUpdateUsageDescription的原文。iOS 模拟器测试发现的 SDK 缺陷(macOS 与 iOS 共用代码,已修复)
unowned引用在释放后被读:iOS 模拟器上的自测偶发Fatal error: Attempted to read an unowned reference but object … was already destroyed。原因是排进监听器队列的调用在测试宿主或运行时释放之后才执行。LocalListener对运行时、RutisTest 的BusEntry对测试宿主改为weak:它们释放后,排队的调用什么也不启动。会话的 dispatch / arrive 闭包也改为weak,对端已不在时以错误答复。LineChannel在两次读取之间被关闭时,描述符已经释放。之后的receive仍会 poll / read 那个号码(这时它可能已是新会话的 socket),读走新会话的问候,新会话就挂住;close的shutdown也可能落在被复用的号码上。现在关闭后receive返回 nil、send报错,shutdown在锁内进行,释放等到进行中的写完成之后。LifetimeTests,在swift run RutisSelfTest里,CI 的runtimes-swift在 macOS 上跑:文档
docs/guide/swift-app.md(中英):§2 改为不声明provides;加 §8 示例应用(写明只在本地运行)与真机人工验收清单(本地网络权限的允许 / 拒绝、提示归属、拒绝与授权、后台与前台、两台设备、断网)。swift/app-access-belongs-to-the-app(部分验证:拒绝与真机为人工,测试只在本地);swift/app-start-stop加模拟器测试。docs/ci§12 加 iOS 一行。Closes #389