From 5599913deda0d173a4fcf885fea248ea825b06d7 Mon Sep 17 00:00:00 2001 From: Jintao Date: Thu, 30 Jul 2026 19:59:06 +0800 Subject: [PATCH 1/4] test: validate Quick Start UI closure --- .github/workflows/ci.yml | 11 ++ CHANGELOG.en.md | 4 +- CHANGELOG.md | 5 +- .../experimental-v0.1.0/COMPOSITION.json | 10 +- .../experimental-v0.1.0/SBOM.spdx.json | 2 +- .../Release/experimental-v0.1.0/SHA256SUMS | 4 +- Docs/Testing.md | 16 ++- Docs/en/Testing.md | 14 +- Examples/PocketRootQuickStartApp/README.md | 16 ++- .../Sources/QuickStartApp.swift | 8 ++ .../PocketRootQuickStartAppUITests.swift | 128 ++++++++++++++++++ Examples/PocketRootQuickStartApp/project.yml | 24 ++++ Scripts/generate-release-compliance.rb | 24 +++- Scripts/run-host-app-ui-smoke.sh | 109 ++------------- Scripts/run-ios-example-ui-smoke.sh | 113 ++++++++++++++++ Scripts/run-quick-start-ui-smoke.sh | 17 +++ Tests/Scripts/ReleaseComplianceTests.rb | 35 ++++- Tests/Scripts/RuntimeSmokeScriptTests.sh | 35 +++-- 18 files changed, 448 insertions(+), 127 deletions(-) create mode 100644 Examples/PocketRootQuickStartApp/UITests/PocketRootQuickStartAppUITests.swift create mode 100755 Scripts/run-ios-example-ui-smoke.sh create mode 100755 Scripts/run-quick-start-ui-smoke.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d59759a..eb46f31 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -356,6 +356,17 @@ jobs: POCKETROOT_SMOKE_TIMEOUT_SECONDS: "600" run: ./Scripts/run-runtime-smoke.sh + - name: Run Quick Start two-entry UI smoke with Xcode 16.0 + run: ./Scripts/run-quick-start-ui-smoke.sh + + - name: Run Quick Start iPad UI smoke with Xcode 16.0 + env: + POCKETROOT_QUICK_START_UI_DEVICE_TYPE: >- + com.apple.CoreSimulator.SimDeviceType.iPad-10th-generation + POCKETROOT_QUICK_START_UI_DEVICE_NAME: >- + PocketRoot-Quick-Start-iPad-UI-Smoke + run: ./Scripts/run-quick-start-ui-smoke.sh + - name: Run Host App PTY and Files UI smoke with Xcode 16.0 run: ./Scripts/run-host-app-ui-smoke.sh diff --git a/CHANGELOG.en.md b/CHANGELOG.en.md index 116815c..d3fdd3d 100644 --- a/CHANGELOG.en.md +++ b/CHANGELOG.en.md @@ -57,7 +57,9 @@ All notable PocketRoot changes are recorded here. Semantic Versioning begins wit - Direct Terminal and Files entry points on `PocketRootIshWorkspaceHost`. Screens prepare and boot automatically without consumer-side `readySystem` polling. A two-button `Examples/PocketRootQuickStartApp` is final-linked in - CI and its pinned Debug RootFS is verified. + CI and its pinned Debug RootFS is verified. iPhone/iPad UI smoke covers cold + Files and Terminal auto-boot and previews a real PTY-created file through + Files; Quick Start and Host App share one bounded Simulator runner. - Unified iOS 18 deployment baseline. - Experimental `PocketRootIshRuntime` pinned to IshEmbed release revision `38d25d6f8726145e7e988172f12000020d89a638` and the `v0.4.0-abi.6` XCFramework. - Experimental `PocketRootIshRuntime` upgraded to IshEmbed release revision diff --git a/CHANGELOG.md b/CHANGELOG.md index 2fca60a..033e846 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,7 +43,10 @@ PocketRoot 的重要变化记录在这里。首个公开版本发布后遵循 Se 并发 boot 合并,页面退出只关闭 PTY,显式 shutdown 先关闭全部 host Workspace。 - 为 `PocketRootIshWorkspaceHost` 加入独立 Terminal 与 Files 公共入口;页面自动 prepare/boot,无需业务层轮询 `readySystem`。新增只有两个按钮的 - `Examples/PocketRootQuickStartApp`,并在 CI 中最终链接和核对固定 Debug RootFS。 + `Examples/PocketRootQuickStartApp`,并在 CI 中最终链接和核对固定 Debug RootFS; + iPhone/iPad UI smoke 从冷启动分别验证 Files 与 Terminal 自动 boot,并确认真实 + PTY 创建的文件可由 Files 预览。Quick Start 与 Host App 复用同一个有界 Simulator + runner。 - 统一 package、Demo、tests 和 CI 的 iOS 18.0 deployment baseline。 - 固定 Experimental `PocketRootIshRuntime` 到 IshEmbed release revision `38d25d6f8726145e7e988172f12000020d89a638` 与 `v0.4.0-abi.6` XCFramework。 - 将 Experimental `PocketRootIshRuntime` 升级到 IshEmbed release revision diff --git a/Compliance/Release/experimental-v0.1.0/COMPOSITION.json b/Compliance/Release/experimental-v0.1.0/COMPOSITION.json index a5d384a..93114a0 100644 --- a/Compliance/Release/experimental-v0.1.0/COMPOSITION.json +++ b/Compliance/Release/experimental-v0.1.0/COMPOSITION.json @@ -14,9 +14,11 @@ "Package.swift": "3c53631753e44d6de1d0cd04cb1c725a105e65b2ba332ec19465c34048d5f339", "Examples/PocketRootDemo/project.yml": "749cfea0c9a9e7cc4919d3dbee1c01720aa85d8f1b5fe4fc887b61d3a2d0f3fa", "Examples/PocketRootHostApp/project.yml": "5d2961cd344b85b5ca0102f3e7b80c3bba7ac8681b645e3d8efae0ec137d33e4", - "Examples/PocketRootQuickStartApp/project.yml": "7fccc3adea4ece71959dee0c54fb1d83a3fc1e49d7f9f13c881c440dd0cf7fdf", + "Examples/PocketRootQuickStartApp/project.yml": "11c6f86f3be0f419a9a6f10dc884b4df9cb157bcdccb5568fe903972a6084416", "Scripts/inject-demo-rootfs.sh": "3982b5382b0d1e13e0c8e8a5bb5404c5bad1dfc4d6e9cd23a39e3395a83087bb", - "Scripts/run-host-app-ui-smoke.sh": "557baabe9721bc65c484c299356176dc8c5300594c533f61245fd620c60f741b", + "Scripts/run-host-app-ui-smoke.sh": "78d0a94afd705ce65a97e11c2bd6ae2094becc0a0c00b3e53a0cace41cd49554", + "Scripts/run-ios-example-ui-smoke.sh": "a9088ff7046992d6462558bd97ab54195cc07555951ab01ede7291058f9c1566", + "Scripts/run-quick-start-ui-smoke.sh": "acf26d68fc37250e24911c2d23791a0d12ff06a624dedcefee0ed7905bc31af2", "Scripts/run-host-app-device-ui-smoke.sh": "f9528bd72aa40e6615633d1bc56ea8a31965b41f6570a0dc9f5fa390c5082490", "ThirdPartyNotices/SwiftTerm-LICENSE.txt": "1c34c11581e20feb2b7ea122146a6690261dae94b2c8444e8cff902e567df6ae", "Sources/CPocketRootArchiveSupport/CPocketRootArchiveSupport.c": "0abfdca5894c5e888de4ba48a73880dbbe2a593785c509684b52bfa412249751", @@ -108,8 +110,10 @@ "Examples/PocketRootDemo/Sources/PocketRootDemo/Shared/UI/PlaceholderViewController.swift": "48e0f6462228a66face2aef51f9bd39ec02923c4d1c01b6b34f920948998f2b8", "Examples/PocketRootHostApp/Sources/HostApp.swift": "0c36bd3104ddc5bebb9e23675a44a92e3ce4aec2c1461b3e182d86d1a75159c9", "Examples/PocketRootHostApp/Sources/Info.plist": "711b38923f11fdb4ceb7d28cb90a51674afec0a64786a20f9bd68e3f406f8d26", + "Examples/PocketRootHostApp/UITests/PocketRootHostAppUITests.swift": "18765d8bb782f440d62493d29a12a272a90cbedbc5fd38365e42ef51f7135893", "Examples/PocketRootQuickStartApp/Sources/Info.plist": "b3c0c96e89db99c72c9f020590b1c5cdc0c927fd3af4b6ae7865abfdc81049b8", - "Examples/PocketRootQuickStartApp/Sources/QuickStartApp.swift": "25ca44394da42e62d93b563aad932c63452c4b65d478a0847d01635ff2cc6439", + "Examples/PocketRootQuickStartApp/Sources/QuickStartApp.swift": "1c77c9229281d0d1f9fbe3678bcc9995fff8c7d2189f4c6152daaa2999683fb6", + "Examples/PocketRootQuickStartApp/UITests/PocketRootQuickStartAppUITests.swift": "229a4d03d5ea3fc19ebafc78ddcc4fb596f6e7aecce3bf6a29800e0f0358c496", "Spikes/PocketRootIshRuntimeCompileSpike/PocketRootIshRuntimeCompileSpike.swift": "829a9ef79d67cb93aae5fbea32561c5c5497a7a017922bb1ebb344b4c0c32da8", "Spikes/PocketRootIshRuntimeSmoke/PocketRootIshRuntimeSmoke.swift": "e5a903fbb2f2994c45c5d57cf8df840263b5dd3a0716d8b9bbf0ad9018785af2", "Compliance/RootFS/v0.3.3/EVIDENCE.json": "98170847e01c3c738d600970ca479202e7f69bf9f3b2cef31138462d0bfc3deb", diff --git a/Compliance/Release/experimental-v0.1.0/SBOM.spdx.json b/Compliance/Release/experimental-v0.1.0/SBOM.spdx.json index a235ed9..08ff541 100644 --- a/Compliance/Release/experimental-v0.1.0/SBOM.spdx.json +++ b/Compliance/Release/experimental-v0.1.0/SBOM.spdx.json @@ -3,7 +3,7 @@ "dataLicense": "CC0-1.0", "SPDXID": "SPDXRef-DOCUMENT", "name": "PocketRoot experimental full-graph composition 0.1.0", - "documentNamespace": "https://github.com/jacklv-coder/PocketRoot/sbom/release/experimental-v0.1.0/57e45cba6a78cfce59ca4a14e50582ae9a8c722502de500f173678b36c954906", + "documentNamespace": "https://github.com/jacklv-coder/PocketRoot/sbom/release/experimental-v0.1.0/344737d54066bd65bc82cc18331866bf2b5e48b6fb6ecebee21840a2f19a4fb4", "creationInfo": { "created": "2026-07-28T00:00:00Z", "creators": [ diff --git a/Compliance/Release/experimental-v0.1.0/SHA256SUMS b/Compliance/Release/experimental-v0.1.0/SHA256SUMS index df2ef9a..551e493 100644 --- a/Compliance/Release/experimental-v0.1.0/SHA256SUMS +++ b/Compliance/Release/experimental-v0.1.0/SHA256SUMS @@ -1,3 +1,3 @@ -57e45cba6a78cfce59ca4a14e50582ae9a8c722502de500f173678b36c954906 COMPOSITION.json +344737d54066bd65bc82cc18331866bf2b5e48b6fb6ecebee21840a2f19a4fb4 COMPOSITION.json 1864fd0585b0eff50cc088d7323b5f38df958739e6ca79aba23d8e189b2fbf1c README.md -c5a965742cf96ae1840e7c703d794d5ba27caa71f40ea37ca85d94f652cd3cf4 SBOM.spdx.json +b7b9be48b743b11f12135d76cbe02258771c321e2b2db281a501a678a2720763 SBOM.spdx.json diff --git a/Docs/Testing.md b/Docs/Testing.md index eada42a..9d20c3a 100644 --- a/Docs/Testing.md +++ b/Docs/Testing.md @@ -15,6 +15,7 @@ PocketRoot 把验证分成宿主逻辑、真实 RootFS、iOS 构建、完整原 | 工程 App/archive 扫描 | `ruby Scripts/scan-release-artifact.rb` | macOS + 外部 `.app`/`.xcarchive` | 确定性文件摘要、Mach-O、签名/entitlement 风险信号与文件级 SPDX | 最终导出制品、依赖许可证完备性或分发授权 | | development-signed archive 门禁 | `./Scripts/build-signed-engineering-archive.sh` | macOS + Xcode 账号/开发签名 | 标准 `.xcarchive`、development entitlement、clean 风险信号、复验与 SPDX schema | IPA/export、发行签名、安装、上传或分发授权 | | Simulator 原生 smoke | `./Scripts/run-runtime-smoke.sh` | Apple Silicon + iOS 18 Simulator + archive | prepare、boot、命令边界和 soft shutdown 返回 | 其他工具链、真机或发行可用 | +| Quick Start UI smoke | `./Scripts/run-quick-start-ui-smoke.sh` | Apple Silicon + iOS 18 Simulator + archive | 最小业务 App 的 Files/Terminal 两个入口可从冷启动自动 boot,真实 PTY 创建的文件可由 Files 预览 | 真机、完整 Host 生命周期或发行可用 | | Host App UI smoke | `./Scripts/run-host-app-ui-smoke.sh` | Apple Silicon + iOS 18 Simulator + archive | iPhone/iPad Simulator 上的公开宿主 Boot、SwiftTerm PTY、生命周期、Workspace 会话持续性、Files 增删改/预览、系统 document picker 导入、share sheet 保存与再次导入 round-trip,以及有序 shutdown | 真机系统文件交互、真机键盘、iPad 真机或发行可用 | | Host App 真机 UI smoke | `./Scripts/run-host-app-device-ui-smoke.sh` | 支持设备 OS 的 Xcode + development-signed iPhone/iPad + archive | 同一 Host App 生命周期 UI 测试的真机执行、签名与 development entitlement | iPad、真实压力或发行可用 | | 物理设备原生 smoke | `./Scripts/run-runtime-device-smoke.sh` | 签名 iOS 18+ iPhone/iPad + archive | 同一 17 项检查、可选进程暂停/恢复、UIKit 前后台、强制重启持久化、受限存储故障或有界内存警告恢复,development entitlement 与 shutdown 返回 | 真实 storage/memory pressure、断电、jetsam、iPad 或发行可用 | @@ -511,13 +512,17 @@ RootFS。这只是兼容性证据,不授权 RootFS 分发,也不改变正式 最低工具链 job 另外固定选择 Xcode 16.0 / iOS 18.0 SDK,验证真实 RootFS install、 安装 iOS 18.0 Simulator runtime、完成 Simulator/device final-link,执行 17 项原生 -smoke,并分别在 iPhone 16 与 iPad(第 10 代)Simulator 运行 Host App 的 PTY、 -Files、Workspace,以及系统 document picker 导入、share sheet 保存、guest 删除后 -再次导入并复验内容的 UI 闭环。测试 fixture 只在显式 `-PocketRootUITesting` +smoke,并分别在 iPhone 16 与 iPad(第 10 代)Simulator 运行最小 Quick Start 的 +Files/Terminal 冷启动与 PTY-to-Files 文件闭环,以及 Host App 的 PTY、Files、 +Workspace、系统 document picker 导入、share sheet 保存、guest 删除后再次导入并 +复验内容的完整 UI 闭环。测试 fixture 只在显式 `-PocketRootUITesting` 启动参数下写入独立 Host App 示例的 Documents,不依赖用户 iCloud 或 Files 数据。可用 `POCKETROOT_HOST_UI_DEVICE_TYPE` / `POCKETROOT_HOST_UI_DEVICE_NAME` 选择 runner -创建的 Simulator;失败诊断时设置 `POCKETROOT_KEEP_UI_RESULT=1` 可保留临时 -DerivedData 与 `.xcresult`。这些 Simulator 结果不证明签名真机或发行可用。 +创建的 Host Simulator,或使用 `POCKETROOT_QUICK_START_UI_DEVICE_TYPE` / +`POCKETROOT_QUICK_START_UI_DEVICE_NAME` 选择 Quick Start Simulator;失败诊断时设置 +`POCKETROOT_KEEP_UI_RESULT=1` 可保留临时 DerivedData 与 `.xcresult`。两个 wrapper +复用 `run-ios-example-ui-smoke.sh` 的相同 RootFS、超时、xcresult 和安全清理边界。 +这些 Simulator 结果不证明签名真机或发行可用。 ## 9. 改动与最小验证矩阵 @@ -530,6 +535,7 @@ DerivedData 与 `.xcresult`。这些 Simulator 结果不证明签名真机或发 | Package.swift 或 native dependency | `swift test` + Demo build + 两个 arm64 final-link + native smoke | | `Examples/PocketRootDemo/project.yml` 或 Demo | regenerate + Demo build | | smoke App/runner | shell syntax + Simulator smoke + 可用时 signed device smoke | +| Quick Start 入口或示例 | strict iOS build + iPhone/iPad Quick Start UI smoke | | terminal/file browser | terminal tests(含二进制 stdin、原子导入、导出上限)+ strict iOS build + Demo build | | PTY/SwiftTerm | session/runtime unit + final-link + Host App UI smoke + signed iPhone/iPad lifecycle | | 文档 | `./Scripts/check-docs.sh` | diff --git a/Docs/en/Testing.md b/Docs/en/Testing.md index 2e99223..639431e 100644 --- a/Docs/en/Testing.md +++ b/Docs/en/Testing.md @@ -15,6 +15,7 @@ PocketRoot separates host logic, real RootFS, iOS build, native final-link, and | Engineering App/archive scan | `ruby Scripts/scan-release-artifact.rb` | Deterministic external `.app`/`.xcarchive` file hashes, Mach-O, signature/entitlement risk signals, and file-level SPDX | Final exported artifact, dependency-license completeness, or distribution authorization | | Development-signed archive gate | `./Scripts/build-signed-engineering-archive.sh` | Standard `.xcarchive`, development entitlements, clean risk signals, deterministic re-verification, and SPDX schema validation | IPA export, release signing, installation, upload, or distribution authorization | | Simulator native smoke | `./Scripts/run-runtime-smoke.sh` | Prepare, boot, command bounds, returning soft shutdown | Other toolchains, physical devices, distribution | +| Quick Start UI smoke | `./Scripts/run-quick-start-ui-smoke.sh` | Cold Files and Terminal entries in the minimal consumer App auto-boot; a real PTY-created file is previewed through Files | Physical devices, full Host lifecycle, distribution | | Host App UI smoke | `./Scripts/run-host-app-ui-smoke.sh` | Public-host boot, SwiftTerm PTY, lifecycle, Workspace persistence, Files mutations/previews, system document-picker import, share-sheet save and re-import round trip, and ordered shutdown on iPhone/iPad iOS 18 Simulators | Physical-device system file interaction, physical keyboards, physical iPad, distribution | | Physical Host App UI smoke | `./Scripts/run-host-app-device-ui-smoke.sh` | The same lifecycle UI test on a development-signed iPhone/iPad, including signature and development entitlements | iPad, real pressure, distribution | | Physical native smoke | `./Scripts/run-runtime-device-smoke.sh` | Same 17 checks with optional process suspend/resume, UIKit lifecycle, forced-relaunch persistence, bounded storage-failure recovery, or bounded memory-warning recovery; development entitlements and returning soft shutdown | Real storage/memory pressure, power cut, jetsam, iPad, distribution | @@ -435,7 +436,8 @@ uploads neither the App nor scan evidence. The minimum-toolchain job explicitly selects Xcode 16.0 / iOS 18.0 SDK, validates real RootFS installation, installs the iOS 18.0 Simulator runtime, final-links Simulator/device Apps, runs the 17-check native smoke, and executes -the Host App PTY, Files, Workspace, and system document-picker import, +the minimal Quick Start cold Files/Terminal and PTY-to-Files file closure plus +the full Host App PTY, Files, Workspace, system document-picker import, share-sheet save, guest deletion, re-import, and content-verification UI closure on iPhone 16 and iPad (10th generation) Simulators. The fixture is written to the standalone Host App example's Documents only under the explicit @@ -443,9 +445,12 @@ written to the standalone Host App example's Documents only under the explicit data. Set `POCKETROOT_HOST_UI_DEVICE_TYPE` and `POCKETROOT_HOST_UI_DEVICE_NAME` to select the Simulator created by the runner. -For failure diagnosis, `POCKETROOT_KEEP_UI_RESULT=1` retains temporary -DerivedData and the `.xcresult`. This Simulator evidence does not prove -signed-device or distribution readiness. +Use `POCKETROOT_QUICK_START_UI_DEVICE_TYPE` and +`POCKETROOT_QUICK_START_UI_DEVICE_NAME` for Quick Start. Both wrappers share +`run-ios-example-ui-smoke.sh` for the same RootFS, timeouts, xcresult, and safe +cleanup boundaries. For failure diagnosis, `POCKETROOT_KEEP_UI_RESULT=1` +retains temporary DerivedData and the `.xcresult`. This Simulator evidence +does not prove signed-device or distribution readiness. ## Minimum checks by change @@ -458,6 +463,7 @@ signed-device or distribution readiness. | Package/native dependency | Package + Demo + both final links + smoke | | `Examples/PocketRootDemo/project.yml` or Demo | Regenerate + Demo build | | smoke | Shell syntax + Simulator smoke + signed-device smoke when available | +| Quick Start entry/example | Strict iOS build + iPhone/iPad Quick Start UI smoke | | terminal/files UI | Terminal tests + strict iOS build + Host App UI smoke | | docs | Documentation check | | release composition/compliance evidence | Generator tests + `--check` + pinned SPDX schema validation | diff --git a/Examples/PocketRootQuickStartApp/README.md b/Examples/PocketRootQuickStartApp/README.md index 88a2eaa..3d6df72 100644 --- a/Examples/PocketRootQuickStartApp/README.md +++ b/Examples/PocketRootQuickStartApp/README.md @@ -35,6 +35,18 @@ Workspace。RootFS 仍由业务 App 作为经过审核的本地资源提供,Po open Examples/PocketRootQuickStartApp/PocketRootQuickStartApp.xcodeproj ``` +用真实 iOS 18 Simulator 验证首次接入闭环: + +```bash +POCKETROOT_ROOTFS_ARCHIVE=/absolute/path/to/fs.tar.gz \ + ./Scripts/run-quick-start-ui-smoke.sh +``` + +测试从未 boot 的 App 分别打开 Files 和 Terminal,验证两个入口都会自动准备 +RootFS/boot;Terminal 通过真实 PTY 创建文件后返回业务首页,再由 Files 打开并预览 +同一个文件。设置 `POCKETROOT_QUICK_START_UI_DEVICE_TYPE` 可选择 iPhone 或 iPad +Simulator;`POCKETROOT_KEEP_UI_RESULT=1` 会保留失败诊断制品。 + 开发 RootFS 仅注入 Debug 构建。Release、TestFlight 和 App Store 分发仍受项目中的 许可证、对应源码、SBOM 与发行审查门禁约束。 @@ -45,4 +57,6 @@ This is the smallest UIKit integration example. Retain one RootFS and Application Support directory, then push the host-managed Terminal or Files screen. Each screen automatically prepares and boots Linux when first shown. Leaving Terminal closes that screen's PTY while keeping the runtime -available for later screens. +available for later screens. Run `Scripts/run-quick-start-ui-smoke.sh` with +`POCKETROOT_ROOTFS_ARCHIVE` to validate cold Files auto-boot and the real +Terminal-to-Files file round trip on an iPhone or iPad Simulator. diff --git a/Examples/PocketRootQuickStartApp/Sources/QuickStartApp.swift b/Examples/PocketRootQuickStartApp/Sources/QuickStartApp.swift index 9465a3d..aa78192 100644 --- a/Examples/PocketRootQuickStartApp/Sources/QuickStartApp.swift +++ b/Examples/PocketRootQuickStartApp/Sources/QuickStartApp.swift @@ -68,7 +68,15 @@ final class QuickStartAppDelegate: UIResponder, UIApplicationDelegate { archiveURL: archiveURL, applicationSupportURL: applicationSupportURL, workDirectory: "/" + ), + workspaceConfiguration: PocketRootWorkspaceConfiguration( + terminalConfiguration: .interactive( + cursorBlinkEnabled: + !ProcessInfo.processInfo.arguments.contains( + "-PocketRootUITesting" + ) ) + ) ) } } diff --git a/Examples/PocketRootQuickStartApp/UITests/PocketRootQuickStartAppUITests.swift b/Examples/PocketRootQuickStartApp/UITests/PocketRootQuickStartAppUITests.swift new file mode 100644 index 0000000..7567698 --- /dev/null +++ b/Examples/PocketRootQuickStartApp/UITests/PocketRootQuickStartAppUITests.swift @@ -0,0 +1,128 @@ +import XCTest + +@MainActor +final class PocketRootQuickStartAppUITests: XCTestCase { + override func setUp() { + super.setUp() + continueAfterFailure = false + } + + func testFilesEntryAutoBootsFromColdLaunch() { + let app = launchApp() + let filesButton = app.buttons["PocketRootQuickStart.files"] + XCTAssertTrue(filesButton.waitForExistence(timeout: 10)) + XCTAssertTrue(filesButton.isEnabled) + filesButton.tap() + + let actions = app.buttons["PocketRootFiles.actions"] + XCTAssertTrue(actions.waitForExistence(timeout: 90)) + XCTAssertTrue(actions.isEnabled) + } + + func testTerminalCreatesFileThatFilesCanPreview() { + let app = launchApp() + let terminalButton = app.buttons["PocketRootQuickStart.terminal"] + XCTAssertTrue(terminalButton.waitForExistence(timeout: 10)) + XCTAssertTrue(terminalButton.isEnabled) + terminalButton.tap() + + let terminal = app.descendants(matching: .any)[ + "PocketRootTerminal.pty" + ] + XCTAssertTrue(terminal.waitForExistence(timeout: 90)) + terminal.tap() + + let suffix = String(UUID().uuidString.prefix(8)).lowercased() + let fileName = "quick-start-\(suffix).txt" + let contents = "PocketRoot Quick Start UI closure" + let marker = "__POCKETROOT_QUICK_START_CREATED__" + terminal.typeText( + "printf '\(contents)\\n' > /root/\(fileName); " + + "sync; printf '\(marker)\\n'\n" + ) + wait( + for: NSPredicate(format: "value CONTAINS %@", marker), + evaluatedWith: terminal, + timeout: 30 + ) + + dismissKeyboard(in: app) + tapBackButton(in: app) + + let filesButton = app.buttons["PocketRootQuickStart.files"] + XCTAssertTrue(filesButton.waitForExistence(timeout: 30)) + filesButton.tap() + + let file = app.descendants(matching: .any)[ + "PocketRootFiles.entry./root/\(fileName)" + ] + XCTAssertTrue(file.waitForExistence(timeout: 90)) + file.tap() + + let preview = app.staticTexts["PocketRootFiles.preview"] + XCTAssertTrue(preview.waitForExistence(timeout: 30)) + XCTAssertEqual(preview.label, "\(contents)\n") + } + + private func launchApp() -> XCUIApplication { + let app = XCUIApplication() + app.launchArguments += [ + "-AppleLanguages", "(en)", + "-AppleLocale", "en_US", + "-PocketRootUITesting" + ] + app.launch() + return app + } + + private func dismissKeyboard(in app: XCUIApplication) { + let keyboard = app.keyboards.element + for _ in 0..<3 where keyboard.exists { + let continueButton = app.buttons["Continue"] + if continueButton.exists { + continueButton.tap() + } else { + let hideKeyboardButton = app.buttons["hide keyboard"] + XCTAssertTrue( + hideKeyboardButton.waitForExistence(timeout: 10) + ) + hideKeyboardButton.tap() + } + RunLoop.current.run( + until: Date().addingTimeInterval(0.5) + ) + } + XCTAssertFalse(keyboard.exists) + } + + private func tapBackButton(in app: XCUIApplication) { + let navigationBar = app.navigationBars["Terminal"] + XCTAssertTrue(navigationBar.waitForExistence(timeout: 10)) + let backButton = navigationBar.buttons.element(boundBy: 0) + XCTAssertTrue(backButton.waitForExistence(timeout: 10)) + backButton.tap() + } + + @discardableResult + private func wait( + for predicate: NSPredicate, + evaluatedWith object: Any, + timeout: TimeInterval, + file: StaticString = #filePath, + line: UInt = #line + ) -> Bool { + let expectation = XCTNSPredicateExpectation( + predicate: predicate, + object: object + ) + let result = XCTWaiter.wait(for: [expectation], timeout: timeout) + XCTAssertEqual( + result, + .completed, + "predicate \(predicate) to become true", + file: file, + line: line + ) + return result == .completed + } +} diff --git a/Examples/PocketRootQuickStartApp/project.yml b/Examples/PocketRootQuickStartApp/project.yml index 9adc859..89c47d0 100644 --- a/Examples/PocketRootQuickStartApp/project.yml +++ b/Examples/PocketRootQuickStartApp/project.yml @@ -77,11 +77,35 @@ targets: "$SRCROOT/../../Scripts/inject-demo-rootfs.sh" basedOnDependencyAnalysis: false + PocketRootQuickStartAppUITests: + type: bundle.ui-testing + platform: iOS + deploymentTarget: "18.0" + + sources: + - path: UITests + + dependencies: + - target: PocketRootQuickStartApp + + settings: + base: + PRODUCT_BUNDLE_IDENTIFIER: com.jacklv.examples.PocketRootQuickStartAppUITests + GENERATE_INFOPLIST_FILE: YES + SWIFT_VERSION: "5.0" + SWIFT_STRICT_CONCURRENCY: complete + SWIFT_TREAT_WARNINGS_AS_ERRORS: YES + TEST_TARGET_NAME: PocketRootQuickStartApp + schemes: PocketRootQuickStartApp: build: targets: PocketRootQuickStartApp: all + PocketRootQuickStartAppUITests: [test] + test: + targets: + - PocketRootQuickStartAppUITests run: config: Debug archive: diff --git a/Scripts/generate-release-compliance.rb b/Scripts/generate-release-compliance.rb index a8747ae..25477ee 100644 --- a/Scripts/generate-release-compliance.rb +++ b/Scripts/generate-release-compliance.rb @@ -212,13 +212,17 @@ module PocketRootReleaseCompliance "Examples/PocketRootHostApp/project.yml" => "5d2961cd344b85b5ca0102f3e7b80c3bba7ac8681b645e3d8efae0ec137d33e4", "Examples/PocketRootQuickStartApp/project.yml" => - "7fccc3adea4ece71959dee0c54fb1d83a3fc1e49d7f9f13c881c440dd0cf7fdf", + "11c6f86f3be0f419a9a6f10dc884b4df9cb157bcdccb5568fe903972a6084416", "Scripts/inject-demo-rootfs.sh" => "3982b5382b0d1e13e0c8e8a5bb5404c5bad1dfc4d6e9cd23a39e3395a83087bb", "Scripts/run-host-app-device-ui-smoke.sh" => "f9528bd72aa40e6615633d1bc56ea8a31965b41f6570a0dc9f5fa390c5082490", "Scripts/run-host-app-ui-smoke.sh" => - "557baabe9721bc65c484c299356176dc8c5300594c533f61245fd620c60f741b", + "78d0a94afd705ce65a97e11c2bd6ae2094becc0a0c00b3e53a0cace41cd49554", + "Scripts/run-ios-example-ui-smoke.sh" => + "a9088ff7046992d6462558bd97ab54195cc07555951ab01ede7291058f9c1566", + "Scripts/run-quick-start-ui-smoke.sh" => + "acf26d68fc37250e24911c2d23791a0d12ff06a624dedcefee0ed7905bc31af2", "ThirdPartyNotices/SwiftTerm-LICENSE.txt" => "1c34c11581e20feb2b7ea122146a6690261dae94b2c8444e8cff902e567df6ae" }.freeze @@ -226,7 +230,9 @@ module PocketRootReleaseCompliance Sources Examples/PocketRootDemo/Sources/PocketRootDemo Examples/PocketRootHostApp/Sources + Examples/PocketRootHostApp/UITests Examples/PocketRootQuickStartApp/Sources + Examples/PocketRootQuickStartApp/UITests Spikes/PocketRootIshRuntimeCompileSpike Spikes/PocketRootIshRuntimeSmoke ].freeze @@ -970,6 +976,16 @@ def collect_inputs(root = repository_root) root.join("Scripts/run-host-app-ui-smoke.sh"), "Host App UI smoke script" ) + example_ui_smoke_bytes = + read_regular( + root.join("Scripts/run-ios-example-ui-smoke.sh"), + "Shared example UI smoke script" + ) + quick_start_ui_smoke_bytes = + read_regular( + root.join("Scripts/run-quick-start-ui-smoke.sh"), + "Quick Start UI smoke script" + ) host_app_device_ui_smoke_bytes = read_regular( root.join("Scripts/run-host-app-device-ui-smoke.sh"), @@ -1018,6 +1034,10 @@ def collect_inputs(root = repository_root) Digest::SHA256.hexdigest(demo_rootfs_injection_bytes), "Scripts/run-host-app-ui-smoke.sh" => Digest::SHA256.hexdigest(host_app_ui_smoke_bytes), + "Scripts/run-ios-example-ui-smoke.sh" => + Digest::SHA256.hexdigest(example_ui_smoke_bytes), + "Scripts/run-quick-start-ui-smoke.sh" => + Digest::SHA256.hexdigest(quick_start_ui_smoke_bytes), "Scripts/run-host-app-device-ui-smoke.sh" => Digest::SHA256.hexdigest(host_app_device_ui_smoke_bytes), SWIFTTERM.fetch("noticePath") => diff --git a/Scripts/run-host-app-ui-smoke.sh b/Scripts/run-host-app-ui-smoke.sh index 6d0403e..84a5ddb 100755 --- a/Scripts/run-host-app-ui-smoke.sh +++ b/Scripts/run-host-app-ui-smoke.sh @@ -3,102 +3,15 @@ set -euo pipefail ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd -P)" -HOST_APP_DIR="$ROOT_DIR/Examples/PocketRootHostApp" -ARCHIVE_PATH="${1:-${POCKETROOT_ROOTFS_ARCHIVE:-}}" -DEVICE_UDID="${POCKETROOT_HOST_UI_SMOKE_DEVICE:-}" -SIMULATOR_DEVICE_TYPE="${POCKETROOT_HOST_UI_DEVICE_TYPE:-com.apple.CoreSimulator.SimDeviceType.iPhone-16}" -SIMULATOR_NAME="${POCKETROOT_HOST_UI_DEVICE_NAME:-PocketRoot-Host-UI-Smoke-$$}" -CREATED_DEVICE="false" -DERIVED_DATA_ROOT="$( - mktemp -d "${TMPDIR:-/tmp}/PocketRootHostAppUISmoke.XXXXXX" -)" -RESULT_BUNDLE_PATH="$DERIVED_DATA_ROOT/PocketRootHostAppUITests.xcresult" -CLONED_SOURCE_PACKAGES_DIR="${POCKETROOT_CLONED_SOURCE_PACKAGES_DIR:-${TMPDIR:-/tmp}/PocketRootSharedSourcePackages}" -ONLY_TESTING="${POCKETROOT_HOST_UI_ONLY_TESTING:-PocketRootHostAppUITests/PocketRootHostAppUITests}" -cleanup() { - if [[ "$CREATED_DEVICE" == "true" && - "${POCKETROOT_KEEP_SIMULATOR:-0}" != "1" ]]; then - xcrun simctl shutdown "$DEVICE_UDID" >/dev/null 2>&1 || true - xcrun simctl delete "$DEVICE_UDID" >/dev/null 2>&1 || true - fi - if [[ "${POCKETROOT_KEEP_UI_RESULT:-0}" == "1" ]]; then - echo "PocketRoot Host App UI artifacts retained at $DERIVED_DATA_ROOT" - else - rm -rf "$DERIVED_DATA_ROOT" - fi -} -trap cleanup EXIT - -if [[ -z "$ARCHIVE_PATH" || ! -f "$ARCHIVE_PATH" ]]; then - echo "Usage: POCKETROOT_ROOTFS_ARCHIVE=/path/to/fs.tar.gz $0" >&2 - exit 2 -fi -if [[ "$(uname -m)" != "arm64" ]]; then - echo "The Host App UI smoke requires an Apple Silicon host." >&2 - exit 2 -fi -if ! command -v xcodegen >/dev/null 2>&1; then - echo "The Host App UI smoke requires XcodeGen." >&2 - exit 2 -fi - -if [[ -z "$DEVICE_UDID" ]]; then - RUNTIME_ID="$( - xcrun simctl list runtimes available \ - | awk -f "$ROOT_DIR/Scripts/select-ios18-simulator-runtime.awk" - )" - if [[ -z "$RUNTIME_ID" ]]; then - echo "No available iOS 18 Simulator runtime was found." >&2 - exit 2 - fi - DEVICE_UDID="$(xcrun simctl create \ - "$SIMULATOR_NAME" \ - "$SIMULATOR_DEVICE_TYPE" \ - "$RUNTIME_ID")" - CREATED_DEVICE="true" -fi -if [[ -z "$DEVICE_UDID" ]]; then - echo "No available iOS 18 Simulator was found." >&2 - exit 2 -fi - -mkdir -p "$CLONED_SOURCE_PACKAGES_DIR" -( - cd "$HOST_APP_DIR" - xcodegen generate --spec project.yml -) - -xcrun simctl boot "$DEVICE_UDID" >/dev/null 2>&1 || true -xcrun simctl bootstatus "$DEVICE_UDID" -b - -set +e -POCKETROOT_DEVELOPMENT_ROOTFS_ARCHIVE="$ARCHIVE_PATH" \ -xcodebuild \ - -quiet \ - -project "$HOST_APP_DIR/PocketRootHostApp.xcodeproj" \ - -scheme PocketRootHostApp \ - -configuration Debug \ - -destination "id=$DEVICE_UDID" \ - -derivedDataPath "$DERIVED_DATA_ROOT" \ - -resultBundlePath "$RESULT_BUNDLE_PATH" \ - -clonedSourcePackagesDirPath "$CLONED_SOURCE_PACKAGES_DIR" \ - -test-timeouts-enabled YES \ - -default-test-execution-time-allowance 300 \ - -maximum-test-execution-time-allowance 600 \ - "-only-testing:$ONLY_TESTING" \ - ARCHS=arm64 \ - ONLY_ACTIVE_ARCH=YES \ - CODE_SIGNING_ALLOWED=NO \ - test -test_exit_code=$? -set -e - -if [[ "$test_exit_code" -ne 0 ]]; then - echo "PocketRoot Host App UI smoke failed; xcresult summary follows." >&2 - xcrun xcresulttool get test-results summary \ - --path "$RESULT_BUNDLE_PATH" || true - exit "$test_exit_code" -fi - -echo "PocketRoot Host App UI smoke passed." +export POCKETROOT_UI_APP_DIR="$ROOT_DIR/Examples/PocketRootHostApp" +export POCKETROOT_UI_PROJECT_NAME="PocketRootHostApp" +export POCKETROOT_UI_SCHEME="PocketRootHostApp" +export POCKETROOT_UI_TEST_BUNDLE="PocketRootHostAppUITests" +export POCKETROOT_UI_ARTIFACT_LABEL="PocketRoot Host App" +export POCKETROOT_UI_SMOKE_DEVICE="${POCKETROOT_HOST_UI_SMOKE_DEVICE:-}" +export POCKETROOT_UI_DEVICE_TYPE="${POCKETROOT_HOST_UI_DEVICE_TYPE:-com.apple.CoreSimulator.SimDeviceType.iPhone-16}" +export POCKETROOT_UI_DEVICE_NAME="${POCKETROOT_HOST_UI_DEVICE_NAME:-PocketRoot-Host-UI-Smoke-$$}" +export POCKETROOT_UI_ONLY_TESTING="${POCKETROOT_HOST_UI_ONLY_TESTING:-PocketRootHostAppUITests/PocketRootHostAppUITests}" + +exec "$ROOT_DIR/Scripts/run-ios-example-ui-smoke.sh" "$@" diff --git a/Scripts/run-ios-example-ui-smoke.sh b/Scripts/run-ios-example-ui-smoke.sh new file mode 100755 index 0000000..1cddab0 --- /dev/null +++ b/Scripts/run-ios-example-ui-smoke.sh @@ -0,0 +1,113 @@ +#!/usr/bin/env bash + +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd -P)" +APP_DIR="${POCKETROOT_UI_APP_DIR:?POCKETROOT_UI_APP_DIR is required}" +PROJECT_NAME="${POCKETROOT_UI_PROJECT_NAME:?POCKETROOT_UI_PROJECT_NAME is required}" +SCHEME="${POCKETROOT_UI_SCHEME:?POCKETROOT_UI_SCHEME is required}" +TEST_BUNDLE="${POCKETROOT_UI_TEST_BUNDLE:?POCKETROOT_UI_TEST_BUNDLE is required}" +ARTIFACT_LABEL="${POCKETROOT_UI_ARTIFACT_LABEL:-PocketRoot Example}" +ARCHIVE_PATH="${1:-${POCKETROOT_ROOTFS_ARCHIVE:-}}" +DEVICE_UDID="${POCKETROOT_UI_SMOKE_DEVICE:-}" +SIMULATOR_DEVICE_TYPE="${POCKETROOT_UI_DEVICE_TYPE:-com.apple.CoreSimulator.SimDeviceType.iPhone-16}" +SIMULATOR_NAME="${POCKETROOT_UI_DEVICE_NAME:-PocketRoot-Example-UI-Smoke-$$}" +CREATED_DEVICE="false" +DERIVED_DATA_ROOT="$( + mktemp -d "${TMPDIR:-/tmp}/PocketRootExampleUISmoke.XXXXXX" +)" +RESULT_BUNDLE_PATH="$DERIVED_DATA_ROOT/$TEST_BUNDLE.xcresult" +CLONED_SOURCE_PACKAGES_DIR="${POCKETROOT_CLONED_SOURCE_PACKAGES_DIR:-${TMPDIR:-/tmp}/PocketRootSharedSourcePackages}" +ONLY_TESTING="${POCKETROOT_UI_ONLY_TESTING:-$TEST_BUNDLE/$TEST_BUNDLE}" + +cleanup() { + if [[ "$CREATED_DEVICE" == "true" && + "${POCKETROOT_KEEP_SIMULATOR:-0}" != "1" ]]; then + xcrun simctl shutdown "$DEVICE_UDID" >/dev/null 2>&1 || true + xcrun simctl delete "$DEVICE_UDID" >/dev/null 2>&1 || true + fi + if [[ "${POCKETROOT_KEEP_UI_RESULT:-0}" == "1" ]]; then + echo "$ARTIFACT_LABEL UI artifacts retained at $DERIVED_DATA_ROOT" + else + rm -rf "$DERIVED_DATA_ROOT" + fi +} +trap cleanup EXIT + +if [[ -z "$ARCHIVE_PATH" || ! -f "$ARCHIVE_PATH" ]]; then + echo "Usage: POCKETROOT_ROOTFS_ARCHIVE=/path/to/fs.tar.gz $0" >&2 + exit 2 +fi +if [[ ! -d "$APP_DIR" || + ! -f "$APP_DIR/project.yml" ]]; then + echo "$ARTIFACT_LABEL project inputs are unavailable at $APP_DIR." >&2 + exit 2 +fi +if [[ "$(uname -m)" != "arm64" ]]; then + echo "$ARTIFACT_LABEL UI smoke requires an Apple Silicon host." >&2 + exit 2 +fi +if ! command -v xcodegen >/dev/null 2>&1; then + echo "$ARTIFACT_LABEL UI smoke requires XcodeGen." >&2 + exit 2 +fi + +if [[ -z "$DEVICE_UDID" ]]; then + RUNTIME_ID="$( + xcrun simctl list runtimes available \ + | awk -f "$ROOT_DIR/Scripts/select-ios18-simulator-runtime.awk" + )" + if [[ -z "$RUNTIME_ID" ]]; then + echo "No available iOS 18 Simulator runtime was found." >&2 + exit 2 + fi + DEVICE_UDID="$(xcrun simctl create \ + "$SIMULATOR_NAME" \ + "$SIMULATOR_DEVICE_TYPE" \ + "$RUNTIME_ID")" + CREATED_DEVICE="true" +fi +if [[ -z "$DEVICE_UDID" ]]; then + echo "No available iOS 18 Simulator was found." >&2 + exit 2 +fi + +mkdir -p "$CLONED_SOURCE_PACKAGES_DIR" +( + cd "$APP_DIR" + xcodegen generate --spec project.yml +) + +xcrun simctl boot "$DEVICE_UDID" >/dev/null 2>&1 || true +xcrun simctl bootstatus "$DEVICE_UDID" -b + +set +e +POCKETROOT_DEVELOPMENT_ROOTFS_ARCHIVE="$ARCHIVE_PATH" \ +xcodebuild \ + -quiet \ + -project "$APP_DIR/$PROJECT_NAME.xcodeproj" \ + -scheme "$SCHEME" \ + -configuration Debug \ + -destination "id=$DEVICE_UDID" \ + -derivedDataPath "$DERIVED_DATA_ROOT" \ + -resultBundlePath "$RESULT_BUNDLE_PATH" \ + -clonedSourcePackagesDirPath "$CLONED_SOURCE_PACKAGES_DIR" \ + -test-timeouts-enabled YES \ + -default-test-execution-time-allowance 300 \ + -maximum-test-execution-time-allowance 600 \ + "-only-testing:$ONLY_TESTING" \ + ARCHS=arm64 \ + ONLY_ACTIVE_ARCH=YES \ + CODE_SIGNING_ALLOWED=NO \ + test +test_exit_code=$? +set -e + +if [[ "$test_exit_code" -ne 0 ]]; then + echo "$ARTIFACT_LABEL UI smoke failed; xcresult summary follows." >&2 + xcrun xcresulttool get test-results summary \ + --path "$RESULT_BUNDLE_PATH" || true + exit "$test_exit_code" +fi + +echo "$ARTIFACT_LABEL UI smoke passed." diff --git a/Scripts/run-quick-start-ui-smoke.sh b/Scripts/run-quick-start-ui-smoke.sh new file mode 100755 index 0000000..d326461 --- /dev/null +++ b/Scripts/run-quick-start-ui-smoke.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd -P)" + +export POCKETROOT_UI_APP_DIR="$ROOT_DIR/Examples/PocketRootQuickStartApp" +export POCKETROOT_UI_PROJECT_NAME="PocketRootQuickStartApp" +export POCKETROOT_UI_SCHEME="PocketRootQuickStartApp" +export POCKETROOT_UI_TEST_BUNDLE="PocketRootQuickStartAppUITests" +export POCKETROOT_UI_ARTIFACT_LABEL="PocketRoot Quick Start" +export POCKETROOT_UI_SMOKE_DEVICE="${POCKETROOT_QUICK_START_UI_SMOKE_DEVICE:-}" +export POCKETROOT_UI_DEVICE_TYPE="${POCKETROOT_QUICK_START_UI_DEVICE_TYPE:-com.apple.CoreSimulator.SimDeviceType.iPhone-16}" +export POCKETROOT_UI_DEVICE_NAME="${POCKETROOT_QUICK_START_UI_DEVICE_NAME:-PocketRoot-Quick-Start-UI-Smoke-$$}" +export POCKETROOT_UI_ONLY_TESTING="${POCKETROOT_QUICK_START_UI_ONLY_TESTING:-PocketRootQuickStartAppUITests/PocketRootQuickStartAppUITests}" + +exec "$ROOT_DIR/Scripts/run-ios-example-ui-smoke.sh" "$@" diff --git a/Tests/Scripts/ReleaseComplianceTests.rb b/Tests/Scripts/ReleaseComplianceTests.rb index b4229c5..f81e63f 100644 --- a/Tests/Scripts/ReleaseComplianceTests.rb +++ b/Tests/Scripts/ReleaseComplianceTests.rb @@ -19,6 +19,8 @@ class ReleaseComplianceTests < Minitest::Test "Scripts/inject-demo-rootfs.sh", "Scripts/run-host-app-device-ui-smoke.sh", "Scripts/run-host-app-ui-smoke.sh", + "Scripts/run-ios-example-ui-smoke.sh", + "Scripts/run-quick-start-ui-smoke.sh", "ThirdPartyNotices/SwiftTerm-LICENSE.txt", "Compliance/RootFS/v0.3.3/EVIDENCE.json", "Compliance/RootFS/v0.3.3/SBOM.spdx.json" @@ -187,6 +189,32 @@ def test_quick_start_profile_uses_two_public_host_entries assert_includes source, "host.makeFilesViewController()" assert_includes source, "func sceneDidDisconnect(_ scene: UIScene)" assert_includes source, "pocketRootHost?.closeWorkspaces()" + + project = + REPOSITORY_ROOT + .join("Examples/PocketRootQuickStartApp/project.yml") + .binread + ui_test = + REPOSITORY_ROOT + .join( + "Examples/PocketRootQuickStartApp/UITests/" \ + "PocketRootQuickStartAppUITests.swift" + ) + .binread + runner = + REPOSITORY_ROOT + .join("Scripts/run-quick-start-ui-smoke.sh") + .binread + workflow = REPOSITORY_ROOT.join(".github/workflows/ci.yml").binread + + assert_includes project, "PocketRootQuickStartAppUITests:" + assert_includes project, "type: bundle.ui-testing" + assert_includes ui_test, "testFilesEntryAutoBootsFromColdLaunch" + assert_includes ui_test, "testTerminalCreatesFileThatFilesCanPreview" + assert_includes ui_test, "PocketRootTerminal.pty" + assert_includes ui_test, "PocketRootFiles.preview" + assert_includes runner, "run-ios-example-ui-smoke.sh" + assert_includes workflow, "./Scripts/run-quick-start-ui-smoke.sh" end def test_standalone_host_retains_runtime_across_scene_recreation @@ -274,6 +302,10 @@ def test_standalone_host_has_real_pty_and_files_ui_smoke REPOSITORY_ROOT .join("Scripts/run-host-app-ui-smoke.sh") .binread + generic_runner = + REPOSITORY_ROOT + .join("Scripts/run-ios-example-ui-smoke.sh") + .binread device_runner = REPOSITORY_ROOT .join("Scripts/run-host-app-device-ui-smoke.sh") @@ -293,7 +325,8 @@ def test_standalone_host_has_real_pty_and_files_ui_smoke assert_includes host_source, "PocketRootIshWorkspaceHost(" assert_includes host_source, "workspaceHost.makeViewController()" assert_includes ui_test, "PocketRootHost.shutdown" - assert_includes runner, "-test-timeouts-enabled YES" + assert_includes runner, "run-ios-example-ui-smoke.sh" + assert_includes generic_runner, "-test-timeouts-enabled YES" assert_includes device_runner, "build-for-testing" assert_includes device_runner, "test-without-building" assert_includes device_runner, "result.deviceProperties.osVersionNumber" diff --git a/Tests/Scripts/RuntimeSmokeScriptTests.sh b/Tests/Scripts/RuntimeSmokeScriptTests.sh index 73d0b02..6a3172b 100755 --- a/Tests/Scripts/RuntimeSmokeScriptTests.sh +++ b/Tests/Scripts/RuntimeSmokeScriptTests.sh @@ -8,10 +8,13 @@ SIMULATOR_RUNNER="$ROOT_DIR/Scripts/run-runtime-smoke.sh" DEVICE_RUNNER="$ROOT_DIR/Scripts/run-runtime-device-smoke.sh" HOST_UI_RUNNER="$ROOT_DIR/Scripts/run-host-app-ui-smoke.sh" HOST_DEVICE_UI_RUNNER="$ROOT_DIR/Scripts/run-host-app-device-ui-smoke.sh" +GENERIC_UI_RUNNER="$ROOT_DIR/Scripts/run-ios-example-ui-smoke.sh" +QUICK_START_UI_RUNNER="$ROOT_DIR/Scripts/run-quick-start-ui-smoke.sh" HOST_APP_SOURCE="$ROOT_DIR/Examples/PocketRootHostApp/Sources/HostApp.swift" HOST_UI_TESTS="$ROOT_DIR/Examples/PocketRootHostApp/UITests/PocketRootHostAppUITests.swift" HOST_PROJECT_SPEC="$ROOT_DIR/Examples/PocketRootHostApp/project.yml" QUICK_START_SOURCE="$ROOT_DIR/Examples/PocketRootQuickStartApp/Sources/QuickStartApp.swift" +QUICK_START_UI_TESTS="$ROOT_DIR/Examples/PocketRootQuickStartApp/UITests/PocketRootQuickStartAppUITests.swift" QUICK_START_PROJECT_SPEC="$ROOT_DIR/Examples/PocketRootQuickStartApp/project.yml" PROJECT_SPEC="$ROOT_DIR/Examples/PocketRootDemo/project.yml" SMOKE_APP="$ROOT_DIR/Spikes/PocketRootIshRuntimeSmoke/PocketRootIshRuntimeSmoke.swift" @@ -47,17 +50,28 @@ bash -n "$SIMULATOR_RUNNER" bash -n "$DEVICE_RUNNER" bash -n "$HOST_UI_RUNNER" bash -n "$HOST_DEVICE_UI_RUNNER" +bash -n "$GENERIC_UI_RUNNER" +bash -n "$QUICK_START_UI_RUNNER" if ! grep -Fq -- 'POCKETROOT_HOST_UI_SMOKE_DEVICE' "$HOST_UI_RUNNER" \ || ! grep -Fq -- 'POCKETROOT_HOST_UI_DEVICE_TYPE' "$HOST_UI_RUNNER" \ - || ! grep -Fq -- 'POCKETROOT_KEEP_UI_RESULT' "$HOST_UI_RUNNER" \ || ! grep -Fq -- 'PocketRootHostAppUITests' "$HOST_UI_RUNNER" \ - || ! grep -Fq -- 'POCKETROOT_DEVELOPMENT_ROOTFS_ARCHIVE="$ARCHIVE_PATH"' "$HOST_UI_RUNNER" \ - || ! grep -Fq -- '-test-timeouts-enabled YES' "$HOST_UI_RUNNER" \ - || ! grep -Fq -- '-default-test-execution-time-allowance 300' "$HOST_UI_RUNNER" \ - || ! grep -Fq -- '-maximum-test-execution-time-allowance 600' "$HOST_UI_RUNNER" \ - || ! grep -Fq -- 'xcrun simctl delete "$DEVICE_UDID"' "$HOST_UI_RUNNER"; then - echo "Host App UI smoke runner is missing deterministic inputs or cleanup." >&2 + || ! grep -Fq -- 'run-ios-example-ui-smoke.sh' "$HOST_UI_RUNNER" \ + || ! grep -Fq -- 'POCKETROOT_QUICK_START_UI_SMOKE_DEVICE' "$QUICK_START_UI_RUNNER" \ + || ! grep -Fq -- 'POCKETROOT_QUICK_START_UI_DEVICE_TYPE' "$QUICK_START_UI_RUNNER" \ + || ! grep -Fq -- 'PocketRootQuickStartAppUITests' "$QUICK_START_UI_RUNNER" \ + || ! grep -Fq -- 'run-ios-example-ui-smoke.sh' "$QUICK_START_UI_RUNNER"; then + echo "Example UI wrappers are missing deterministic target mappings." >&2 + exit 1 +fi + +if ! grep -Fq -- 'POCKETROOT_KEEP_UI_RESULT' "$GENERIC_UI_RUNNER" \ + || ! grep -Fq -- 'POCKETROOT_DEVELOPMENT_ROOTFS_ARCHIVE="$ARCHIVE_PATH"' "$GENERIC_UI_RUNNER" \ + || ! grep -Fq -- '-test-timeouts-enabled YES' "$GENERIC_UI_RUNNER" \ + || ! grep -Fq -- '-default-test-execution-time-allowance 300' "$GENERIC_UI_RUNNER" \ + || ! grep -Fq -- '-maximum-test-execution-time-allowance 600' "$GENERIC_UI_RUNNER" \ + || ! grep -Fq -- 'xcrun simctl delete "$DEVICE_UDID"' "$GENERIC_UI_RUNNER"; then + echo "Shared example UI runner is missing bounded execution or cleanup." >&2 exit 1 fi @@ -74,10 +88,15 @@ if ! grep -Fq -- 'makeTerminalViewController()' "$QUICK_START_SOURCE" \ || ! grep -Fq -- 'makeFilesViewController()' "$QUICK_START_SOURCE" \ || ! grep -Fq -- 'func sceneDidDisconnect(_ scene: UIScene)' "$QUICK_START_SOURCE" \ || ! grep -Fq -- 'pocketRootHost?.closeWorkspaces()' "$QUICK_START_SOURCE" \ + || ! grep -Fq -- 'testFilesEntryAutoBootsFromColdLaunch' "$QUICK_START_UI_TESTS" \ + || ! grep -Fq -- 'testTerminalCreatesFileThatFilesCanPreview' "$QUICK_START_UI_TESTS" \ + || ! grep -Fq -- 'PocketRootTerminal.pty' "$QUICK_START_UI_TESTS" \ + || ! grep -Fq -- 'PocketRootFiles.preview' "$QUICK_START_UI_TESTS" \ + || ! grep -Fq -- 'PocketRootQuickStartAppUITests:' "$QUICK_START_PROJECT_SPEC" \ || ! grep -Fq -- 'path: ../..' "$QUICK_START_PROJECT_SPEC" \ || ! grep -Fq -- 'product: PocketRootIshRuntimeIntegration' "$QUICK_START_PROJECT_SPEC" \ || ! grep -Fq -- '"$SRCROOT/../../Scripts/inject-demo-rootfs.sh"' "$QUICK_START_PROJECT_SPEC"; then - echo "Quick Start App is missing its two public entry points or package boundary." >&2 + echo "Quick Start App is missing its two-entry UI closure or package boundary." >&2 exit 1 fi From 0861f74582e8295bf5664d7498928edf19abb959 Mon Sep 17 00:00:00 2001 From: Jintao Date: Thu, 30 Jul 2026 20:58:02 +0800 Subject: [PATCH 2/4] test: stabilize Host iPad file actions --- .../experimental-v0.1.0/COMPOSITION.json | 2 +- .../experimental-v0.1.0/SBOM.spdx.json | 2 +- .../Release/experimental-v0.1.0/SHA256SUMS | 4 +-- .../UITests/PocketRootHostAppUITests.swift | 32 +++++++++++++++---- 4 files changed, 30 insertions(+), 10 deletions(-) diff --git a/Compliance/Release/experimental-v0.1.0/COMPOSITION.json b/Compliance/Release/experimental-v0.1.0/COMPOSITION.json index 93114a0..ea05b38 100644 --- a/Compliance/Release/experimental-v0.1.0/COMPOSITION.json +++ b/Compliance/Release/experimental-v0.1.0/COMPOSITION.json @@ -110,7 +110,7 @@ "Examples/PocketRootDemo/Sources/PocketRootDemo/Shared/UI/PlaceholderViewController.swift": "48e0f6462228a66face2aef51f9bd39ec02923c4d1c01b6b34f920948998f2b8", "Examples/PocketRootHostApp/Sources/HostApp.swift": "0c36bd3104ddc5bebb9e23675a44a92e3ce4aec2c1461b3e182d86d1a75159c9", "Examples/PocketRootHostApp/Sources/Info.plist": "711b38923f11fdb4ceb7d28cb90a51674afec0a64786a20f9bd68e3f406f8d26", - "Examples/PocketRootHostApp/UITests/PocketRootHostAppUITests.swift": "18765d8bb782f440d62493d29a12a272a90cbedbc5fd38365e42ef51f7135893", + "Examples/PocketRootHostApp/UITests/PocketRootHostAppUITests.swift": "1ef4f857d0cd44887157a82d9875db9a43be711ee6b86cdc7df811486519fdfe", "Examples/PocketRootQuickStartApp/Sources/Info.plist": "b3c0c96e89db99c72c9f020590b1c5cdc0c927fd3af4b6ae7865abfdc81049b8", "Examples/PocketRootQuickStartApp/Sources/QuickStartApp.swift": "1c77c9229281d0d1f9fbe3678bcc9995fff8c7d2189f4c6152daaa2999683fb6", "Examples/PocketRootQuickStartApp/UITests/PocketRootQuickStartAppUITests.swift": "229a4d03d5ea3fc19ebafc78ddcc4fb596f6e7aecce3bf6a29800e0f0358c496", diff --git a/Compliance/Release/experimental-v0.1.0/SBOM.spdx.json b/Compliance/Release/experimental-v0.1.0/SBOM.spdx.json index 08ff541..03da821 100644 --- a/Compliance/Release/experimental-v0.1.0/SBOM.spdx.json +++ b/Compliance/Release/experimental-v0.1.0/SBOM.spdx.json @@ -3,7 +3,7 @@ "dataLicense": "CC0-1.0", "SPDXID": "SPDXRef-DOCUMENT", "name": "PocketRoot experimental full-graph composition 0.1.0", - "documentNamespace": "https://github.com/jacklv-coder/PocketRoot/sbom/release/experimental-v0.1.0/344737d54066bd65bc82cc18331866bf2b5e48b6fb6ecebee21840a2f19a4fb4", + "documentNamespace": "https://github.com/jacklv-coder/PocketRoot/sbom/release/experimental-v0.1.0/34e4c92fe45f34b5fb75fe3a7e78efeb6a961564c94763386fbbbc7299cd83ef", "creationInfo": { "created": "2026-07-28T00:00:00Z", "creators": [ diff --git a/Compliance/Release/experimental-v0.1.0/SHA256SUMS b/Compliance/Release/experimental-v0.1.0/SHA256SUMS index 551e493..70c36d2 100644 --- a/Compliance/Release/experimental-v0.1.0/SHA256SUMS +++ b/Compliance/Release/experimental-v0.1.0/SHA256SUMS @@ -1,3 +1,3 @@ -344737d54066bd65bc82cc18331866bf2b5e48b6fb6ecebee21840a2f19a4fb4 COMPOSITION.json +34e4c92fe45f34b5fb75fe3a7e78efeb6a961564c94763386fbbbc7299cd83ef COMPOSITION.json 1864fd0585b0eff50cc088d7323b5f38df958739e6ca79aba23d8e189b2fbf1c README.md -b7b9be48b743b11f12135d76cbe02258771c321e2b2db281a501a678a2720763 SBOM.spdx.json +2e5e1c17d59b87602d017ee2767928fdc698f86810204a4e9322aec5c4963a8c SBOM.spdx.json diff --git a/Examples/PocketRootHostApp/UITests/PocketRootHostAppUITests.swift b/Examples/PocketRootHostApp/UITests/PocketRootHostAppUITests.swift index 095aef2..f2ce85e 100644 --- a/Examples/PocketRootHostApp/UITests/PocketRootHostAppUITests.swift +++ b/Examples/PocketRootHostApp/UITests/PocketRootHostAppUITests.swift @@ -174,14 +174,13 @@ final class PocketRootHostAppUITests: XCTestCase { let app = launchAndBoot() app.buttons["PocketRootHost.files"].tap() - let actions = app.buttons["PocketRootFiles.actions"] - XCTAssertTrue(actions.waitForExistence(timeout: 30)) - waitForEnabled(actions) deleteGuestFileIfPresent( named: Self.systemImportFixtureName, in: app ) - actions.tap() + guard openFileActionsMenu(in: app) else { + return + } let importFile = app.buttons["Import File"] XCTAssertTrue(importFile.waitForExistence(timeout: 10)) guard waitForHittable(importFile) else { @@ -267,8 +266,9 @@ final class PocketRootHostAppUITests: XCTestCase { timeout: 30 ) - waitForEnabled(actions) - actions.tap() + guard openFileActionsMenu(in: app) else { + return + } XCTAssertTrue(importFile.waitForExistence(timeout: 10)) guard waitForHittable(importFile) else { return @@ -942,6 +942,26 @@ final class PocketRootHostAppUITests: XCTestCase { ) } + @discardableResult + private func openFileActionsMenu( + in app: XCUIApplication + ) -> Bool { + // System document and share controllers can finish dismissing after + // the host Files screen is visible. Re-query the button and wait for + // a valid activation point before tapping, especially on iPad. + let actions = app.buttons["PocketRootFiles.actions"] + guard actions.waitForExistence(timeout: 30) else { + XCTFail("file actions button to exist") + return false + } + waitForEnabled(actions) + guard waitForHittable(actions) else { + return false + } + actions.tap() + return true + } + @discardableResult private func waitForHittable( _ element: XCUIElement, From c066591bc8eea7887d2aa8ddfa31b9fb5067afb3 Mon Sep 17 00:00:00 2001 From: Jintao Date: Thu, 30 Jul 2026 21:47:14 +0800 Subject: [PATCH 3/4] test: bound Host UI execution allowance --- .../Release/experimental-v0.1.0/COMPOSITION.json | 6 +++--- .../Release/experimental-v0.1.0/SBOM.spdx.json | 2 +- Compliance/Release/experimental-v0.1.0/SHA256SUMS | 4 ++-- .../UITests/PocketRootQuickStartAppUITests.swift | 6 +++++- Scripts/generate-release-compliance.rb | 4 ++-- Scripts/run-host-app-ui-smoke.sh | 2 ++ Scripts/run-ios-example-ui-smoke.sh | 12 ++++++++++-- Tests/Scripts/RuntimeSmokeScriptTests.sh | 5 +++-- 8 files changed, 28 insertions(+), 13 deletions(-) diff --git a/Compliance/Release/experimental-v0.1.0/COMPOSITION.json b/Compliance/Release/experimental-v0.1.0/COMPOSITION.json index ea05b38..328f90e 100644 --- a/Compliance/Release/experimental-v0.1.0/COMPOSITION.json +++ b/Compliance/Release/experimental-v0.1.0/COMPOSITION.json @@ -16,8 +16,8 @@ "Examples/PocketRootHostApp/project.yml": "5d2961cd344b85b5ca0102f3e7b80c3bba7ac8681b645e3d8efae0ec137d33e4", "Examples/PocketRootQuickStartApp/project.yml": "11c6f86f3be0f419a9a6f10dc884b4df9cb157bcdccb5568fe903972a6084416", "Scripts/inject-demo-rootfs.sh": "3982b5382b0d1e13e0c8e8a5bb5404c5bad1dfc4d6e9cd23a39e3395a83087bb", - "Scripts/run-host-app-ui-smoke.sh": "78d0a94afd705ce65a97e11c2bd6ae2094becc0a0c00b3e53a0cace41cd49554", - "Scripts/run-ios-example-ui-smoke.sh": "a9088ff7046992d6462558bd97ab54195cc07555951ab01ede7291058f9c1566", + "Scripts/run-host-app-ui-smoke.sh": "811c58bd540512d35247970b994c54b28212145228fb40011c04456216052733", + "Scripts/run-ios-example-ui-smoke.sh": "d401c53b2bfb731713a9d3a894c306ce0f406e02810b49bf4a6ddd9c0a280f23", "Scripts/run-quick-start-ui-smoke.sh": "acf26d68fc37250e24911c2d23791a0d12ff06a624dedcefee0ed7905bc31af2", "Scripts/run-host-app-device-ui-smoke.sh": "f9528bd72aa40e6615633d1bc56ea8a31965b41f6570a0dc9f5fa390c5082490", "ThirdPartyNotices/SwiftTerm-LICENSE.txt": "1c34c11581e20feb2b7ea122146a6690261dae94b2c8444e8cff902e567df6ae", @@ -113,7 +113,7 @@ "Examples/PocketRootHostApp/UITests/PocketRootHostAppUITests.swift": "1ef4f857d0cd44887157a82d9875db9a43be711ee6b86cdc7df811486519fdfe", "Examples/PocketRootQuickStartApp/Sources/Info.plist": "b3c0c96e89db99c72c9f020590b1c5cdc0c927fd3af4b6ae7865abfdc81049b8", "Examples/PocketRootQuickStartApp/Sources/QuickStartApp.swift": "1c77c9229281d0d1f9fbe3678bcc9995fff8c7d2189f4c6152daaa2999683fb6", - "Examples/PocketRootQuickStartApp/UITests/PocketRootQuickStartAppUITests.swift": "229a4d03d5ea3fc19ebafc78ddcc4fb596f6e7aecce3bf6a29800e0f0358c496", + "Examples/PocketRootQuickStartApp/UITests/PocketRootQuickStartAppUITests.swift": "17db21ce0e23489ea85b19cef285746f33e1b6f8f6efedda0599d1934a8766ba", "Spikes/PocketRootIshRuntimeCompileSpike/PocketRootIshRuntimeCompileSpike.swift": "829a9ef79d67cb93aae5fbea32561c5c5497a7a017922bb1ebb344b4c0c32da8", "Spikes/PocketRootIshRuntimeSmoke/PocketRootIshRuntimeSmoke.swift": "e5a903fbb2f2994c45c5d57cf8df840263b5dd3a0716d8b9bbf0ad9018785af2", "Compliance/RootFS/v0.3.3/EVIDENCE.json": "98170847e01c3c738d600970ca479202e7f69bf9f3b2cef31138462d0bfc3deb", diff --git a/Compliance/Release/experimental-v0.1.0/SBOM.spdx.json b/Compliance/Release/experimental-v0.1.0/SBOM.spdx.json index 03da821..e1f403f 100644 --- a/Compliance/Release/experimental-v0.1.0/SBOM.spdx.json +++ b/Compliance/Release/experimental-v0.1.0/SBOM.spdx.json @@ -3,7 +3,7 @@ "dataLicense": "CC0-1.0", "SPDXID": "SPDXRef-DOCUMENT", "name": "PocketRoot experimental full-graph composition 0.1.0", - "documentNamespace": "https://github.com/jacklv-coder/PocketRoot/sbom/release/experimental-v0.1.0/34e4c92fe45f34b5fb75fe3a7e78efeb6a961564c94763386fbbbc7299cd83ef", + "documentNamespace": "https://github.com/jacklv-coder/PocketRoot/sbom/release/experimental-v0.1.0/f23a5237b7a47d4247007e6fc5ee41d669cede57f9488434228b503d54b1797b", "creationInfo": { "created": "2026-07-28T00:00:00Z", "creators": [ diff --git a/Compliance/Release/experimental-v0.1.0/SHA256SUMS b/Compliance/Release/experimental-v0.1.0/SHA256SUMS index 70c36d2..24108f0 100644 --- a/Compliance/Release/experimental-v0.1.0/SHA256SUMS +++ b/Compliance/Release/experimental-v0.1.0/SHA256SUMS @@ -1,3 +1,3 @@ -34e4c92fe45f34b5fb75fe3a7e78efeb6a961564c94763386fbbbc7299cd83ef COMPOSITION.json +f23a5237b7a47d4247007e6fc5ee41d669cede57f9488434228b503d54b1797b COMPOSITION.json 1864fd0585b0eff50cc088d7323b5f38df958739e6ca79aba23d8e189b2fbf1c README.md -2e5e1c17d59b87602d017ee2767928fdc698f86810204a4e9322aec5c4963a8c SBOM.spdx.json +be9d0af697b34dab6f9bda60f91bf9407bf42d619427ee38e22a2e26c9af4799 SBOM.spdx.json diff --git a/Examples/PocketRootQuickStartApp/UITests/PocketRootQuickStartAppUITests.swift b/Examples/PocketRootQuickStartApp/UITests/PocketRootQuickStartAppUITests.swift index 7567698..b435799 100644 --- a/Examples/PocketRootQuickStartApp/UITests/PocketRootQuickStartAppUITests.swift +++ b/Examples/PocketRootQuickStartApp/UITests/PocketRootQuickStartAppUITests.swift @@ -16,7 +16,11 @@ final class PocketRootQuickStartAppUITests: XCTestCase { let actions = app.buttons["PocketRootFiles.actions"] XCTAssertTrue(actions.waitForExistence(timeout: 90)) - XCTAssertTrue(actions.isEnabled) + wait( + for: NSPredicate(format: "enabled == true"), + evaluatedWith: actions, + timeout: 30 + ) } func testTerminalCreatesFileThatFilesCanPreview() { diff --git a/Scripts/generate-release-compliance.rb b/Scripts/generate-release-compliance.rb index 25477ee..380dac0 100644 --- a/Scripts/generate-release-compliance.rb +++ b/Scripts/generate-release-compliance.rb @@ -218,9 +218,9 @@ module PocketRootReleaseCompliance "Scripts/run-host-app-device-ui-smoke.sh" => "f9528bd72aa40e6615633d1bc56ea8a31965b41f6570a0dc9f5fa390c5082490", "Scripts/run-host-app-ui-smoke.sh" => - "78d0a94afd705ce65a97e11c2bd6ae2094becc0a0c00b3e53a0cace41cd49554", + "811c58bd540512d35247970b994c54b28212145228fb40011c04456216052733", "Scripts/run-ios-example-ui-smoke.sh" => - "a9088ff7046992d6462558bd97ab54195cc07555951ab01ede7291058f9c1566", + "d401c53b2bfb731713a9d3a894c306ce0f406e02810b49bf4a6ddd9c0a280f23", "Scripts/run-quick-start-ui-smoke.sh" => "acf26d68fc37250e24911c2d23791a0d12ff06a624dedcefee0ed7905bc31af2", "ThirdPartyNotices/SwiftTerm-LICENSE.txt" => diff --git a/Scripts/run-host-app-ui-smoke.sh b/Scripts/run-host-app-ui-smoke.sh index 84a5ddb..45ac6f1 100755 --- a/Scripts/run-host-app-ui-smoke.sh +++ b/Scripts/run-host-app-ui-smoke.sh @@ -13,5 +13,7 @@ export POCKETROOT_UI_SMOKE_DEVICE="${POCKETROOT_HOST_UI_SMOKE_DEVICE:-}" export POCKETROOT_UI_DEVICE_TYPE="${POCKETROOT_HOST_UI_DEVICE_TYPE:-com.apple.CoreSimulator.SimDeviceType.iPhone-16}" export POCKETROOT_UI_DEVICE_NAME="${POCKETROOT_HOST_UI_DEVICE_NAME:-PocketRoot-Host-UI-Smoke-$$}" export POCKETROOT_UI_ONLY_TESTING="${POCKETROOT_HOST_UI_ONLY_TESTING:-PocketRootHostAppUITests/PocketRootHostAppUITests}" +export POCKETROOT_UI_DEFAULT_TEST_EXECUTION_TIME_ALLOWANCE="${POCKETROOT_HOST_UI_DEFAULT_TEST_EXECUTION_TIME_ALLOWANCE:-600}" +export POCKETROOT_UI_MAXIMUM_TEST_EXECUTION_TIME_ALLOWANCE="${POCKETROOT_HOST_UI_MAXIMUM_TEST_EXECUTION_TIME_ALLOWANCE:-600}" exec "$ROOT_DIR/Scripts/run-ios-example-ui-smoke.sh" "$@" diff --git a/Scripts/run-ios-example-ui-smoke.sh b/Scripts/run-ios-example-ui-smoke.sh index 1cddab0..42de3fb 100755 --- a/Scripts/run-ios-example-ui-smoke.sh +++ b/Scripts/run-ios-example-ui-smoke.sh @@ -19,6 +19,8 @@ DERIVED_DATA_ROOT="$( RESULT_BUNDLE_PATH="$DERIVED_DATA_ROOT/$TEST_BUNDLE.xcresult" CLONED_SOURCE_PACKAGES_DIR="${POCKETROOT_CLONED_SOURCE_PACKAGES_DIR:-${TMPDIR:-/tmp}/PocketRootSharedSourcePackages}" ONLY_TESTING="${POCKETROOT_UI_ONLY_TESTING:-$TEST_BUNDLE/$TEST_BUNDLE}" +DEFAULT_TEST_ALLOWANCE="${POCKETROOT_UI_DEFAULT_TEST_EXECUTION_TIME_ALLOWANCE:-300}" +MAXIMUM_TEST_ALLOWANCE="${POCKETROOT_UI_MAXIMUM_TEST_EXECUTION_TIME_ALLOWANCE:-600}" cleanup() { if [[ "$CREATED_DEVICE" == "true" && @@ -51,6 +53,12 @@ if ! command -v xcodegen >/dev/null 2>&1; then echo "$ARTIFACT_LABEL UI smoke requires XcodeGen." >&2 exit 2 fi +if [[ ! "$DEFAULT_TEST_ALLOWANCE" =~ ^[1-9][0-9]*$ || + ! "$MAXIMUM_TEST_ALLOWANCE" =~ ^[1-9][0-9]*$ || + "$DEFAULT_TEST_ALLOWANCE" -gt "$MAXIMUM_TEST_ALLOWANCE" ]]; then + echo "Example UI test allowances must be positive integers with default <= maximum." >&2 + exit 2 +fi if [[ -z "$DEVICE_UDID" ]]; then RUNTIME_ID="$( @@ -93,8 +101,8 @@ xcodebuild \ -resultBundlePath "$RESULT_BUNDLE_PATH" \ -clonedSourcePackagesDirPath "$CLONED_SOURCE_PACKAGES_DIR" \ -test-timeouts-enabled YES \ - -default-test-execution-time-allowance 300 \ - -maximum-test-execution-time-allowance 600 \ + -default-test-execution-time-allowance "$DEFAULT_TEST_ALLOWANCE" \ + -maximum-test-execution-time-allowance "$MAXIMUM_TEST_ALLOWANCE" \ "-only-testing:$ONLY_TESTING" \ ARCHS=arm64 \ ONLY_ACTIVE_ARCH=YES \ diff --git a/Tests/Scripts/RuntimeSmokeScriptTests.sh b/Tests/Scripts/RuntimeSmokeScriptTests.sh index 6a3172b..8e4ba28 100755 --- a/Tests/Scripts/RuntimeSmokeScriptTests.sh +++ b/Tests/Scripts/RuntimeSmokeScriptTests.sh @@ -56,6 +56,7 @@ bash -n "$QUICK_START_UI_RUNNER" if ! grep -Fq -- 'POCKETROOT_HOST_UI_SMOKE_DEVICE' "$HOST_UI_RUNNER" \ || ! grep -Fq -- 'POCKETROOT_HOST_UI_DEVICE_TYPE' "$HOST_UI_RUNNER" \ || ! grep -Fq -- 'PocketRootHostAppUITests' "$HOST_UI_RUNNER" \ + || ! grep -Fq -- 'POCKETROOT_HOST_UI_DEFAULT_TEST_EXECUTION_TIME_ALLOWANCE:-600' "$HOST_UI_RUNNER" \ || ! grep -Fq -- 'run-ios-example-ui-smoke.sh' "$HOST_UI_RUNNER" \ || ! grep -Fq -- 'POCKETROOT_QUICK_START_UI_SMOKE_DEVICE' "$QUICK_START_UI_RUNNER" \ || ! grep -Fq -- 'POCKETROOT_QUICK_START_UI_DEVICE_TYPE' "$QUICK_START_UI_RUNNER" \ @@ -68,8 +69,8 @@ fi if ! grep -Fq -- 'POCKETROOT_KEEP_UI_RESULT' "$GENERIC_UI_RUNNER" \ || ! grep -Fq -- 'POCKETROOT_DEVELOPMENT_ROOTFS_ARCHIVE="$ARCHIVE_PATH"' "$GENERIC_UI_RUNNER" \ || ! grep -Fq -- '-test-timeouts-enabled YES' "$GENERIC_UI_RUNNER" \ - || ! grep -Fq -- '-default-test-execution-time-allowance 300' "$GENERIC_UI_RUNNER" \ - || ! grep -Fq -- '-maximum-test-execution-time-allowance 600' "$GENERIC_UI_RUNNER" \ + || ! grep -Fq -- '-default-test-execution-time-allowance "$DEFAULT_TEST_ALLOWANCE"' "$GENERIC_UI_RUNNER" \ + || ! grep -Fq -- '-maximum-test-execution-time-allowance "$MAXIMUM_TEST_ALLOWANCE"' "$GENERIC_UI_RUNNER" \ || ! grep -Fq -- 'xcrun simctl delete "$DEVICE_UDID"' "$GENERIC_UI_RUNNER"; then echo "Shared example UI runner is missing bounded execution or cleanup." >&2 exit 1 From 8def22133fb9b05ab5c331c0bfbfe823937ae28d Mon Sep 17 00:00:00 2001 From: Jintao Date: Thu, 30 Jul 2026 23:35:54 +0800 Subject: [PATCH 4/4] test: isolate Host UI runtime lifecycles --- .../experimental-v0.1.0/COMPOSITION.json | 2 +- .../experimental-v0.1.0/SBOM.spdx.json | 2 +- .../Release/experimental-v0.1.0/SHA256SUMS | 4 +- .../UITests/PocketRootHostAppUITests.swift | 47 ++++++++++--------- 4 files changed, 29 insertions(+), 26 deletions(-) diff --git a/Compliance/Release/experimental-v0.1.0/COMPOSITION.json b/Compliance/Release/experimental-v0.1.0/COMPOSITION.json index 328f90e..8dcb762 100644 --- a/Compliance/Release/experimental-v0.1.0/COMPOSITION.json +++ b/Compliance/Release/experimental-v0.1.0/COMPOSITION.json @@ -110,7 +110,7 @@ "Examples/PocketRootDemo/Sources/PocketRootDemo/Shared/UI/PlaceholderViewController.swift": "48e0f6462228a66face2aef51f9bd39ec02923c4d1c01b6b34f920948998f2b8", "Examples/PocketRootHostApp/Sources/HostApp.swift": "0c36bd3104ddc5bebb9e23675a44a92e3ce4aec2c1461b3e182d86d1a75159c9", "Examples/PocketRootHostApp/Sources/Info.plist": "711b38923f11fdb4ceb7d28cb90a51674afec0a64786a20f9bd68e3f406f8d26", - "Examples/PocketRootHostApp/UITests/PocketRootHostAppUITests.swift": "1ef4f857d0cd44887157a82d9875db9a43be711ee6b86cdc7df811486519fdfe", + "Examples/PocketRootHostApp/UITests/PocketRootHostAppUITests.swift": "f8435d5c0a006e35b95804b49b485a05e0034265bb5ed0fd8e55458b8faaead6", "Examples/PocketRootQuickStartApp/Sources/Info.plist": "b3c0c96e89db99c72c9f020590b1c5cdc0c927fd3af4b6ae7865abfdc81049b8", "Examples/PocketRootQuickStartApp/Sources/QuickStartApp.swift": "1c77c9229281d0d1f9fbe3678bcc9995fff8c7d2189f4c6152daaa2999683fb6", "Examples/PocketRootQuickStartApp/UITests/PocketRootQuickStartAppUITests.swift": "17db21ce0e23489ea85b19cef285746f33e1b6f8f6efedda0599d1934a8766ba", diff --git a/Compliance/Release/experimental-v0.1.0/SBOM.spdx.json b/Compliance/Release/experimental-v0.1.0/SBOM.spdx.json index e1f403f..41678ce 100644 --- a/Compliance/Release/experimental-v0.1.0/SBOM.spdx.json +++ b/Compliance/Release/experimental-v0.1.0/SBOM.spdx.json @@ -3,7 +3,7 @@ "dataLicense": "CC0-1.0", "SPDXID": "SPDXRef-DOCUMENT", "name": "PocketRoot experimental full-graph composition 0.1.0", - "documentNamespace": "https://github.com/jacklv-coder/PocketRoot/sbom/release/experimental-v0.1.0/f23a5237b7a47d4247007e6fc5ee41d669cede57f9488434228b503d54b1797b", + "documentNamespace": "https://github.com/jacklv-coder/PocketRoot/sbom/release/experimental-v0.1.0/867d814a9b9144d98b849ec3feff767821a9c3b0cfc3131080fe14a163ba213d", "creationInfo": { "created": "2026-07-28T00:00:00Z", "creators": [ diff --git a/Compliance/Release/experimental-v0.1.0/SHA256SUMS b/Compliance/Release/experimental-v0.1.0/SHA256SUMS index 24108f0..6e3a853 100644 --- a/Compliance/Release/experimental-v0.1.0/SHA256SUMS +++ b/Compliance/Release/experimental-v0.1.0/SHA256SUMS @@ -1,3 +1,3 @@ -f23a5237b7a47d4247007e6fc5ee41d669cede57f9488434228b503d54b1797b COMPOSITION.json +867d814a9b9144d98b849ec3feff767821a9c3b0cfc3131080fe14a163ba213d COMPOSITION.json 1864fd0585b0eff50cc088d7323b5f38df958739e6ca79aba23d8e189b2fbf1c README.md -be9d0af697b34dab6f9bda60f91bf9407bf42d619427ee38e22a2e26c9af4799 SBOM.spdx.json +1dc3c97ef35d4beff2744b0ed8e7558fdeb526281c182d0c58bf792c167ea9ae SBOM.spdx.json diff --git a/Examples/PocketRootHostApp/UITests/PocketRootHostAppUITests.swift b/Examples/PocketRootHostApp/UITests/PocketRootHostAppUITests.swift index f2ce85e..d09b0e6 100644 --- a/Examples/PocketRootHostApp/UITests/PocketRootHostAppUITests.swift +++ b/Examples/PocketRootHostApp/UITests/PocketRootHostAppUITests.swift @@ -114,6 +114,7 @@ final class PocketRootHostAppUITests: XCTestCase { evaluatedWith: folder, timeout: 30 ) + shutdownRuntime(in: app) } func testPTYCommandCreatesFileVisibleInFiles() { @@ -168,6 +169,7 @@ final class PocketRootHostAppUITests: XCTestCase { let preview = app.staticTexts["PocketRootFiles.preview"] XCTAssertTrue(preview.waitForExistence(timeout: 30)) XCTAssertEqual(preview.label, "PocketRoot UI smoke\n") + shutdownRuntime(in: app) } func testSystemFileImportAndShareExportRoundTrip() { @@ -297,11 +299,15 @@ final class PocketRootHostAppUITests: XCTestCase { named: Self.systemImportFixtureName, in: app ) + shutdownRuntime(in: app) } func testPTYLifecycleAndShutdown() { addTeardownBlock { - XCUIDevice.shared.orientation = .portrait + let device = XCUIDevice.shared + if device.orientation != .portrait { + device.orientation = .portrait + } } let app = launchAndBoot() @@ -417,17 +423,8 @@ final class PocketRootHostAppUITests: XCTestCase { "before-background\nafter-foreground\nafter-reopen\n" ) - returnToHost(in: app) + shutdownRuntime(in: app) let shutdownButton = app.buttons["PocketRootHost.shutdown"] - XCTAssertTrue(shutdownButton.waitForExistence(timeout: 30)) - shutdownButton.tap() - - let status = app.staticTexts["PocketRootHost.status"] - wait( - for: NSPredicate(format: "label == %@", "Runtime: Restart App"), - evaluatedWith: status, - timeout: 30 - ) XCTAssertFalse(terminalButton.isEnabled) XCTAssertFalse(filesButton.isEnabled) XCTAssertFalse(shutdownButton.isEnabled) @@ -496,6 +493,7 @@ final class PocketRootHostAppUITests: XCTestCase { evaluatedWith: workspaceButton, timeout: 30 ) + shutdownRuntime(in: app) } func testIntegratedWorkspaceBootsAndOwnsShutdownOrdering() { @@ -560,17 +558,7 @@ final class PocketRootHostAppUITests: XCTestCase { timeout: 30 ) - let shutdownButton = app.buttons["PocketRootHost.shutdown"] - XCTAssertTrue(shutdownButton.isEnabled) - shutdownButton.tap() - wait( - for: NSPredicate( - format: "label == %@", - "Runtime: Restart App" - ), - evaluatedWith: hostStatus, - timeout: 30 - ) + shutdownRuntime(in: app) XCTAssertFalse(integratedButton.isEnabled) } @@ -830,6 +818,21 @@ final class PocketRootHostAppUITests: XCTestCase { XCTAssertTrue(status.waitForExistence(timeout: 10)) } + private func shutdownRuntime(in app: XCUIApplication) { + returnToHost(in: app) + let shutdownButton = app.buttons["PocketRootHost.shutdown"] + XCTAssertTrue(shutdownButton.waitForExistence(timeout: 30)) + waitForEnabled(shutdownButton) + shutdownButton.tap() + + let status = app.staticTexts["PocketRootHost.status"] + wait( + for: NSPredicate(format: "label == %@", "Runtime: Restart App"), + evaluatedWith: status, + timeout: 30 + ) + } + private func allowTerminalToDrain() { RunLoop.current.run(until: Date().addingTimeInterval(1)) }