Files
ykxmz/_tests/verify_all.ps1
T
xmz7891 e611b07fb1 纠正一个传出去的错误结论:不是「运营商封了 UDP」
我在给部署脚本的指示里写过:

    实测这条线路上运营商对 UDP 有限制 ——
    12 个 Cloudflare edge IP:TCP 12/12 通、UDP 0/12 通

它照这个把注释写进了 relay/deploy.sh。

**但这个测量是无效的。** relay-stability 后来自己纠正了:
那个 UDP 探测包是手搓的简化包,格式不对,
Cloudflare 本来就不该回应它 —— 「0/12 无回应」什么都证明不了。

用 cloudflared --loglevel debug 跑临时实例,真相是:

    UDP Connectivity  region1/region2  PASS  QUIC connection successful
    SUMMARY: Environment is healthy. cloudflared will use 'quic'
    Registered tunnel connection        (握手 1 秒)

**UDP 完全通。**

真因是:QUIC 建连要 6+ 个包往返,而 TCP 的 SYN 只有 3 个包、
内核还会按 1s/2s/4s 激进重传。约 5.4%/包 丢包下:

    (1 - 0.054)^6 ≈ 72%     <- 和实测的 72% 对得上

所以 QUIC 的弱点在「连不上」,不在「连上之后不好」;
一旦连上,它没有 TCP 的跨流队头阻塞,反而更稳。

结论的**用途**没变(还是要两条协议一起跑),但留着错误的根因注释会:
  * 误导后人以为 UDP 被封 -> 可能做出「干脆别用 QUIC」的错误决定
  * 掩盖真正可优化的方向(QUIC 握手对丢包敏感是链路物理属性,
    只能靠 HTTP/2 兜底来绕,不是「运营商限制」)

改动:relay/deploy.sh 两处、_build/test_deploy_sh.sh 一处。

教训记一笔:**手搓协议包去探测可达性非常容易得出错误结论** ——
那个包格式不对,对方根本没义务回应。要么用官方工具的自检
(cloudflared 自己会打 UDP Connectivity PASS/FAIL),
要么别下结论。
2026-10-07 18:22:19 +08:00

650 lines
31 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 = @()
# 当前会话是不是锁屏 / 没有交互桌面?
# 锁屏时截屏类测试**必然**失败(拿不到桌面会话),
# 但那不是代码问题。不识别的话汇总表一片红,真问题反而被淹没。
$script:desktopAvailable = $true
try {
Add-Type -Namespace Ykxmz -Name Desk -MemberDefinition @'
[System.Runtime.InteropServices.DllImport("user32.dll", SetLastError=true)]
public static extern System.IntPtr OpenInputDesktop(int flags, bool inherit, int access);
[System.Runtime.InteropServices.DllImport("user32.dll")]
public static extern bool CloseDesktop(System.IntPtr h);
public static bool HasDesktop() {
System.IntPtr h = OpenInputDesktop(0, false, 0x0001);
if (h == System.IntPtr.Zero) return false;
CloseDesktop(h);
return true;
}
'@ -ErrorAction Stop
$script:desktopAvailable = [Ykxmz.Desk]::HasDesktop()
} catch {
$script:desktopAvailable = $true # 查不出来就当可用,别把失败藏起来
}
if (-not $script:desktopAvailable) {
Write-Host ""
Write-Host " ⚠ 检测到电脑处于锁屏状态。" -ForegroundColor Yellow
Write-Host " 截屏相关的测试会失败,这是环境限制,不是代码问题。" -ForegroundColor Yellow
Write-Host " 这些阶段会记为 SKIP 而不是 FAIL。解锁后重跑即可验证。" -ForegroundColor Yellow
}
function Record($name, $ok, $needsDesktop = $false) {
# needsDesktop 的阶段在锁屏时记 SKIP —— 避免误报把真问题淹没
if ($needsDesktop -and -not $script:desktopAvailable) {
$script:results += [pscustomobject]@{
项目 = $name; 结果 = "SKIP(锁屏)" }
return
}
$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 {
Write-Host " $($_.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
# ---------------------------------------------------------------- 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
Banner "1e/7 新增功能(电量/磁盘/性能/进程/剪贴板历史/安全边界)"
Stop-Strays
& 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\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\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\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\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\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/7 局域网优化(网段掩码 / 发现重发 / 回切)"
# 盯四件事:
# 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/7 手表局域网切换逻辑(源码断言)"
$stageOut = & python (Join-Path $root "_tests\test_watch_lan_switch.py") 2>&1
Show-Fails $stageOut
Record "手表局域网切换" ($LASTEXITCODE -eq 0)
# ---------------------------------------------------------------- 3a4
Banner "3a4/7 编码与换行(.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)
# ---------------------------------------------------------------- 3a5
Banner "3a5/7 局域网/中转对比基准(防测试自己骗人)"
# 这个基准一度是红的,而且报告里写着「这是中转链路的产品 bug」——
# 实际是基准自己有两个 bug:两条路用了同一个配对码、
# 以及 attach 用了旧码(打到已经死掉的设备上)。
# 一个已经修好的产品因此背了黑锅,而且症状(连接超时)
# 看起来完全像产品问题,极难自己发现。
$stageOut = & python (Join-Path $root "_tests\test_lan_bench_guard.py") 2>&1
Show-Fails $stageOut
Record "局域网对比基准" ($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)
# ---------------------------------------------------------------- 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