Files
ykxmz/_tests/verify_all.ps1
T
xmz7891 fd8be87209 2.1.8:修 Win7 语音播报 + 快捷栏按钮做大且4格全能换
用户这一轮报了两个问题,都修了。

== 一、Win7 上语音播报不能用 ==

用户原话:"我 win7 上的语音播报好像不能用,你再试一下别的版本能不能用"

**原来只有一条路**:

    powershell -NoProfile -WindowStyle Hidden -Command
        "Add-Type -AssemblyName System.Speech; $s = New-Object ...; $s.Speak('...')"

这在 Win7 上有三个坑,任何一个都能让它彻底不出声:

  1. **`powershell` 只按名字找**
     Win7 的 powershell.exe 在 `%SystemRoot%\System32\WindowsPowerShell\v1.0\`。
     精简版系统、或从服务/计划任务拉起时 PATH 里可能没有 ——
     CreateProcess 按名字找不到时是**静默失败**的。

  2. **Win7 默认 PowerShell 2.0,它按 ANSI 代码页解析 `-Command`**
     PS 3.0 起才按 UTF-16 处理。中文经 `-Command` 传进去容易乱码 ——
     表现是念出来是乱码,或者干脆不念。

  3. **`System.Speech` 需要 .NET Framework 3.0+**
     而系统自带的 **SAPI5** 从 Windows XP 就有,**根本不需要 .NET**。

**改成多方案降级**,按"依赖越少越优先"排:

    1. cscript + VBScript + SAPI5      <- Win98 起就有,不用装任何东西
    2. wscript + VBScript + SAPI5      <- 上一条被安全软件拦时的备选
    3. powershell + SAPI5 (COM)        <- 绕开 .NET
    4. powershell + System.Speech      <- 原来的做法,改用绝对路径
    5. powershell(PATH) + System.Speech <- 最原始的,兜底

一条通了**记住它**,以后直接用(不用每次从头试)。

**顺带修掉一个"骗人"的行为**:原来 `say()` 用 `Popen` 不等进程结束,
失败了也返回"已播报" —— 明明没出声却报成功。
现在等退出码,**全失败会明确报错并列出试过哪几条**。
代价是同步等待(念多久等多久),但"报成功其实没出声"比"慢一点"糟糕得多。

还加了 `_build/diag_tts.py`:在一台机器上把 5 条方案全试一遍并报告
哪条能用 —— 下次再有"某台机器上不行",跑一下就知道。

VBScript 的细节:**必须用 ANSI(mbcs) 写**,写成 UTF-8 的话
cscript 读出来中文是乱码(VBScript 的老规矩,很隐蔽)。
长文本还要分句 —— VBScript 单行有长度上限。

== 二、快捷栏按钮太小 + 4 格都要能换 ==

用户原话:"屏幕最上面这个按钮有些按不到,按钮太小了,做大一点,
           然后呢,这个每每个四格算换一下吧,第一个第二个第三个
           第四个都能换东西"

  * **按钮做大**:30dp -> 46dp(接近 Android 建议的最小可点区域 48),
    字号 11 -> 14。30dp 在表盘上手指按上去一半在栏外。
    **栏高做成常量 QUICK_BAR_DP,栏高 / 面板 topMargin / addView
    三处用同一个值** —— 分三处写的话改一个忘一个,
    表现是"内容被盖住"或"上面空一条"。

  * **4 格全部可自定义**。用户最早说"其中有一个按钮不能改,
    功能就是一键返回桌面",第一版就按"第 4 格写死 desktop"做的。
    现在他改主意了 —— 所以 desktop 变成**普通可选动作**,
    默认仍放第 4 格(习惯不用改),但想换也能换。
    可选动作从 7 个变成 8 个。

  **教训(写进测试注释了)**:别把"用户当时的一句话"当成永久约束
  焊进结构里;真是约束的话,注释里要写清是什么时候的要求,
  下次他改主意时才知道该动哪儿。

== 三、修一个"测试自己没擦干净"造成的假 bug ==

全量回归报:

    [FAIL] 配对码正确写入  实际 '(旧的残留值)'

而 `(旧的残留值)` 是**上一次测试遗留**的配对码 —— 数据目录
`C:\ProgramData\ykxmz\data` 在卸载时**故意不删**(那里存着用户的
中转地址、配对码等,卸载不该删),所以机器上会留着上一轮的
settings.json。测试装的时候虽然传了 `/CODE=517284`,但环境没擦干净时
就可能读到旧值 —— 表现成"安装包有 bug"。

**修法**:`test_installer_cycle.py` 加一步"装前先清残留"
(正规卸载 -> 杀进程 -> 删安装目录 -> **删数据目录**)。
数据目录那一步是关键 —— 卸载器不删它。

== 四、其他 ==

  * `docs/项目规矩.md` 第一节改成**按预案执行**:
    预案一 = 做完测试没问题就上传;预案二 = 做完测试但不上传,直接给用户。
    用户没说预案几时默认按预案一,并在交付时说明。
  * 顺手把那份文档里的老域名 `relay.xxmxxs.top` 改成现在的
    `yk.xxmxxs.top`(老服务器早就退役了)。
  * 修掉自己造的重复定义:往 actions.py 插降级链时,
    `list_voices` / `say` 各插了两份(pyflakes 报 redefinition)。
  * 一次危险的误操作:`Path.write_text(..., newline='\n')` 会**先截断文件
    再校验参数**,参数非法时文件已经空了 —— 我用它把
    `_tests/test_quickbar.py` 清空了。已重写。
    **以后不给 write_text 传 newline。**

== 验证 ==

    全量回归(21 关)全部 PASS
    新增关卡:3a13 手表快捷栏 / 3a14 语音播报降级
    安装包周期 23 项全过(装 -> 验 -> 卸 -> 再验)
    Java 编译通过(android-34, --release 8)
    本机实测语音播报:cscript 那条通了(2.4 秒)
    全部产物重建,Win7 兼容校验通过
2026-10-09 23:30:05 +08:00

844 lines
42 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 = @()
# 当前会话是不是锁屏 / 没有交互桌面?
# 锁屏时截屏类测试**必然**失败(拿不到屏幕内容),但那不是代码问题。
# 不识别的话汇总表一片红,真问题反而被淹没。
#
# ⚠ 判断不能只看 OpenInputDesktop 成不成功 —— **锁屏时它同样成功**
# (那时输入桌面是 Winlogon)。实测漏判过一次:屏幕明明锁着,
# desktopAvailable 却是 true,于是 6/7、7/7 两个截屏阶段记成 FAIL,
# 把真正的结论淹掉了。
# 正确做法是拿输入桌面的**名字**:Default = 正常,Winlogon = 锁屏。
$script:desktopAvailable = $true
$script:desktopName = "(未检测)"
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);
[System.Runtime.InteropServices.DllImport("user32.dll", SetLastError=true, CharSet=System.Runtime.InteropServices.CharSet.Unicode)]
public static extern bool GetUserObjectInformationW(System.IntPtr h, int idx, System.Text.StringBuilder pv, int len, ref int need);
public static string InputDesktopName() {
System.IntPtr h = OpenInputDesktop(0, false, 0x0001);
if (h == System.IntPtr.Zero) return "";
try {
System.Text.StringBuilder sb = new System.Text.StringBuilder(256);
int need = 0;
if (GetUserObjectInformationW(h, 2, sb, 512, ref need)) return sb.ToString();
return "";
} finally { CloseDesktop(h); }
}
public static bool HasDesktop() {
string n = InputDesktopName();
if (n.Length == 0) return false;
if (n.Equals("Winlogon", System.StringComparison.OrdinalIgnoreCase)) return false;
return true;
}
'@ -ErrorAction Stop
$script:desktopName = [Ykxmz.Desk]::InputDesktopName()
if ($script:desktopName -eq "") { $script:desktopName = "(拿不到)" }
$script:desktopAvailable = [Ykxmz.Desk]::HasDesktop()
} catch {
$script:desktopName = "(检测失败: $($_.Exception.GetType().Name))"
$script:desktopAvailable = $true # 查不出来就当可用,别把失败藏起来
}
# 无论可不可用都打一行 —— 上次漏判就是因为什么都没打,无从判断。
Write-Host (" 输入桌面: {0} -> {1}" -f $script:desktopName,
$(if ($script:desktopAvailable) { "可用" } else { "不可用(锁屏)" })) `
-ForegroundColor $(if ($script:desktopAvailable) { "DarkGray" } else { "Yellow" })
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 {
# ⚠ 这里可能是 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