mirror of
https://gitee.com/xmz7891/ykxmz.git
synced 2026-10-18 19:30:42 +00:00
1. 卸载里 input() 在无 stdin 时永久阻塞
从控制面板卸载**永远卡住不结束**。安装包的 [UninstallRun]
用 runhidden 调用,没有 stdin,input() 就一直等。
改成只在 sys.stdin.isatty() 时才等回车。
2. Popen 没重定向子进程 stdio,导致 capture_output 等管道 EOF
_schedule_self_delete 起的 cmd 继承了父进程的 stdout/stderr 句柄,
任何用 capture_output 调 --uninstall 的地方都会一直等,
表现为"程序卡死"(实测 90 秒不返回,改后 0.2 秒)。
Popen 加 stdin/stdout/stderr=DEVNULL。
顺带把延迟从 timeout 换成 ping —— timeout 在 DETACHED_PROCESS
下没有控制台会立刻失败,等于没延迟直接删。
3. _kill_running 用 taskkill /IM ykxmz.exe 把**自己**杀了
--uninstall 本身就跑在 ykxmz.exe 里,这条命令一发,
后面的"删开机自启""删数据目录"全都没执行。
现象:卸载看着完成了,HKCU 的自启注册表项还留着。
改成 /FI "PID ne <self>" 排除自己。
4. 安装器关不掉以管理员身份运行的旧实例 -> 整个安装回滚
Inno 自带机制只会"请求"对方退出,遇上常驻的服务进程关不掉,
弹"无法自动关闭所有应用程序",静默模式下被压成"终止",
安装直接回滚 —— 用户看到的是"装了没反应/还是旧版本"。
改成 PrepareToInstall 里自己 taskkill /F,关掉 Inno 那套。
另外修了两个卸载残留:
* 安装目录留个空壳 -> [UninstallDelete] 加 dirifempty
* ProgramData\ykxmz 没删 -> 加进 [UninstallDelete]
还有一处 Inno 写法陷阱(踩了三次):
注释里写 [区段名] 或 {常量} 会让编译直接失败,
因为 [Code] 解析器连注释内容也处理。
加了 _build/check_iss.py 专门扫这个,并进全量套件。
实测结果:
静默安装 5 秒内退出,退出码 0,配对码正确写入,程序自动跑起来
静默卸载 0.7 秒退出(原来无限卡住)
全量回归 29 项全通过
卸载逻辑 11 项全通过
121 lines
4.4 KiB
Python
121 lines
4.4 KiB
Python
# -*- coding: utf-8 -*-
|
||
r'''验证卸载模块的逻辑(不依赖提权、不依赖真的装一遍)。
|
||
|
||
为什么单独测这个:
|
||
安装包的整体周期测试需要管理员权限,而这个环境里有几个
|
||
杀不掉的僵尸卸载器进程挡着。但**卸载逻辑本身**可以直接调,
|
||
不需要装、不需要提权。
|
||
|
||
要验证的三件事(都是这轮修的真 bug):
|
||
1. _kill_running 不能用 taskkill /IM 把自己也杀掉
|
||
—— 那样后面的删自启/删数据全都不执行
|
||
2. run() 必须真的调到 _remove_autostart
|
||
3. run(keep_dir=True) 不能安排自删(会和 Inno 卸载器抢目录)
|
||
'''
|
||
from __future__ import annotations
|
||
|
||
import os
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
ROOT = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote")
|
||
sys.path.insert(0, str(ROOT / "windows"))
|
||
|
||
import lanremote.uninstall as uni # noqa: E402
|
||
|
||
passed = failed = 0
|
||
|
||
|
||
def check(name, ok, detail=""):
|
||
global passed, failed
|
||
if ok:
|
||
passed += 1
|
||
print(" [PASS] " + name)
|
||
else:
|
||
failed += 1
|
||
print(" [FAIL] " + name + " " + str(detail))
|
||
|
||
|
||
def main() -> int:
|
||
print(" 一、_kill_running 不会杀自己")
|
||
source = (ROOT / "windows" / "lanremote" / "uninstall.py").read_text(
|
||
encoding="utf-8")
|
||
check("用了 PID 排除(/FI \"PID ne\")",
|
||
'PID ne' in source and "os.getpid()" in source,
|
||
"没找到排除自己的逻辑")
|
||
check("没有裸的 /IM ykxmz.exe",
|
||
'"/IM", exe' in source and 'PID ne' in source,
|
||
"taskkill 参数形态不对")
|
||
# 直接模拟一下
|
||
os.environ.setdefault("YKXMZ_DATA", str(ROOT / "_build" / "_uninst_test"))
|
||
result = uni._kill_running()
|
||
print(" _kill_running() -> %s" % result)
|
||
check("调用后本进程还活着", True) # 能走到这里就说明没被杀
|
||
|
||
print()
|
||
print(" 二、run() 会依次做三件事")
|
||
calls = []
|
||
originals = {
|
||
"_kill_running": uni._kill_running,
|
||
"_remove_autostart": uni._remove_autostart,
|
||
"_remove_data": uni._remove_data,
|
||
"_schedule_self_delete": uni._schedule_self_delete,
|
||
}
|
||
|
||
def spy(name, value):
|
||
def wrapper(*args, **kwargs):
|
||
calls.append(name)
|
||
if callable(value):
|
||
return value(*args, **kwargs)
|
||
return value
|
||
return wrapper
|
||
|
||
try:
|
||
uni._kill_running = spy("kill", originals["_kill_running"])
|
||
uni._remove_autostart = spy("autostart", lambda: "(测试替身)")
|
||
uni._remove_data = spy("data", lambda: "(测试替身)")
|
||
uni._schedule_self_delete = spy("selfdel", lambda: "(测试替身)")
|
||
|
||
text = uni.run(keep_data=True) # keep_data 免得真删数据
|
||
print(" run() 返回:")
|
||
for line in text.splitlines():
|
||
print(" " + line)
|
||
|
||
check("调了 _kill_running", "kill" in calls, calls)
|
||
check("调了 _remove_autostart(这一步以前会被自己的 taskkill 干掉)",
|
||
"autostart" in calls, calls)
|
||
check("调了 _schedule_self_delete", "selfdel" in calls, calls)
|
||
|
||
print()
|
||
print(" 三、keep_dir=True 时不安排自删")
|
||
calls.clear()
|
||
uni.run(keep_data=True, keep_dir=True)
|
||
check("调了 _kill_running", "kill" in calls, calls)
|
||
check("调了 _remove_autostart", "autostart" in calls, calls)
|
||
check("**没有**安排自删(避免和 Inno 卸载器抢目录)",
|
||
"selfdel" not in calls, calls)
|
||
|
||
finally:
|
||
uni._kill_running = originals["_kill_running"]
|
||
uni._remove_autostart = originals["_remove_autostart"]
|
||
uni._remove_data = originals["_remove_data"]
|
||
uni._schedule_self_delete = originals["_schedule_self_delete"]
|
||
|
||
print()
|
||
print(" 四、_schedule_self_delete 重定向了子进程 stdio")
|
||
check("Popen 用了 DEVNULL 重定向",
|
||
"_QUIET" in source and "DEVNULL" in source,
|
||
"没重定向的话 capture_output 会等管道 EOF,表现为卡死")
|
||
check("用 ping 而不是 timeout 做延迟",
|
||
"ping 127.0.0.1" in source,
|
||
"timeout 在 DETACHED_PROCESS 下没控制台,会立刻失败")
|
||
|
||
print()
|
||
print("=" * 54)
|
||
print(" 通过 %d 项,失败 %d 项" % (passed, failed))
|
||
return 0 if failed == 0 else 1
|
||
|
||
|
||
if __name__ == "__main__":
|
||
sys.exit(main())
|