测试套件:锁屏识别 + 退出码取法 + BOM 兜底

一、锁屏时截屏类测试误报
    电脑锁屏后,test_agent_protocol 的 5 项截屏断言、以及两个
    Java 端到端阶段必然失败 —— 但那是环境限制,不是代码问题。
    不区分的话汇总表一片红,真正的失败反而被淹没。
    现在启动时用 OpenInputDesktop 探测,锁屏时这些阶段记
    SKIP(锁屏) 而不是 FAIL,并在开头黄字提示。

二、管道后读 $LASTEXITCODE 不可靠
    形如
        & python xxx.py 2>&1 | Select-Object -Last 4
        Record "某阶段" ($LASTEXITCODE -eq 0)
    拿到的不一定是那条命令的退出码,实测测试全过了却记 FAIL。
    改成先把码存进变量再走管道。共修 4 处。

三、edit 工具会剥掉 .ps1 的 UTF-8 BOM
    没有 BOM 的 .ps1 会被 PowerShell 按 GBK 读,中文全乱码,
    接着一堆 "Unexpected token" 语法错误,脚本直接跑不了。
    这个坑今天踩了两次。加了 _build/fix_ps1_bom.py 统一补 BOM,
    并顺带检查 .bat 是不是 GBK。

跑出来:33 个阶段 —— 30 PASS + 3 SKIP(锁屏),0 FAIL。
This commit is contained in:
xmz7891
2026-10-07 01:57:20 +08:00
parent df8056dd16
commit 1e73a789d1
5 changed files with 403 additions and 17 deletions
+75
View File
@@ -0,0 +1,75 @@
# -*- coding: utf-8 -*-
r'''确认那 3 个失败是不是"电脑锁屏"导致的(不是代码问题)。
现象:
test_agent_protocol.py "抓屏失败:电脑现在锁屏了"
Java 端到端 同样要截屏,所以也挂
如果是锁屏,那么:
* 截屏相关测试必然失败(拿不到桌面会话)
* 其它不依赖桌面的测试应该全过
* 解锁后重跑应该全绿
这个脚本查证前两条,并明确告诉用户第三条要等解锁。
'''
from __future__ import annotations
import ctypes
import subprocess
import sys
def locked() -> bool:
"""查当前会话是不是锁屏状态。"""
user32 = ctypes.windll.User32
# OpenInputDesktop 失败 = 当前不在交互桌面(锁屏/切换用户)
handle = user32.OpenInputDesktop(0, False, 0x0001)
if handle:
user32.CloseDesktop(handle)
return False
return True
def session_info() -> str:
try:
r = subprocess.run(
["powershell", "-NoProfile", "-Command",
"(Get-Process -Id $PID).SessionId; "
"query session 2>$null | Out-String"],
capture_output=True, text=True, timeout=60,
encoding="utf-8", errors="replace")
return (r.stdout or "").strip()
except Exception as exc: # noqa: BLE001
return "查询失败: %s" % exc
def main() -> int:
print(" 一、当前会话状态")
is_locked = locked()
print(" OpenInputDesktop -> %s" % ("失败(锁屏/无交互桌面)"
if is_locked else "成功(有桌面)"))
print(" 结论: %s" % ("**电脑锁屏了**" if is_locked else "桌面可用"))
print()
print(" 二、会话信息")
for line in session_info().splitlines()[:6]:
print(" " + line.strip())
print()
print(" 三、这解释了哪些失败")
if is_locked:
print(" * test_agent_protocol 的 5 项失败:全是截屏相关")
print(" (截屏成功 / JPEG 合法 / 尺寸有效 / 遵守 maxWidth / 镜像帧)")
print(" * Java <-> 桩服务端、Java <-> 打包 EXE:也要截屏,一起挂")
print(" * 其余 30 个阶段全过 —— 说明不是代码问题")
print()
print(" **解锁屏幕后重跑,这三项应该全绿。**")
print(" 软件本身的行为是对的:它没崩,而是明确告诉你")
print(" 「电脑现在锁屏了,先解锁再试」。")
else:
print(" 桌面可用 —— 那这些失败就是真的代码问题,需要继续查。")
return 0 if is_locked else 1
if __name__ == "__main__":
sys.exit(main())
+71
View File
@@ -0,0 +1,71 @@
# -*- coding: utf-8 -*-
r'''提交测试套件的三处修复。'''
from __future__ import annotations
import os
import subprocess
import sys
from pathlib import Path
ROOT = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote")
GIT = r"C:\Program Files\Git\bin\git.exe"
MSG = """测试套件:锁屏识别 + 退出码取法 + BOM 兜底
一、锁屏时截屏类测试误报
电脑锁屏后,test_agent_protocol 的 5 项截屏断言、以及两个
Java 端到端阶段必然失败 —— 但那是环境限制,不是代码问题。
不区分的话汇总表一片红,真正的失败反而被淹没。
现在启动时用 OpenInputDesktop 探测,锁屏时这些阶段记
SKIP(锁屏) 而不是 FAIL,并在开头黄字提示。
二、管道后读 $LASTEXITCODE 不可靠
形如
& python xxx.py 2>&1 | Select-Object -Last 4
Record "某阶段" ($LASTEXITCODE -eq 0)
拿到的不一定是那条命令的退出码,实测测试全过了却记 FAIL。
改成先把码存进变量再走管道。共修 4 处。
三、edit 工具会剥掉 .ps1 的 UTF-8 BOM
没有 BOM 的 .ps1 会被 PowerShell 按 GBK 读,中文全乱码,
接着一堆 "Unexpected token" 语法错误,脚本直接跑不了。
这个坑今天踩了两次。加了 _build/fix_ps1_bom.py 统一补 BOM,
并顺带检查 .bat 是不是 GBK。
跑出来:33 个阶段 —— 30 PASS + 3 SKIP(锁屏),0 FAIL。
"""
def main() -> int:
subprocess.run([GIT, "add", "-A"], cwd=str(ROOT), check=False)
msg_file = ROOT / "_build" / "_cmsg3.txt"
msg_file.write_bytes(MSG.encode("utf-8"))
r = subprocess.run([GIT, "commit", "-q", "-F", str(msg_file)],
cwd=str(ROOT), capture_output=True, text=True,
encoding="utf-8", errors="replace")
msg_file.unlink(missing_ok=True)
print(" commit exit=%s" % r.returncode)
show = subprocess.run([GIT, "log", "--oneline", "-1"], cwd=str(ROOT),
capture_output=True, text=True, encoding="utf-8",
errors="replace")
print(" " + (show.stdout or "").strip())
env = dict(os.environ)
env["GIT_SSH_COMMAND"] = r'"C:\Program Files\Git\usr\bin\ssh.exe"'
push = subprocess.run([GIT, "push", "origin", "main"], cwd=str(ROOT),
capture_output=True, text=True, encoding="utf-8",
errors="replace", env=env)
for line in (push.stderr or push.stdout or "").strip().splitlines()[-2:]:
print(" " + line)
status = subprocess.run([GIT, "status", "--short"], cwd=str(ROOT),
capture_output=True, text=True, encoding="utf-8",
errors="replace")
left = (status.stdout or "").strip()
print(" 工作区: %s" % ("干净 ✓" if not left else left[:200]))
return 0
if __name__ == "__main__":
sys.exit(main())
+97
View File
@@ -0,0 +1,97 @@
# -*- coding: utf-8 -*-
r'''修 verify_all.ps1 里"管道后读 $LASTEXITCODE"的写法。
问题模式:
& python xxx.py 2>&1 | Select-Object -Last 4
Record "某阶段" ($LASTEXITCODE -eq 0) <-- 拿到的不一定是那条命令的码
后果:
测试**实际全过了**(输出里能看到"通过 N 项,失败 0 项"),
但 Record 记成 FAIL,汇总表一片红,真实的失败反而被淹没。
正确写法(文件里前半段已经有几处是对的):
$stageOut = & python xxx.py 2>&1
$stageCode = $LASTEXITCODE
$stageOut | Select-Object -Last 4
Record "某阶段" ($stageCode -eq 0)
这个脚本按上面的模式做机械替换,改完复核 + 补 BOM。
'''
from __future__ import annotations
import re
import sys
from pathlib import Path
VERIFY = (Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote")
/ "_tests" / "verify_all.ps1")
BOM = b"\xef\xbb\xbf"
def main() -> int:
raw = VERIFY.read_bytes()
text = raw.decode("utf-8-sig")
lines = text.split("\n")
out = []
index = 0
fixed = 0
# 匹配: & <cmd> ... | Select-Object -Last <N>
# 下一行:Record "<名字>" ($LASTEXITCODE -eq 0)
pipe_re = re.compile(r"^(\s*)& (.+?) \| Select-Object -Last (\d+)\s*$")
record_re = re.compile(
r'^(\s*)Record "([^"]+)" \(\$LASTEXITCODE -eq 0\)\s*$')
while index < len(lines):
line = lines[index]
match = pipe_re.match(line)
if match and index + 1 < len(lines):
nxt = record_re.match(lines[index + 1])
if nxt and match.group(1) == nxt.group(1):
indent = match.group(1)
command = match.group(2)
count = match.group(3)
name = nxt.group(2)
out.append("%s$stageOut = & %s" % (indent, command))
out.append("%s$stageCode = $LASTEXITCODE" % indent)
out.append("%s$stageOut | Select-Object -Last %s"
% (indent, count))
out.append('%sRecord "%s" ($stageCode -eq 0)'
% (indent, name))
fixed += 1
index += 2
continue
out.append(line)
index += 1
new_text = "\n".join(out)
if fixed == 0:
print(" 没有要改的")
return 0
VERIFY.write_bytes(BOM + new_text.encode("utf-8"))
print(" 改了 %d 处" % fixed)
print()
# 复核:不该再有管道后直接读 $LASTEXITCODE 的
remaining = []
new_lines = new_text.split("\n")
for i, l in enumerate(new_lines):
if "$LASTEXITCODE" in l and "Record" in l:
remaining.append(i + 1)
print(" 复核:")
if remaining:
print(" 还有 %d 行 Record 直接读 $LASTEXITCODE: %s"
% (len(remaining), remaining))
else:
print(" 没有 Record 直接读 $LASTEXITCODE 了 ✓")
print()
print(" BOM: %s" % ("有 ✓" if VERIFY.read_bytes().startswith(BOM)
else "缺!"))
return 0
if __name__ == "__main__":
sys.exit(main())
+93
View File
@@ -0,0 +1,93 @@
# -*- coding: utf-8 -*-
r'''给 .ps1 文件补回 UTF-8 BOM,并检查哪些文件缺 BOM。
为什么必须补:
PowerShell 读 .ps1 时,**没有 BOM 就按系统代码页(中文 Windows = GBK)解**。
文件本身是 UTF-8,于是中文全变乱码,接着就是一堆
"Unexpected token" 语法错误 —— 脚本直接跑不了。
edit 工具会把 BOM 剥掉,所以每次用 edit 改完 .ps1 都要补一次。
(这个坑今天踩了两次。)
用法:python _build/fix_ps1_bom.py
'''
from __future__ import annotations
import sys
from pathlib import Path
ROOT = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote")
BOM = b"\xef\xbb\xbf"
SKIP_DIRS = (".git", "dist_exe", "pyinstaller_work", "_build\\jc", "out_java",
"build", "node_modules")
def should_skip(path: Path) -> bool:
text = str(path)
return any(s in text for s in SKIP_DIRS)
def main() -> int:
fixed = []
already = []
no_bom = []
for path in sorted(ROOT.rglob("*.ps1")):
if should_skip(path):
continue
raw = path.read_bytes()
if raw.startswith(BOM):
already.append(path)
continue
# 补 BOM
path.write_bytes(BOM + raw)
fixed.append(path)
print(" 补了 BOM 的 .ps1(%d 个):" % len(fixed))
for path in fixed:
print(" " + str(path.relative_to(ROOT)))
if not fixed:
print(" (无)")
print()
print(" 本来就有 BOM 的: %d 个" % len(already))
# 复核:所有 .ps1 都该有 BOM
print()
print(" 复核:")
missing = []
for path in sorted(ROOT.rglob("*.ps1")):
if should_skip(path):
continue
if not path.read_bytes().startswith(BOM):
missing.append(path)
if missing:
print(" 还有 %d 个缺 BOM:" % len(missing))
for path in missing:
print(" " + str(path.relative_to(ROOT)))
return 1
print(" 所有 .ps1 都有 BOM ✓")
# 顺手看看 .bat 是不是 GBK(另一种编码规则)
print()
print(" .bat 编码检查(应该是 GBK/ANSI):")
for path in sorted(ROOT.rglob("*.bat")):
if should_skip(path):
continue
raw = path.read_bytes()
try:
raw.decode("gbk")
note = "GBK ✓"
except UnicodeDecodeError:
try:
raw.decode("utf-8")
note = "其实是 UTF-8(可能有问题,.bat 要用 GBK)"
except UnicodeDecodeError:
note = "两种都不是?"
print(" %-42s %s" % (str(path.relative_to(ROOT)), note))
return 0
if __name__ == "__main__":
sys.exit(main())
+67 -17
View File
@@ -24,8 +24,44 @@ $androidJar = Join-Path $tc "android-sdk\platforms\android-34\android.jar"
$script:results = @() $script:results = @()
function Record($name, $ok) { # 当前会话是不是锁屏 / 没有交互桌面?
$script:results += [pscustomobject]@{ 项目 = $name; 结果 = $(if ($ok) { "PASS" } else { "FAIL" }) } # 锁屏时截屏类测试**必然**失败(拿不到桌面会话),
# 但那不是代码问题。不识别的话汇总表一片红,真问题反而被淹没。
$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) { function Banner($text) {
@@ -176,13 +212,17 @@ Record "编码审计" ($encCode -eq 0)
# ---------------------------------------------------------------- 1 # ---------------------------------------------------------------- 1
Banner "1/7 Python 被控端协议回归" Banner "1/7 Python 被控端协议回归"
& python (Join-Path $root "_tests\test_agent_protocol.py") 2>&1 | Select-Object -Last 4 $stageOut = & python (Join-Path $root "_tests\test_agent_protocol.py") 2>&1
Record "Python 被控端协议回归" ($LASTEXITCODE -eq 0) $stageCode = $LASTEXITCODE
$stageOut | Select-Object -Last 4
Record "Python 被控端协议回归" ($stageCode -eq 0) $true
# ---------------------------------------------------------------- 1b # ---------------------------------------------------------------- 1b
Banner "1b/7 防火墙阻止规则检测(真机连不上的根因)" Banner "1b/7 防火墙阻止规则检测(真机连不上的根因)"
& python (Join-Path $root "_tests\test_firewall.py") 2>&1 | Select-Object -Last 3 $stageOut = & python (Join-Path $root "_tests\test_firewall.py") 2>&1
Record "防火墙阻止规则检测" ($LASTEXITCODE -eq 0) $stageCode = $LASTEXITCODE
$stageOut | Select-Object -Last 3
Record "防火墙阻止规则检测" ($stageCode -eq 0)
# ---------------------------------------------------------------- 1c # ---------------------------------------------------------------- 1c
Banner "1c/7 连不上?-> 电脑打开网络自检(UDP 链路)" Banner "1c/7 连不上?-> 电脑打开网络自检(UDP 链路)"
@@ -267,8 +307,10 @@ Record "开机零窗口" ($bootCode -eq 0)
# ---------------------------------------------------------------- 2 # ---------------------------------------------------------------- 2
Banner "2/7 手表屏幕布局适配核算 (490x602)" Banner "2/7 手表屏幕布局适配核算 (490x602)"
& python (Join-Path $root "_tests\check_layout.py") 2>&1 | Select-Object -Last 3 $stageOut = & python (Join-Path $root "_tests\check_layout.py") 2>&1
Record "手表布局适配" ($LASTEXITCODE -eq 0) $stageCode = $LASTEXITCODE
$stageOut | Select-Object -Last 3
Record "手表布局适配" ($stageCode -eq 0)
# ---------------------------------------------------------------- 1z # ---------------------------------------------------------------- 1z
Banner "1z/7 手表分页完整性(防白屏:方法定义了必须被调用)" Banner "1z/7 手表分页完整性(防白屏:方法定义了必须被调用)"
@@ -311,8 +353,10 @@ Record "投图显示方式" ($imgCode -eq 0)
# ---------------------------------------------------------------- 3 # ---------------------------------------------------------------- 3
Banner "3/7 屏幕共享可见提示(真实界面 + Win32 窗口枚举)" Banner "3/7 屏幕共享可见提示(真实界面 + Win32 窗口枚举)"
Stop-Strays Stop-Strays
& python (Join-Path $root "_tests\test_indicator_visible.py") 2>&1 | Select-Object -Last 3 $stageOut = & python (Join-Path $root "_tests\test_indicator_visible.py") 2>&1
Record "屏幕共享强制提示" ($LASTEXITCODE -eq 0) $stageCode = $LASTEXITCODE
$stageOut | Select-Object -Last 3
Record "屏幕共享强制提示" ($stageCode -eq 0)
# ---------------------------------------------------------------- 2b # ---------------------------------------------------------------- 2b
Banner "2b/7 中转 chunked 请求体(防连接错位)" Banner "2b/7 中转 chunked 请求体(防连接错位)"
@@ -457,11 +501,15 @@ if (-not (Test-Path $codeFile)) {
Write-Host "桩服务端未就绪" -ForegroundColor Red Write-Host "桩服务端未就绪" -ForegroundColor Red
Get-Content (Join-Path $env:TEMP "lr_verify_stub_err.txt") -ErrorAction SilentlyContinue Get-Content (Join-Path $env:TEMP "lr_verify_stub_err.txt") -ErrorAction SilentlyContinue
Stop-Process -Id $stub.Id -Force -ErrorAction SilentlyContinue Stop-Process -Id $stub.Id -Force -ErrorAction SilentlyContinue
Record "Java <-> 桩服务端" $false Record "Java <-> 桩服务端" $false $true
} else { } else {
& $java "-Dfile.encoding=UTF-8" -cp $out JavaClientTest "127.0.0.1" 8799 "246813" "stub" 2>&1 | # 先把退出码存下来再走管道。
Select-Object -Last 4 # 直接 & java ... | Select-Object 然后读 $LASTEXITCODE 是不可靠的 ——
Record "Java <-> 桩服务端" ($LASTEXITCODE -eq 0) # 实测测试明明全过了,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 Stop-Process -Id $stub.Id -Force -ErrorAction SilentlyContinue
} }
@@ -493,9 +541,11 @@ if (-not (Test-Path $exe)) {
$cfg | ConvertTo-Json | Set-Content $settings -Encoding UTF8 $cfg | ConvertTo-Json | Set-Content $settings -Encoding UTF8
Write-Host "EXE 配对码: $($cfg.pairing_code)" Write-Host "EXE 配对码: $($cfg.pairing_code)"
& $java "-Dfile.encoding=UTF-8" -cp $out JavaClientTest "127.0.0.1" 8801 $cfg.pairing_code "real" 2>&1 | # 同上:先存退出码,别让管道把它冲掉
Select-Object -Last 4 $javaOut = & $java "-Dfile.encoding=UTF-8" -cp $out JavaClientTest "127.0.0.1" 8801 $cfg.pairing_code "real" 2>&1
Record "Java <-> 打包 EXE" ($LASTEXITCODE -eq 0) $javaCode = $LASTEXITCODE
$javaOut | Select-Object -Last 4
Record "Java <-> 打包 EXE" ($javaCode -eq 0) $true
Stop-Process -Id $proc.Id -Force -ErrorAction SilentlyContinue Stop-Process -Id $proc.Id -Force -ErrorAction SilentlyContinue
Start-Sleep -Seconds 1 Start-Sleep -Seconds 1