Files
ykxmz/_tests/verify_all.ps1
T
xmz7891 1d368979aa 2.2.0:手表端连上后先到「远控 / 静默更新被控端」选择页
== 用户要求 ==

"安卓那个逻辑也要改一下,就是你输入配对码后,进入一个页面,
 页面有两个按钮,第一个按钮是远控,第二个按钮是静默更新被控端"

== 做成什么样 ==

新增 choicePanel,连接成功后先进这一页:

    已连接「电脑名」
    电脑端 2.1.5 → 可更新到 2.2.0

    [        远控        ]   <- 第一个按钮
    [   静默更新被控端    ]   <- 第二个按钮

按钮顺序按用户说的来(远控在前)。

页面顶上显示电脑名和**电脑端当前版本** —— 用户之前要求
"手表端连接那个电脑端也可以查看电脑端目前所处的版本",
这里是最自然的展示位置(不弹框,只显示一行)。

为什么不让它直接进控制页:老版本的被控端可能缺功能或有 bug,
"先更新再远控"是更合理的顺序 —— 但不强制,给用户自己选。

== 一个刻意不改的地方 ==

自动切到中转服务器**恢复**时的 `showControlPanel()` **没动** ——
用户本来就在远控页,中途弹一层选择页会打断他。
测试里专门断言了这一点,免得以后有人"顺手统一一下"改坏。

== 顺手修的几个问题 ==

1. **忘了定义 lastDeviceName** —— 写选择页时凭直觉用了这个变量,
   编译报"找不到符号"。补上字段并在连接回调里赋值。
   (教训:写代码时要用的变量,先确认它存在。)

2. **泄漏闸门拦下我自己** —— 这两天写的一批服务器诊断/配置脚本里
   硬编码了服务器 IP(26 处,5 个文件)。规则是 IP 不能进任何
   被跟踪的文件。已全部改成占位符,真实值仍从
   _build/.lightsail_host(已 gitignore)读。

3. **verify_all.ps1 删掉「锁屏时记 SKIP」的逻辑** ——
   用户明确要求删。那个好意带来一个更糟的后果:锁屏时几关根本不跑,
   回归"全绿"了但关键测试其实一跳而过。
   **"因为环境不满足所以不测"比"测了发现失败"更危险。**
   删掉之后果然暴露出 3 个 FAIL —— 屏幕解锁后重跑全部通过,
   确认是环境问题而不是代码问题。

== 服务器这边(不在这份提交里,但同一天做的)==

  * nginx 按用户要求重排:
      IP(80) 任何路径 -> 404(没东西)
      域名 / -> 官网;/zhongzhuanzhan -> 中转站设置
      9100 -> 同样的内容(等 AWS 开防火墙)
  * 删掉早年 Cloudflare Tunnel 的 systemd 残留
  * **补上 ACME 挑战例外** —— 这是最要紧的一条:
    80 端口原来把所有请求都 301 到 https,包括
    /.well-known/acme-challenge/,**续期时会失败**。
    而且它是延迟爆发的(最长 90 天后证书到期才暴露)。
  * 清理 1Panel 安装包残留(223MB)

== 验证 ==

    全量回归 21 关全部 PASS(零 FAIL,桌面可用状态)
    Java 编译通过(android-34, --release 8)
    623+ Python 文件语法通过
    泄漏闸门通过
    全部产物重建,Win7 兼容校验通过
2026-10-10 17:13:24 +08:00

