diff --git a/docs/environment.md b/docs/environment.md index 379cdad..9c172f2 100644 --- a/docs/environment.md +++ b/docs/environment.md @@ -1,7 +1,7 @@ # 本地开发环境基线 > 状态:已从当前主机实测 -> 最后核验:2026-08-07 +> 最后核验:2026-08-08 > 适用范围:`D:\OPC\brainwave` 的 Android 开发、构建和设备验证 本文件记录项目当前可用的本地工具链,不是期望环境清单。后续实现必须先使用这里已经确认的能力;需要升级或安装新工具时,应说明原因并在完成后更新本文件。 @@ -125,7 +125,7 @@ Android Studio 不是当前环境的可用前提。项目必须先支持 PowerSh | 物理分辨率 | `1080 × 2280` | | 物理密度 | `480 dpi` | -该设备满足 `minSdk=26`,可用于 `adb` 安装、手工验收和 `connectedDebugAndroidTest`。测试脚本不得硬编码序列号;设备可能随时断开,环境独立门禁必须单独可运行。本机 `PATH` 还存在 `D:\Portable\adb\adb.exe`,曾运行旧版 ADB server;项目设备命令应显式使用 `$env:ANDROID_HOME\platform-tools\adb.exe`,避免与 SDK Platform Tools 37.0.0 争用 server。ADB server 重启后若接口消失,应先保持手机解锁并重新确认 USB 调试授权,不应把“Windows 能看到 USB 复合设备”误当作 ADB 已连接。 +该设备满足 `minSdk=26`,可用于 `adb` 安装、手工验收和仪器测试。测试脚本不得硬编码序列号;设备可能随时断开,环境独立门禁必须单独可运行。本机 `PATH` 中的 `D:\Portable\adb\adb.exe` 为 ADB 1.0.32,SDK Platform Tools 37.0.0 提供 ADB 1.0.41;两者会争用 5037 端口并互相重启 server。本轮三星设备只在便携 ADB server 下稳定枚举,SDK ADB 重启后设备接口消失,因此真机门禁通过 `run-connected-tests.ps1 -AdbPath` 显式选择单一 ADB。不要同时启动两个 server,也不要把“Windows 能看到 USB 复合设备”误当作 ADB 已连接。 ## 8. 网络与代理状态 @@ -154,10 +154,10 @@ Android Studio 不是当前环境的可用前提。项目必须先支持 PowerSh - Android Studio 未安装。 - Android Emulator、system image 和 AVD 不可用。 - 本机只确认安装了 Android Platform 34 / Build Tools 34.0.0。 -- `PATH` 中旧便携 ADB 与 SDK ADB 可能争用 server;设备接口消失时需要手机端重新授权或重新连接。 +- `PATH` 中 ADB 1.0.32 与 SDK ADB 1.0.41 会争用 server;当前三星设备的可重复门禁需要显式选择便携 ADB,后续应评估统一驱动和 Platform Tools。 - 代理已配置;本轮 Maven 依赖下载成功,但不能据此保证未来网络始终可用。 -已解除的缺口:Gradle Wrapper、Version Catalog、根 `AGENTS.md`、CI、正式 Android 身份和 application 壳均已建立;`assembleDebug`、13 个 debug 单元测试、`lintDebug` 与测试 APK 编译已在 JDK 17 上通过。`verifyLocal` 现已包含 Android lint 与 debug APK,设备测试仍按环境单独执行。 +已解除的缺口:Gradle Wrapper、Version Catalog、根 `AGENTS.md`、CI、正式 Android 身份和 application 壳均已建立;`assembleDebug`、13 个 debug 单元测试、`lintDebug` 与测试 APK 编译已在 JDK 17 上通过。2026-08-08,`app-debug.apk` 已安装并冷启动到 `MainActivity`,Compose 仪器测试 `MainActivityTest` 以 `OK (1 test)` 通过,首页与结果页语义树和截图人工复核通过,未发现应用崩溃日志。 这些缺口分别由[实施计划](implementation-plan.md)的 P0 和[质量门禁](quality-gates.md)处理。环境缺口不是跳过验证的理由;无法运行的门禁必须在交付报告中准确说明。 diff --git a/docs/implementation-plan.md b/docs/implementation-plan.md index 4a6788d..6bbec54 100644 --- a/docs/implementation-plan.md +++ b/docs/implementation-plan.md @@ -1,6 +1,6 @@ # 分阶段实施计划 -> 状态:P-1 v0.3 待视觉复核;P0 Android 壳与本地门禁已完成,真机闭环正在执行;P1 已完成并通过穷举测试;P2 内容契约已建立但授权内容未开始 +> 状态:P0 Android 壳、本地门禁与三星 API 31 真机闭环已完成;P-1 v0.3 待视觉复核;P1 已完成并通过穷举测试;P2 内容契约已建立但授权内容未开始 > 计划原则:先用原型确认高返工成本体验,再锁定确定性领域核心,随后接内容和 UI,最后接网络 AI ## 1. 依赖图 @@ -62,6 +62,8 @@ P1 与 P2 可并行,但 P3 不能在领域与内容契约未稳定时复制原 - 开发验证应用可在授权真机启动,并从首页导航到已知领域夹具结果页。 - 没有生产服务密钥或真实内容。 +完成证据(2026-08-08):`verifyLocal`、13 个 debug 单元测试、`lintDebug`、主/测试 APK 构建均通过;调试 APK 在 Samsung API 31 设备冷启动到 `MainActivity`,`MainActivityTest` 真机执行 1/1 通过,并验证首页进入“复 24 → 坤 2、初爻动”结果页。因本机 ADB 1.0.32/1.0.41 server 冲突,设备门禁使用仓库脚本显式选择可枚举该设备的 ADB,未改写 SDK 工具。 + ## 4. P1:领域核心 目标:在纯 Kotlin 中完成并证明三枚铜币算法。 diff --git a/docs/quality-gates.md b/docs/quality-gates.md index a65e5b9..5a1326a 100644 --- a/docs/quality-gates.md +++ b/docs/quality-gates.md @@ -35,7 +35,13 @@ .\gradlew.bat connectedDebugAndroidTest ``` -若采用不同格式化插件,命令可以调整,但必须在本文件和 CI 同步更新。`connectedDebugAndroidTest` 需要模拟器或设备,继续与环境独立的 `verifyLocal` 分开;`verifyLocal` 通过证明 APK 已构建,但不等于真机测试已经通过。 +若本机存在两个不兼容的 ADB server,可在只保留一个 server 的前提下使用等价的可重复入口: + +```powershell +.\scripts\run-connected-tests.ps1 -AdbPath <能够稳定识别设备的 adb.exe> +``` + +该脚本重新构建主/测试 APK、检查授权状态、覆盖安装两者并直接调用同一个 `AndroidJUnitRunner`;失败、零测试或缺少 `OK` 结果都会使脚本失败。若采用不同格式化插件,命令可以调整,但必须在本文件和 CI 同步更新。设备测试继续与环境独立的 `verifyLocal` 分开;`verifyLocal` 通过证明 APK 已构建,但不等于真机测试已经通过。 ## 3. 测试层次 diff --git a/scripts/run-connected-tests.ps1 b/scripts/run-connected-tests.ps1 new file mode 100644 index 0000000..e2c8b13 --- /dev/null +++ b/scripts/run-connected-tests.ps1 @@ -0,0 +1,52 @@ +[CmdletBinding()] +param( + [string]$AdbPath = (Join-Path $env:ANDROID_HOME "platform-tools\adb.exe") +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repositoryRoot = Split-Path -Parent $PSScriptRoot +$gradleWrapper = Join-Path $repositoryRoot "gradlew.bat" +$mainApk = Join-Path $repositoryRoot "app\build\outputs\apk\debug\app-debug.apk" +$testApk = Join-Path $repositoryRoot "app\build\outputs\apk\androidTest\debug\app-debug-androidTest.apk" +$instrumentation = "net.opcapp.flash.test/androidx.test.runner.AndroidJUnitRunner" + +if (-not (Test-Path -LiteralPath $AdbPath -PathType Leaf)) { + throw "ADB executable not found: $AdbPath" +} + +& $gradleWrapper :app:assembleDebug :app:assembleDebugAndroidTest --no-daemon --console=plain +if ($LASTEXITCODE -ne 0) { + throw "Android app or test APK build failed." +} + +$deviceState = (& $AdbPath get-state 2>$null) +if ($LASTEXITCODE -ne 0 -or $deviceState.Trim() -ne "device") { + throw "No authorized Android device is available through: $AdbPath" +} + +& $AdbPath install -r $mainApk +if ($LASTEXITCODE -ne 0) { + throw "Main APK installation failed." +} + +& $AdbPath install -r $testApk +if ($LASTEXITCODE -ne 0) { + throw "Test APK installation failed." +} + +$instrumentationOutput = & $AdbPath shell am instrument -w -r $instrumentation 2>&1 +$instrumentationExitCode = $LASTEXITCODE +$instrumentationOutput | Write-Output +$joinedOutput = $instrumentationOutput -join "`n" + +if ( + $instrumentationExitCode -ne 0 -or + $joinedOutput -match "FAILURES!!!" -or + $joinedOutput -notmatch "OK \(\d+ tests?\)" +) { + throw "Connected Android instrumentation tests failed." +} + +Write-Output "Connected Android instrumentation tests passed." diff --git a/scripts/verify-format.mjs b/scripts/verify-format.mjs index 531b0a7..809c8b7 100644 --- a/scripts/verify-format.mjs +++ b/scripts/verify-format.mjs @@ -15,6 +15,7 @@ const checkedExtensions = new Set([ ".md", ".mjs", ".properties", + ".ps1", ".toml", ".xml", ".yaml",