798 lines
40 KiB
PowerShell
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 局域网遥控 全量验证脚本
#
# 依次执行:
# 0. 清理上一次测试可能残留的进程(否则发现端口会被抢占,导致假失败)
# 1. Python 被控端协议回归(含屏幕镜像/截屏)
# 2. 手表屏幕布局适配核算(490x602)
# 3. 屏幕共享可见提示(真实界面 + Win32 窗口枚举)
# 4. 小窗口 / 无托盘 / 后台常驻 / 双启动找回窗口
# 5. Java 协议层编译
# 6. Java <-> 桩服务端(含帧流、截屏、自动发现、单播探测)
# 7. Java <-> 打包后的 EXE 端到端
#
# 用法: powershell -ExecutionPolicy Bypass -File _tests\verify_all.ps1
$ErrorActionPreference = "Continue"
$root = Split-Path -Parent $PSScriptRoot
$tc = Join-Path $root "..\starklink\toolchain"
$jdk = Join-Path $tc "jdk17"
$java = Join-Path $jdk "bin\java.exe"
$javac= Join-Path $jdk "bin\javac.exe"
$out = Join-Path $root "_tests\out_java"
$exe = Join-Path $root "dist_exe\ykxmz\ykxmz.exe"
$androidJar = Join-Path $tc "android-sdk\platforms\android-34\android.jar"
$script:results = @()
# ⚠ 这里**原来有一段锁屏检测**,锁屏时把 needsDesktop 的阶段记成
# `SKIP(锁屏)` 而不是 FAIL。**已经按用户要求删掉了。**
#
# 删的理由:那个好意带来一个更糟的后果 —— 锁屏时那几关根本不跑,
# 于是回归"全绿"了,但关键的 Java 端到端、协议回归其实一跳而过。
# 用户看到"全部通过"会以为没问题。
#
# **"因为环境不满足所以不测"比"测了发现失败"更危险**:
# 失败至少是信息,跳过是假的安全感。
#
# 现在一律真跑、真记。环境不合适导致失败的话,就去修环境或改测试,
# 而不是让它变成 SKIP。
function Record($name, $ok, $needsDesktop = $false) {
# ⚠ $needsDesktop 参数**保留但不再使用** ——
# 6 个调用点还在传它,留着就不用改那些地方。
# 以后真要重新引入"特定环境下跳过",签名已经在了。
$script:results += [pscustomobject]@{
项目 = $name; 结果 = $(if ($ok) { "PASS" } else { "FAIL" }) }
}
function Banner($text) {
Write-Host ""
Write-Host ("=" * 70) -ForegroundColor DarkCyan
Write-Host " $text" -ForegroundColor Cyan
Write-Host ("=" * 70) -ForegroundColor DarkCyan
}
function Show-Fails($lines) {
# 失败的阶段光看最后几行没用,得把 [FAIL] 打出来
$fails = $lines | Select-String -Pattern '\[FAIL\]'
if ($fails) {
$fails | Select-Object -First 8 | ForEach-Object {
Write-Host " $($_.Line.Trim())" -ForegroundColor Red
}
}
$lines | Select-Object -Last 1 | ForEach-Object {
# ⚠ 这里可能是 ErrorRecord(子进程的 stderr 被塞进输出流时),
# 而 ErrorRecord 没有 .Trim() 方法 —— 直接调会抛
# "does not contain a method named 'Trim'",反而把真正的
# 失败信息盖住。所以先转成字符串再 Trim。
Write-Host " $([string]$_.Trim())"
}
}
function Close-TestWindows {
# 测试会弹真实的 Tk 窗口(首次设置、投图、屏幕共享提示…)。
# 正常路径下测试自己会关;但用例失败/超时时进程还挂着,
# 窗口就留在屏幕上不走了。这里统一收尾。
$closed = 0
Get-Process python, pythonw -ErrorAction SilentlyContinue |
Where-Object { $_.Id -ne $PID } |
ForEach-Object {
$line = (Get-CimInstance Win32_Process -Filter "ProcessId=$($_.Id)" -ErrorAction SilentlyContinue).CommandLine
if ($line -and ($line -match '_tests' -or $line -match 'run_agent' `
-or $line -match 'relay\.py')) {
# 先好好关主窗口
try { $_.CloseMainWindow() | Out-Null } catch { }
Start-Sleep -Milliseconds 250
if (-not $_.HasExited) {
Stop-Process -Id $_.Id -Force -ErrorAction SilentlyContinue
}
$closed++
}
}
# 被控端自己(测试起过 ykxmz.exe)
Get-Process ykxmz -ErrorAction SilentlyContinue | ForEach-Object {
try { $_.CloseMainWindow() | Out-Null } catch { }
Start-Sleep -Milliseconds 250
if (-not $_.HasExited) {
Stop-Process -Id $_.Id -Force -ErrorAction SilentlyContinue
}
$closed++
}
# Java 测试进程
Get-Process java -ErrorAction SilentlyContinue | ForEach-Object {
$line = (Get-CimInstance Win32_Process -Filter "ProcessId=$($_.Id)" -ErrorAction SilentlyContinue).CommandLine
if ($line -and ($line -match 'JavaClientTest' -or $line -match 'JavaRelayTest')) {
Stop-Process -Id $_.Id -Force -ErrorAction SilentlyContinue
$closed++
}
}
if ($closed -gt 0) {
Write-Host " 关了 $closed 个测试进程/窗口" -ForegroundColor DarkGray
}
Start-Sleep -Milliseconds 400
}
function Stop-Strays {
Get-Process ykxmz -ErrorAction SilentlyContinue |
ForEach-Object { Stop-Process -Id $_.Id -Force -ErrorAction SilentlyContinue }
# 各测试会临时起 relay.py / stub / java。
# 只清 run_agent/run_stub 是不够的 —— 漏掉的 relay.py 如果没及时退出,
# 端口就占着,下一个测试起不来,报出"假失败"
# (实测:chunked 单跑 9/9、协议单跑 69/69,套件里却 FAIL)。
Get-Process python, pythonw -ErrorAction SilentlyContinue |
Where-Object { $_.Id -ne $PID } |
ForEach-Object {
$line = (Get-CimInstance Win32_Process -Filter "ProcessId=$($_.Id)" -ErrorAction SilentlyContinue).CommandLine
if ($line -and ($line -match 'run_agent' -or $line -match 'run_stub' `
-or $line -match 'relay\.py' -or $line -match '_tests')) {
Stop-Process -Id $_.Id -Force -ErrorAction SilentlyContinue
}
}
# Java 测试进程(JavaClientTest / JavaRelayTest)
Get-Process java -ErrorAction SilentlyContinue |
ForEach-Object {
$line = (Get-CimInstance Win32_Process -Filter "ProcessId=$($_.Id)" -ErrorAction SilentlyContinue).CommandLine
if ($line -and ($line -match 'JavaClientTest' -or $line -match 'JavaRelayTest')) {
Stop-Process -Id $_.Id -Force -ErrorAction SilentlyContinue
}
}
# 等到关键端口真的空出来(最多 6 秒),别让下一个测试撞上
for ($i = 0; $i -lt 12; $i++) {
$busy = $false
foreach ($p in 8765, 8766, 8799, 8130, 8140) {
if (Get-NetTCPConnection -LocalPort $p -State Listen -ErrorAction SilentlyContinue) {
$busy = $true
}
if (Get-NetUDPEndpoint -LocalPort $p -ErrorAction SilentlyContinue) {
$busy = $true
}
}
if (-not $busy) { break }
Start-Sleep -Milliseconds 500
}
Start-Sleep -Milliseconds 300
}
# ---------------------------------------------------------------- 0
Banner "0/7 清理残留进程与端口"
Stop-Strays
$busy = @()
foreach ($p in 8765, 8766, 8799, 8801, 8803, 8805) {
if (Get-NetUDPEndpoint -LocalPort $p -ErrorAction SilentlyContinue) { $busy += "UDP $p" }
if (Get-NetTCPConnection -LocalPort $p -State Listen -ErrorAction SilentlyContinue) { $busy += "TCP $p" }
}
if ($busy.Count -eq 0) {
Write-Host "端口干净" -ForegroundColor Green
} else {
Write-Host ("仍被占用: " + ($busy -join ", ")) -ForegroundColor Yellow
}
# ---------------------------------------------------------------- 0a
# 清掉上一次测试留在 received 里的文件。
# 不清的话会越攒越多,把 file_list 的前 100 个窗口挤满,
# 导致"看不到刚上传的文件"这类假失败(真踩过:攒到 116 个)。
$receivedDir = Join-Path $root "_tests\received"
if (Test-Path $receivedDir) {
$old = (Get-ChildItem $receivedDir -File -ErrorAction SilentlyContinue).Count
if ($old -gt 0) {
Get-ChildItem $receivedDir -File | Remove-Item -Force -ErrorAction SilentlyContinue
Write-Host " 清掉上次测试残留的 $old 个文件" -ForegroundColor DarkGray
}
}
# ---------------------------------------------------------------- 0
# 编码审计放最前面:编码错了后面全白搭
Banner "0/7 编码审计(BOM / 行尾 / 乱码)"
& python (Join-Path $root "_tests\audit_encoding.py") *> (Join-Path $env:TEMP "lr_enc.txt")
$encCode = $LASTEXITCODE
Get-Content (Join-Path $env:TEMP "lr_enc.txt") -Encoding UTF8 -ErrorAction SilentlyContinue |
Select-Object -Last 3
Record "编码审计" ($encCode -eq 0)
# ---------------------------------------------------------------- 1
Banner "1/7 Python 被控端协议回归"
$stageOut = & python (Join-Path $root "_tests\test_agent_protocol.py") 2>&1
$stageCode = $LASTEXITCODE
$stageOut | Select-Object -Last 4
Record "Python 被控端协议回归" ($stageCode -eq 0) $true
# ---------------------------------------------------------------- 1a2
Banner "1a2/7 手表面板接线(每个面板都要挂到 root)"
$stageOut = & python (Join-Path $root "_tests\test_watch_panels_wired.py") 2>&1
Show-Fails $stageOut
Record "手表面板接线" ($LASTEXITCODE -eq 0)
# ---------------------------------------------------------------- 1a3
Banner "1a3/7 网络热门音效(生成/列表/播放/自放素材)"
$stageOut = & python (Join-Path $root "_tests\test_sfx.py") 2>&1
Show-Fails $stageOut
Record "网络热门音效" ($LASTEXITCODE -eq 0)
# ---------------------------------------------------------------- 1b
Banner "1b/7 防火墙阻止规则检测(真机连不上的根因)"
$stageOut = & python (Join-Path $root "_tests\test_firewall.py") 2>&1
$stageCode = $LASTEXITCODE
$stageOut | Select-Object -Last 3
Record "防火墙阻止规则检测" ($stageCode -eq 0)
# ---------------------------------------------------------------- 1c
Banner "1c/7 连不上?-> 电脑打开网络自检(UDP 链路)"
Stop-Strays
& python (Join-Path $root "_tests\test_selfcheck.py") *> (Join-Path $env:TEMP "lr_sc.txt")
$scCode = $LASTEXITCODE
Get-Content (Join-Path $env:TEMP "lr_sc.txt") -Encoding UTF8 -ErrorAction SilentlyContinue |
Select-Object -Last 2
Record "自检请求链路" ($scCode -eq 0)
# ---------------------------------------------------------------- 1e
# ---------------------------------------------------------------- 隐藏桌面
# 下面 6 个阶段会真的开窗口,其中「投图显示方式」还会开**全屏** ——
# 直接在用户眼前跑的话屏幕会被锁住,没法用电脑。
#
# 现在统一套一层 hidden_desktop.py:它新建一个 Windows 桌面,把子进程
# 放上去,所有窗口都创建在那个不显示的桌面上。
# **窗口是真的**(全屏、可见、几何、Win32 枚举都真实),
# 只是用户看不到 —— 测试有效性没有损失。
#
# ⚠ 新增会弹窗的测试时,记得也套一层,否则用户屏幕又会被闪。
Banner "1e/7 新增功能(电量/磁盘/性能/进程/剪贴板历史/安全边界)"
Stop-Strays
& python (Join-Path $root "_tests\hidden_desktop.py") -- python (Join-Path $root "_tests\test_new_features.py") *> (Join-Path $env:TEMP "lr_feat.txt")
$featCode = $LASTEXITCODE
Get-Content (Join-Path $env:TEMP "lr_feat.txt") -Encoding UTF8 -ErrorAction SilentlyContinue |
Select-Object -Last 2
Record "新增功能" ($featCode -eq 0)
# ---------------------------------------------------------------- 1f
Banner "1f/7 电脑功能注册表(39 个功能 + 安全边界 + 可配置)"
Stop-Strays
& python (Join-Path $root "_tests\hidden_desktop.py") -- python (Join-Path $root "_tests\test_features.py") *> (Join-Path $env:TEMP "lr_feats.txt")
$featsCode = $LASTEXITCODE
Get-Content (Join-Path $env:TEMP "lr_feats.txt") -Encoding UTF8 -ErrorAction SilentlyContinue |
Select-Object -Last 2
Record "电脑功能注册表" ($featsCode -eq 0)
# ---------------------------------------------------------------- 1g
Banner "1g/7 功能模糊测试(105 个功能 × 空参数 / 垃圾参数 / 越权路径)"
Stop-Strays
& python (Join-Path $root "_tests\hidden_desktop.py") -- python (Join-Path $root "_tests\fuzz_features.py") *> (Join-Path $env:TEMP "lr_fuzz.txt")
$fuzzCode = $LASTEXITCODE
Get-Content (Join-Path $env:TEMP "lr_fuzz.txt") -Encoding UTF8 -ErrorAction SilentlyContinue |
Select-Object -Last 3
Record "功能模糊测试" ($fuzzCode -eq 0)
# ---------------------------------------------------------------- 1h
Banner "1h/7 端到端(打包后的 EXE:配对 / 功能列表 / 越权 / 防爆破)"
Stop-Strays
& python (Join-Path $root "_tests\test_e2e_features.py") *> (Join-Path $env:TEMP "lr_e2e.txt")
$e2eCode = $LASTEXITCODE
Get-Content (Join-Path $env:TEMP "lr_e2e.txt") -Encoding UTF8 -ErrorAction SilentlyContinue |
Select-Object -Last 3
Record "端到端(打包 EXE)" ($e2eCode -eq 0)
# ---------------------------------------------------------------- 1i
Banner "1i/7 静默模式(0 窗口 / 无终端 / 认证仍生效)"
Stop-Strays
& python (Join-Path $root "_tests\test_silent_mode.py") *> (Join-Path $env:TEMP "lr_silent.txt")
$silentCode = $LASTEXITCODE
Get-Content (Join-Path $env:TEMP "lr_silent.txt") -Encoding UTF8 -ErrorAction SilentlyContinue |
Select-Object -Last 3
Record "静默模式" ($silentCode -eq 0)
# ---------------------------------------------------------------- 1j
Banner "1j/7 配一次→零窗口(令牌重连,全程无窗口)"
Stop-Strays
& python (Join-Path $root "_tests\test_pair_once_flow.py") *> (Join-Path $env:TEMP "lr_flow.txt")
$flowCode = $LASTEXITCODE
Get-Content (Join-Path $env:TEMP "lr_flow.txt") -Encoding UTF8 -ErrorAction SilentlyContinue |
Select-Object -Last 3
Record "配一次→零窗口" ($flowCode -eq 0)
# ---------------------------------------------------------------- 1k
Banner "1k/7 首次安装设置窗口(出现一次,之后不再出现)"
Stop-Strays
& python (Join-Path $root "_tests\hidden_desktop.py") -- python (Join-Path $root "_tests\test_first_run_setup.py") *> (Join-Path $env:TEMP "lr_setup.txt")
$setupCode2 = $LASTEXITCODE
Get-Content (Join-Path $env:TEMP "lr_setup.txt") -Encoding UTF8 -ErrorAction SilentlyContinue |
Select-Object -Last 3
Record "首次设置窗口" ($setupCode2 -eq 0)
# ---------------------------------------------------------------- 1l
Banner "1l/7 开机零窗口(照注册表里的自启命令真跑一遍)"
Stop-Strays
& python (Join-Path $root "_tests\test_boot_silent.py") *> (Join-Path $env:TEMP "lr_boot.txt")
$bootCode = $LASTEXITCODE
Get-Content (Join-Path $env:TEMP "lr_boot.txt") -Encoding UTF8 -ErrorAction SilentlyContinue |
Select-Object -Last 3
Record "开机零窗口" ($bootCode -eq 0)
# ---------------------------------------------------------------- 2
Banner "2/7 手表屏幕布局适配核算 (490x602)"
$stageOut = & python (Join-Path $root "_tests\check_layout.py") 2>&1
$stageCode = $LASTEXITCODE
$stageOut | Select-Object -Last 3
Record "手表布局适配" ($stageCode -eq 0)
# ---------------------------------------------------------------- 1z
Banner "1z/7 手表分页完整性(防白屏:方法定义了必须被调用)"
& python (Join-Path $root "_tests\test_no_orphan_methods.py") 2>&1 |
Select-Object -Last 6
Record "手表分页完整性" ($LASTEXITCODE -eq 0)
# ---------------------------------------------------------------- 2a2
Banner "2a2/7 APK 内容(诊断已合并进主程序)"
& python (Join-Path $root "_tests\test_apk_contents.py") *> (Join-Path $env:TEMP "lr_apk.txt")
$apkCode = $LASTEXITCODE
Get-Content (Join-Path $env:TEMP "lr_apk.txt") -Encoding UTF8 -ErrorAction SilentlyContinue |
Select-Object -Last 2
Record "APK 内容" ($apkCode -eq 0)
# ---------------------------------------------------------------- 2a
Banner "2a/7 网络调优(MTU 修复)"
# 这是可用率从 48% 提到 100% 的关键,而且容易被后人误改
# (比如"顺手把接口 MTU 也降一点" —— 实测那样会掉到 50%)。
& python (Join-Path $root "_tests\test_net_tune.py") 2>&1 |
Select-Object -Last 4
Record "网络调优(MTU)" ($LASTEXITCODE -eq 0)
# ---------------------------------------------------------------- 2b
Banner "2b/7 投图显示方式(屏幕中间 / 全屏 / 自定义大小)"
Stop-Strays
$imgLog = Join-Path $env:TEMP "lr_imgmode.txt"
& python (Join-Path $root "_tests\hidden_desktop.py") -- python (Join-Path $root "_tests\test_image_modes.py") *> $imgLog
$imgCode = $LASTEXITCODE
if ($imgCode -eq 0) {
Get-Content $imgLog -Encoding UTF8 -ErrorAction SilentlyContinue |
Select-Object -Last 3
} else {
Write-Host "失败输出:" -ForegroundColor Red
Get-Content $imgLog -Encoding UTF8 -ErrorAction SilentlyContinue |
Where-Object { $_ -match "FAIL|通过|Traceback|Error" }
}
Record "投图显示方式" ($imgCode -eq 0)
# ---------------------------------------------------------------- 3
Banner "3/7 屏幕共享可见提示(真实界面 + Win32 窗口枚举)"
Stop-Strays
$stageOut = & python (Join-Path $root "_tests\hidden_desktop.py") -- python (Join-Path $root "_tests\test_indicator_visible.py") 2>&1
$stageCode = $LASTEXITCODE
$stageOut | Select-Object -Last 3
Record "屏幕共享强制提示" ($stageCode -eq 0)
# ---------------------------------------------------------------- 2b
Banner "2b/7 中转 chunked 请求体(防连接错位)"
# Cloudflare 转发时可能用 Transfer-Encoding: chunked。
# 中转原来只读 Content-Length,残留的 "0\r\n\r\n" 会被当成下一个请求行,
# 报 "Bad request syntax ('0')",整条连接错位 -> 隧道大面积 502。
$stageOut = & python (Join-Path $root "_tests\test_chunked_body.py") 2>&1
Show-Fails $stageOut
Record "中转 chunked 请求体" ($LASTEXITCODE -eq 0)
# ---------------------------------------------------------------- 3a
Banner "3a/7 局域网直连路径(不经中转)"
# 手表和电脑同一个 WiFi 时走这条:UDP 广播找到电脑 + TCP 直连。
# 这条路径完全不碰中转,所以单独盯一下。
$stageOut = & python (Join-Path $root "_tests\test_lan_path.py") 2>&1
Show-Fails $stageOut
Record "局域网直连路径" ($LASTEXITCODE -eq 0)
# ---------------------------------------------------------------- 3a2
Banner "3a2/9 局域网优化(网段掩码 / 发现重发 / 回切)"
# 盯四件事:
# 1. 网段判断按网卡真实掩码,不再写死 /24(/16 网络下能直连却去走中转)
# 2. 广播发现重发 + 多网段(原来只发一次、只探一个网段)
# 3. 走中转之后能定期回切局域网
# 4. 默认中转地址为空白时,局域网照样能配对、能执行命令
$stageOut = & python (Join-Path $root "_tests\test_lan_optimize.py") 2>&1
Show-Fails $stageOut
Record "局域网优化" ($LASTEXITCODE -eq 0)
# ---------------------------------------------------------------- 3a3
Banner "3a3/9 手表局域网切换逻辑(源码断言)"
$stageOut = & python (Join-Path $root "_tests\test_watch_lan_switch.py") 2>&1
Show-Fails $stageOut
Record "手表局域网切换" ($LASTEXITCODE -eq 0)
# ---------------------------------------------------------------- 3a3b
Banner "3a3b/7 中转默认必须为空(防内置作者服务器)"
$stageOut = & python (Join-Path $root "_tests\test_relay_default_blank.py") 2>&1
Show-Fails $stageOut
Record "中转默认空白" ($LASTEXITCODE -eq 0)
# ---------------------------------------------------------------- 3a3c
Banner "3a3c/7 后台抢救报告分区(鉴权 + 不缓存 + 防穿越)"
$stageOut = & python (Join-Path $root "_tests\test_rescue_section.py") 2>&1
Show-Fails $stageOut
Record "抢救报告分区" ($LASTEXITCODE -eq 0)
# ---------------------------------------------------------------- 3a4
Banner "3a4/9 编码与换行(.java/.py 必须 UTF-8 无 BOM + LF)"
# 踩过的坑:edit 工具会把行尾写成 CRLF,而仓库约定是 LF。
# 这个检查专门盯住,免得改动悄悄把整个文件的行尾换掉。
$stageOut = & python (Join-Path $root "_tests\check_encoding.py") 2>&1
Show-Fails $stageOut
Record "编码与换行" ($LASTEXITCODE -eq 0)
# ---------------------------------------------------------------- 3a7
Banner "3a7/9 Python 语法(必须用 compile,不能用 ast.parse)"
# 这一轮被同一件事咬了三次:补丁脚本把 import 插到 from __future__
# 前面,整个文件就废了。而更糟的是 ast.parse 查不出来(它只解析,
# 不做编译期检查)—— 前几次「全仓语法检查 0 问题」都是假绿。
$stageOut = & python (Join-Path $root "_tests\check_python_syntax.py") 2>&1
Show-Fails $stageOut
Record "Python 语法检查" ($LASTEXITCODE -eq 0)
# ---------------------------------------------------------------- 3a8
Banner "3a8/9 版本号一致性(防漏改)"
$stageOut = & python (Join-Path $root "_tests\check_version_consistency.py") 2>&1
Show-Fails $stageOut
Record "版本号一致性" ($LASTEXITCODE -eq 0)
# ---------------------------------------------------------------- 3a9
Banner "3a9/9 Windows 7 兼容(学校电脑是 Win7)"
# 用户报:学校电脑是 Windows 7,装完报"dll 缺失"。
#
# 根因不是漏打包,是 **Python 版本越界**:
# Python 3.8 -> 最后一个支持 Windows 7 的版本
# Python 3.9+ -> 只支持 Windows 8 及以上
# Python 3.9 起用到 api-ms-win-core-path-l1-1-0.dll 里的
# PathCchCanonicalizeEx,而这个 DLL **Win7 上根本不存在**,
# 也没法用 api-set 转发 —— 那个 DLL 放进去也没用。
#
# 这个关卡盯三件事:
# 1. 内嵌的是 python38.dll(不是 python311.dll 之类)
# 2. **没有** api-ms-win-core-path-l1-1-0.dll
# 3. exe 是 32 位(Win7 的 32/64 位系统都能跑;64 位在 32 位系统上起不来)
# 打包脚本 windows\build_exe.ps1 里也调了同一个检查,
# 不过就直接拒绝出包 —— 这里是回归层面的双保险。
$stageOut = & python (Join-Path $root "_build\check_win7_compat.py") 2>&1
Show-Fails $stageOut
Record "Windows 7 兼容" ($LASTEXITCODE -eq 0)
# ---------------------------------------------------------------- 3a10
Banner "3a10/9 静默自动更新链路"
# 用户要求:"手表端点击一个更新按钮,电脑端就可以自动进行静默更新,
# 其他什么设置都不变 …… 不弹任何窗口出来"
# 这条链是:中转 /v1/update/latest -> 被控端 updater.py 下载校验
# -> 分离 .cmd 帮手 -> 静默装 -> 重启。
#
# 这里验协议常量、版本比较(按数字段,不是字符串)、下载校验、
# 以及**服务端 dispatch 的结构没被补丁弄坏**。
$stageOut = & python (Join-Path $root "_tests\test_update_flow.py") 2>&1
Show-Fails $stageOut
Record "静默自动更新" ($LASTEXITCODE -eq 0)
# ---------------------------------------------------------------- 3a11
Banner "3a11/9 远程鼠标 + 一键显示桌面"
# 鼠标注入是最"有力量"的功能,参数校验写错的话表现是"手表点了没反应",
# 教室里没法排查。这里盯坐标归一化(除以 宽-1 那条规则)、
# 滚轮夹取、Win+D 的事件顺序、以及非法参数必须被拒。
$stageOut = & python (Join-Path $root "_tests\test_mouse_control.py") 2>&1
Show-Fails $stageOut
Record "远程鼠标控制" ($LASTEXITCODE -eq 0)
# ---------------------------------------------------------------- 3a12
Banner "3a12/9 后台更新按钮 + 弹窗位置/大小"
# 用户要求:后台能点一下让电脑自己更新;弹文字窗口位置和大小可自定义。
# 后台那条链路横跨三端(JS -> 中转 -> 被控端),错了的表现是
# "我点了更新,电脑没反应",所以几个边界必须卡死:
# 设备不存在 / 离线 / 重复点 / 在线。
$stageOut = & python (Join-Path $root "_tests\test_admin_update.py") 2>&1
Show-Fails $stageOut
Record "后台更新按钮" ($LASTEXITCODE -eq 0)
$stageOut = & python (Join-Path $root "_tests\test_notice_options.py") 2>&1
Show-Fails $stageOut
Record "弹窗位置和大小" ($LASTEXITCODE -eq 0)
# ---------------------------------------------------------------- 3a13
Banner "3a13/9 手表快捷栏 + 允许新设备连接"
# 用户要求:"手表端不管干什么,上面都有一栏,栏里的按钮可以在设置页
# 自定义,最多可以加 4 个,其中有一个按钮不能改,
# 功能就是一键返回桌面"。
#
# 四个地方最容易漏,漏了都不会报错、只会让人困惑:
# 1. buildQuickBar 没挂进 onCreate -> 栏根本不显示
# 2. 没加进 showOnly 的数组 -> 面板永远压在上面
# 3. 第 4 格做成了可配置 -> 用户找半天"显示桌面"没了
# 4. 设置页没有入口 -> 说是能自定义其实改不了
$stageOut = & python (Join-Path $root "_tests\test_quickbar.py") 2>&1
Show-Fails $stageOut
Record "手表快捷栏" ($LASTEXITCODE -eq 0)
# ---------------------------------------------------------------- 3a14
Banner "3a14/9 语音播报降级链(修 Win7 上用不了)"
# 用户反馈:"我 win7 上的语音播报好像不能用,你再试一下别的版本能不能用"。
#
# 原来只有一条路(powershell + System.Speech),而 Win7 上
# PowerShell 2.0 + 中文 + PATH 三样都可能出问题。改成降级链之后必须保证:
# * cscript/VBScript 排最前(从 Win98 就有,SAPI5 是系统组件,不用装东西)
# * 前一条失败要接着试下一条,不是试一条就放弃
# * 全失败要**报错**,不能像原来那样 Popen 不等待、明明没出声却说"已播报"
# * VBScript 用 ANSI 写(UTF-8 的话 cscript 读出来中文是乱码)
$stageOut = & python (Join-Path $root "_tests\test_tts.py") 2>&1
Show-Fails $stageOut
Record "语音播报降级" ($LASTEXITCODE -eq 0)
# ---------------------------------------------------------------- 3a5
Banner "3a5/9 局域网/中转对比基准(防测试自己骗人)"
# 这个基准一度是红的,而且报告里写着「这是中转链路的产品 bug」——
# 实际是基准自己有两个 bug:两条路用了同一个配对码、
# 以及 attach 用了旧码(打到已经死掉的设备上)。
# 一个已经修好的产品因此背了黑锅,而且症状(连接超时)
# 看起来完全像产品问题,极难自己发现。
$stageOut = & python (Join-Path $root "_tests\test_lan_bench_guard.py") 2>&1
Show-Fails $stageOut
Record "局域网对比基准" ($LASTEXITCODE -eq 0)
# ---------------------------------------------------------------- 3a6
Banner "3a6/9 管理页 JS 语法(防按钮全死)"
# 踩过两次的坑:admin.py 的 ADMIN_JS 是普通三引号字符串,
# 里面写 \n 会被 Python 变成真实换行;而 JS 的行注释 // 不能跨行,
# 一断行整段 <script> 语法错误 -> 页面上所有按钮失效。
# 第二次尤其讽刺:注释为了举例说明「不能写 \n」,自己写了一个 \n,
# 于是注释被断成两行,把整段 JS 弄坏了。
# 原来那个「管理页 JS 合法性」阶段只查 token 在不在,查不出语法错 ——
# 「测过」和「能用」是两件事。
$stageOut = & python (Join-Path $root "_tests\test_admin_js_syntax.py") 2>&1
Show-Fails $stageOut
Record "管理页 JS 语法" ($LASTEXITCODE -eq 0)
# ---------------------------------------------------------------- 3b
Banner "3b/7 Python 静态检查(pyflakes)"
# 这一层能抓到"运行到某行才炸"的问题。
# 踩过的坑:check_code_available() 用了没导入的 DEFAULT_RELAY_URL,
# 结果装到最后一步弹 NameError —— 而回归全绿、编码审计也过。
$stageOut = & python (Join-Path $root "_tests\audit_python_names.py") 2>&1
Show-Fails $stageOut
Record "Python 静态检查" ($LASTEXITCODE -eq 0)
# ---------------------------------------------------------------- 3b3
Banner "3b3/7 安装包脚本语法(Inno 的注释陷阱)"
# 踩过的坑:Inno 的 [Code] 解析器**连注释内容也处理**。
# 注释里写 [区段名] 报"无效的区段标记",写 {常量} 报 "Syntax error",
# 都是直接编译失败。这个检查专门拦这类。
$stageOut = & python (Join-Path $root "_build\check_iss.py") 2>&1
Show-Fails $stageOut
Record "安装包脚本语法" ($LASTEXITCODE -eq 0)
# ---------------------------------------------------------------- 3b4
Banner "3b4/7 卸载逻辑(不能杀自己 / 不能阻塞)"
# 踩过的坑:
# 1. taskkill /IM ykxmz.exe 把正在卸载的进程自己杀了 -> 删自启没执行
# 2. Popen 不重定向 stdio -> capture_output 等管道 EOF -> "卡死"
# 3. input() 在无 stdin 时永久阻塞 -> 控制面板卸载卡死
# 4. keep_dir 缺失 -> 自删和 Inno 卸载器抢目录
$stageOut = & python (Join-Path $root "_build\test_uninstall_logic.py") 2>&1
Show-Fails $stageOut
Record "卸载逻辑" ($LASTEXITCODE -eq 0)
# ---------------------------------------------------------------- 3b2
Banner "3b2/7 管理页网络体检"
# 用户看到"连不上"时最需要知道:隧道几条连接、公网通不通、延迟多少。
# 中转和 cloudflared 同机,能直接读它的指标。
$stageOut = & python (Join-Path $root "_tests\test_admin_netcheck.py") 2>&1
Show-Fails $stageOut
Record "管理页网络体检" ($LASTEXITCODE -eq 0)
# ---------------------------------------------------------------- 3b5
Banner "3b5/7 安装包完整周期(装 -> 验 -> 卸 -> 再验)"
# 这一步真的装一遍、再卸一遍。踩过的四个卡死 bug 全靠它才发现:
# input() 无 stdin 阻塞 / Popen 不重定向 stdio /
# taskkill 把自己杀了 / 关不掉管理员权限的旧实例导致安装回滚。
# 注意:需要管理员权限,且会真的往 Program Files 装东西。
$stageOut = & python (Join-Path $root "_build\test_installer_cycle.py") 2>&1
Show-Fails $stageOut
Record "安装包完整周期" ($LASTEXITCODE -eq 0)
# ---------------------------------------------------------------- 3b6
Banner "3b6/7 网页响应头(Content-Type / HEAD)"
# 踩过的坑:重做管理页时 _send() 没传 content_type,
# 默认成了 application/json —— 浏览器把整页 HTML 当纯文本显示,
# 用户看到的是一屏源码。
# 更糟的是当时没发现:只验了 HTTP 200 和字节数,两个都对。
# **200 不等于页面能看。**
$stageOut = & python (Join-Path $root "_build\test_response_headers.py") 2>&1
Show-Fails $stageOut
Record "网页响应头" ($LASTEXITCODE -eq 0)
# ---------------------------------------------------------------- 3b7
Banner "3b7/7 对外材料清洁度(不许出现管理后台)"
# 用户规矩:对外公开的内容里只能提官网,不能提中转管理后台。
# 文档没人看,所以把规矩变成会红的断言。
$stageOut = & python (Join-Path $root "_build\test_public_clean.py") 2>&1
Show-Fails $stageOut
Record "对外材料清洁度" ($LASTEXITCODE -eq 0)
# ---------------------------------------------------------------- 3c
Banner "3c/7 中转管理端批量删除"
$stageOut = & python (Join-Path $root "_tests\test_admin_batch_delete.py") 2>&1
Show-Fails $stageOut
Record "中转批量删除" ($LASTEXITCODE -eq 0)
# ---------------------------------------------------------------- 3c2
Banner "3c2/7 中转删除设备(含孤儿绑定自愈)"
# 这个测试原来**不在套件里**(僵尸),所以一直是红的没人知道:
# * 它抓的是 get("/"),而管理页早就搬到 /zhongzhuanzhan 了
# -> 后面三条"找不到删除按钮"
# * 它断言"孤儿绑定会挡着配对码",但那个行为已经被**有意修好**了
# (snapshot() 现在会把指向已不存在设备的码当废码扔掉并落盘)
# -> 用户报过的"设备下线了码还占着"问题已经不需要手动清场
# 修好之后 31/31 通过,收进套件。顺带把「孤儿自愈」也纳入回归。
$stageOut = & python (Join-Path $root "_tests\test_admin_delete.py") 2>&1
Show-Fails $stageOut
Record "中转删除设备" ($LASTEXITCODE -eq 0)
# ---------------------------------------------------------------- 3d
Banner "3d/7 管理页 JS 合法性(防幽灵按键)"
# 管理页的 JS 是拼出来的,字符串里混进真换行会让整段脚本报错,
# 按钮点了没反应。这个阶段逐字符扫描验证。
$stageOut = & python (Join-Path $root "_tests\test_admin_js.py") 2>&1
Show-Fails $stageOut
Record "管理页 JS 合法性" ($LASTEXITCODE -eq 0)
# ---------------------------------------------------------------- 3e
Banner "3e/7 管理页设备控制(接入开关 + 改配对码)"
# 两个功能各自最容易写错的地方:
# * 判断"新设备"必须按 deviceId,不能按配对码 ——
# 否则管理员给老设备改过码之后,老设备会被开关挡在外面
# * 改码按钮不能带 data-device ——
# 现有"单台删除"按 button.danger[data-device] 绑,
# 带上就会被当成删除按钮,点改码变删设备
$stageOut = & python (Join-Path $root "_tests\test_admin_device_control.py") 2>&1
Show-Fails $stageOut
Record "管理页设备控制" ($LASTEXITCODE -eq 0)
# ---------------------------------------------------------------- 4b
Banner "4b/7 手表 Activity 骨架(防白屏)"
# onCreate 被删掉过一次:照样编译通过,但打开就是白屏。
# 这一阶段专门断言生命周期骨架完整。
$stageOut = & python (Join-Path $root "_tests\test_watch_skeleton.py") 2>&1
Show-Fails $stageOut
Record "手表 Activity 骨架" ($LASTEXITCODE -eq 0)
# ---------------------------------------------------------------- 4c
Banner "4c/7 编译整个手表 App(含界面代码)"
# 这一步不能省:只编协议层的话,MainActivity.java 里的错误
# 根本检查不到 —— 改界面时删坏过字段声明,协议层照样通过,
# 回归全绿,结果 APK 编不出来。用 android.jar 做 classpath 编全量。
if (-not (Test-Path $androidJar)) {
Write-Host "找不到 android.jar: $androidJar" -ForegroundColor Red
Record "手表 App 整体编译" $false
} else {
$appOut = Join-Path $root "_tests\out_app"
if (Test-Path $appOut) { Remove-Item -Recurse -Force $appOut }
New-Item -ItemType Directory -Force -Path $appOut | Out-Null
$appSrcs = Get-ChildItem (Join-Path $root "android\src") -Recurse -Filter "*.java" |
ForEach-Object { $_.FullName }
$env:JAVA_TOOL_OPTIONS = "-Duser.language=en -Duser.country=US"
& $javac --release 8 -encoding UTF-8 -cp $androidJar -d $appOut $appSrcs 2>&1 |
Select-String -Pattern "error" | Select-Object -First 8 | ForEach-Object { Write-Host " $_" }
$appOk = ($LASTEXITCODE -eq 0)
$env:JAVA_TOOL_OPTIONS = ""
Write-Host $(if ($appOk) { " App 编译通过" } else { " App 编译失败" })
Record "手表 App 整体编译" $appOk
}
# ---------------------------------------------------------------- 5
Banner "5/7 编译 Java 协议层"
if (Test-Path $out) { Remove-Item -Recurse -Force $out }
New-Item -ItemType Directory -Force -Path $out | Out-Null
# Transport / RelayTransport 是跨网络用的:把"怎么传字节"抽出来,
# 这样 RemoteClient 的协议逻辑不用改就能走中转。
& $javac --release 8 -encoding UTF-8 -d $out `
(Join-Path $root "android\src\com\lanremote\watch\Json.java") `
(Join-Path $root "android\src\com\lanremote\watch\B64.java") `
(Join-Path $root "android\src\com\lanremote\watch\DiscoveryCore.java") `
(Join-Path $root "android\src\com\lanremote\watch\Transport.java") `
(Join-Path $root "android\src\com\lanremote\watch\RelayTransport.java") `
(Join-Path $root "android\src\com\lanremote\watch\TlsCompat.java") `
(Join-Path $root "android\src\com\lanremote\watch\RemoteClient.java") `
(Join-Path $root "_tests\java\JavaClientTest.java") 2>&1 | Out-Null
$compiled = ($LASTEXITCODE -eq 0)
Write-Host $(if ($compiled) { "编译通过" } else { "编译失败" })
Record "Java 协议层编译" $compiled
if (-not $compiled) { return }
# ---------------------------------------------------------------- 6
Banner "6/7 Java <-> 桩服务端 跨语言联调"
Stop-Strays
$stubData = Join-Path $root "_tests\_stubdata"
Remove-Item -Recurse -Force $stubData -ErrorAction SilentlyContinue
$codeFile = Join-Path $env:TEMP "lr_verify_code.txt"
Remove-Item -Force $codeFile -ErrorAction SilentlyContinue
$stub = Start-Process -FilePath "python" `
-ArgumentList "_tests\run_stub_server.py", "--port", "8799",
"--code-file", $codeFile, "--data-dir", $stubData `
-WorkingDirectory $root -PassThru -WindowStyle Hidden `
-RedirectStandardOutput (Join-Path $env:TEMP "lr_verify_stub.txt") `
-RedirectStandardError (Join-Path $env:TEMP "lr_verify_stub_err.txt")
$deadline = (Get-Date).AddSeconds(25)
while (-not (Test-Path $codeFile) -and (Get-Date) -lt $deadline) { Start-Sleep -Milliseconds 300 }
if (-not (Test-Path $codeFile)) {
Write-Host "桩服务端未就绪" -ForegroundColor Red
Get-Content (Join-Path $env:TEMP "lr_verify_stub_err.txt") -ErrorAction SilentlyContinue
Stop-Process -Id $stub.Id -Force -ErrorAction SilentlyContinue
Record "Java <-> 桩服务端" $false $true
} else {
# 先把退出码存下来再走管道。
# 直接 & java ... | Select-Object 然后读 $LASTEXITCODE 是不可靠的 ——
# 实测测试明明全过了,Record 却记成 FAIL(管道会干扰 $LASTEXITCODE)。
$javaOut = & $java "-Dfile.encoding=UTF-8" -cp $out JavaClientTest "127.0.0.1" 8799 "246813" "stub" 2>&1
$javaCode = $LASTEXITCODE
$javaOut | Select-Object -Last 4
Record "Java <-> 桩服务端" ($javaCode -eq 0) $true
Stop-Process -Id $stub.Id -Force -ErrorAction SilentlyContinue
}
# ---------------------------------------------------------------- 7
Banner "7/7 Java <-> 打包后的 EXE 端到端联调"
Stop-Strays
if (-not (Test-Path $exe)) {
Write-Host "未找到打包后的 EXE: $exe" -ForegroundColor Yellow
Write-Host "请先运行: powershell -File windows\build_exe.ps1" -ForegroundColor Yellow
Record "Java <-> 打包 EXE" $false
} else {
$appDir = Split-Path -Parent $exe
Remove-Item -Recurse -Force (Join-Path $appDir "data"), (Join-Path $appDir "received") -ErrorAction SilentlyContinue
# --add-device:静默运行 + 开放配对(默认流程会弹设置窗口,测试会卡住)
$proc = Start-Process -FilePath $exe -ArgumentList "--port", "8801", "--add-device", "10" -PassThru
Start-Sleep -Seconds 7
$settings = Join-Path $appDir "data\settings.json"
$deadline = (Get-Date).AddSeconds(25)
while (-not (Test-Path $settings) -and (Get-Date) -lt $deadline) { Start-Sleep -Milliseconds 400 }
if (-not (Test-Path $settings)) {
Write-Host "EXE 未生成配置,启动失败" -ForegroundColor Red
Stop-Process -Id $proc.Id -Force -ErrorAction SilentlyContinue
Record "Java <-> 打包 EXE" $false
} else {
$cfg = Get-Content $settings -Raw | ConvertFrom-Json
$cfg.image_timeout = 4
$cfg | ConvertTo-Json | Set-Content $settings -Encoding UTF8
Write-Host "EXE 配对码: $($cfg.pairing_code)"
# 同上:先存退出码,别让管道把它冲掉
$javaOut = & $java "-Dfile.encoding=UTF-8" -cp $out JavaClientTest "127.0.0.1" 8801 $cfg.pairing_code "real" 2>&1
$javaCode = $LASTEXITCODE
$javaOut | Select-Object -Last 4
Record "Java <-> 打包 EXE" ($javaCode -eq 0) $true
Stop-Process -Id $proc.Id -Force -ErrorAction SilentlyContinue
Start-Sleep -Seconds 1
Write-Host "`nEXE 接收到的文件:"
Get-ChildItem (Join-Path $appDir "received") -ErrorAction SilentlyContinue |
ForEach-Object { Write-Host (" {0,8:N0} B {1}" -f $_.Length, $_.Name) }
}
}
Stop-Strays
# ---------------------------------------------------------------- 汇总
# 收尾:把测试过程中弹出来的窗口关掉(用户要求)
Close-TestWindows
Banner "验证汇总"
$script:results | Format-Table -AutoSize
$failed = ($script:results | Where-Object { $_.结果 -eq "FAIL" }).Count
if ($failed -eq 0) {
Write-Host "全部通过" -ForegroundColor Green
} else {
Write-Host "$failed 项失败" -ForegroundColor Red
}
exit $failed