mirror of
https://gitee.com/xmz7891/ykxmz.git
synced 2026-10-18 13:40:45 +00:00
琚帶绔?- 淇 MTU 闂锛氬す MSS + 璺緞 MTU 鎺㈡祴锛堝叕缃戝彲鐢ㄧ巼 48% -> 100%锛?- 澶嶇敤 HTTP 杩炴帴锛堝厤鎺夋瘡娆℃柊寤?TLS 鐨?30% 鎻℃墜澶辫触锛?- 姣忓垎閽熷洖澶存壘灞€鍩熺綉锛岃嚜鍔ㄥ垏鍥炲揩 400 鍊嶇殑璺緞 - 鐢婚潰蹇冭烦甯э細闈欐鏃剁渷娴侀噺锛屽鎴风浠嶇煡閬撴祦鏄椿鐨?- 閿佸睆鏃剁粰鍑哄彲鎿嶄綔鐨勬彁绀? 鎵嬭〃绔?- 鍐呯疆 GTS Root R4 / GlobalSign Root CA锛屼慨 SSLHandshakeException - 杩炴帴閿欒缈昏瘧鎴愪汉璇濓紙璇佷功/鏃堕棿/瑙f瀽/瓒呮椂鍒嗗埆鎻愮ず锛?- 缁忎腑杞繛涓婂悗鐢ㄦ湇鍔″櫒鎶ョ殑鍦板潃鍒锋柊宸查厤瀵硅褰?- 鏂板灞€鍩熺綉杩炴帴椤甸潰銆佹湇鍔″櫒寤惰繜妫€娴? 鏈嶅姟鍣ㄧ - 鏀寔 chunked 璇锋眰浣擄紙淇瘡 16 绉掍竴娆$殑杩炴帴閿欎贡涓庡ぇ闈㈢Н 502锛?- 缃戠粶璋冧紭鑴氭湰 net-tune.sh锛堝紑鏈鸿嚜鍔ㄥ簲鐢級 - 绠$悊椤碉細鎵归噺鍒犻櫎銆佺綉缁滀綋妫€銆佷笅杞藉尯 - 闅ч亾鏀?HTTP/2 + 鍙岃繛鎺? 鏂囨。 - 璁稿彲鍗忚 / 瀹夎鍓嶅繀璇?/ 瀹夎鍚庨』鐭ワ紙浣滆€咃細灏忛害瀛愶級
274 lines
10 KiB
Python
274 lines
10 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""端到端验证:启动**打包后的 EXE**,配对,然后调 feature_list。
|
||
|
||
这是唯一能验出"Features 没被构造"这个 bug 的方式 ——
|
||
之前的测试都自己 new 一个 Features,所以永远测不出来(真机上功能页是死的)。
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import json
|
||
import os
|
||
import shutil
|
||
import socket
|
||
import subprocess
|
||
import sys
|
||
import tempfile
|
||
import time
|
||
from pathlib import Path
|
||
|
||
ROOT = Path(__file__).resolve().parent.parent
|
||
EXE = ROOT / "dist_exe" / "ykxmz" / "ykxmz.exe"
|
||
PORT = 8871
|
||
|
||
PASSED = 0
|
||
FAILED = 0
|
||
|
||
|
||
def check(name, ok, detail=""):
|
||
global PASSED, FAILED
|
||
if ok:
|
||
PASSED += 1
|
||
print(f" [PASS] {name}")
|
||
else:
|
||
FAILED += 1
|
||
print(f" [FAIL] {name} {detail}")
|
||
|
||
|
||
class Client:
|
||
def __init__(self, host, port, timeout=10.0, source_ip=None):
|
||
if source_ip:
|
||
# 绑定一个具体的源地址,用来模拟"另一台机器"
|
||
# (Windows 上整个 127.0.0.0/8 都是回环)
|
||
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||
self.sock.settimeout(timeout)
|
||
self.sock.bind((source_ip, 0))
|
||
self.sock.connect((host, port))
|
||
else:
|
||
self.sock = socket.create_connection((host, port), timeout=timeout)
|
||
self.sock.settimeout(timeout)
|
||
self.buf = b""
|
||
|
||
def send(self, obj):
|
||
self.sock.sendall((json.dumps(obj, ensure_ascii=False) + "\n").encode("utf-8"))
|
||
|
||
def recv(self, timeout=12.0):
|
||
deadline = time.time() + timeout
|
||
while b"\n" not in self.buf:
|
||
if time.time() > deadline:
|
||
raise TimeoutError("等回复超时")
|
||
chunk = self.sock.recv(65536)
|
||
if not chunk:
|
||
raise ConnectionError("连接断开")
|
||
self.buf += chunk
|
||
line, _, self.buf = self.buf.partition(b"\n")
|
||
return json.loads(line.decode("utf-8"))
|
||
|
||
def call(self, obj, timeout=12.0):
|
||
self.send(obj)
|
||
return self.recv(timeout)
|
||
|
||
def close(self):
|
||
try:
|
||
self.sock.close()
|
||
except OSError:
|
||
pass
|
||
|
||
|
||
def main() -> int:
|
||
if not EXE.is_file():
|
||
print(f" 找不到打包好的 EXE: {EXE}")
|
||
print(" 先跑 windows\\build_exe.ps1")
|
||
return 1
|
||
|
||
# 用独立的数据目录,别碰用户真实配置
|
||
work = Path(tempfile.mkdtemp(prefix="lr_e2e_"))
|
||
exe_dir = EXE.parent
|
||
|
||
# 先把真实 data 目录挪开(EXE 的 CWD 决定 data 位置)
|
||
backup = None
|
||
real_data = exe_dir / "data"
|
||
if real_data.exists():
|
||
backup = exe_dir / f"data_backup_{int(time.time())}"
|
||
shutil.move(str(real_data), str(backup))
|
||
print(f" 已备份真实 data 到 {backup.name}")
|
||
|
||
proc = None
|
||
try:
|
||
proc = subprocess.Popen(
|
||
# --add-device:静默运行 + 临时开放配对。
|
||
# 不能用 --minimized —— 全新数据目录会弹设置窗口卡住。
|
||
[str(EXE), "--port", str(PORT), "--add-device", "10"],
|
||
cwd=str(exe_dir),
|
||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||
print(f" 已启动 EXE(PID {proc.pid}),等待监听…")
|
||
|
||
code = None
|
||
for _ in range(40):
|
||
time.sleep(0.5)
|
||
settings = exe_dir / "data" / "settings.json"
|
||
if settings.is_file():
|
||
try:
|
||
code = json.loads(settings.read_text(encoding="utf-8")).get(
|
||
"pairing_code")
|
||
except Exception: # noqa: BLE001
|
||
pass
|
||
if code:
|
||
try:
|
||
probe = socket.create_connection(("127.0.0.1", PORT), timeout=1)
|
||
probe.close()
|
||
break
|
||
except OSError:
|
||
continue
|
||
|
||
check("EXE 起来了并读到配对码", bool(code), f"code={code}")
|
||
|
||
if not code:
|
||
return 1
|
||
|
||
print()
|
||
print("一、配对")
|
||
client = Client("127.0.0.1", PORT)
|
||
|
||
print()
|
||
print("一b、★ 配对防爆破(必须在成功配对之前测,"
|
||
"否则配对窗口已经关了)")
|
||
# 从 127.0.0.2 发起 —— 限速是按 IP 的,
|
||
# 用同一个 IP 会把后面的合法配对一起锁掉
|
||
breaker = Client("127.0.0.1", PORT, source_ip="127.0.0.2")
|
||
wrong = "000000" if code != "000000" else "111111"
|
||
limited = False
|
||
for i in range(12):
|
||
r = breaker.call({"cmd": "pair", "deviceId": f"brute-{i}",
|
||
"deviceName": "爆破", "code": wrong}, timeout=10)
|
||
if not r.get("ok") and "过多" in str(r.get("error", "")):
|
||
limited = True
|
||
print(f" 第 {i + 1} 次开始被限速")
|
||
break
|
||
check("连续错码会被限速", limited,
|
||
f"12 次都没触发限速,最后一次: {r}")
|
||
breaker.close()
|
||
|
||
# 限速状态在内存里,重启就清空。
|
||
# 重启既能让后续的合法配对不被挡住,也顺带验证了
|
||
# "限速不会写进磁盘、重启即恢复"。
|
||
print(" 重启被控端以清空限速…")
|
||
proc.terminate()
|
||
try:
|
||
proc.wait(timeout=10)
|
||
except Exception: # noqa: BLE001
|
||
proc.kill()
|
||
time.sleep(1.5)
|
||
proc = subprocess.Popen(
|
||
[str(EXE), "--port", str(PORT), "--add-device", "10"],
|
||
cwd=str(exe_dir),
|
||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||
restarted = False
|
||
for _ in range(30):
|
||
time.sleep(0.5)
|
||
try:
|
||
s = socket.create_connection(("127.0.0.1", PORT), timeout=1)
|
||
s.close()
|
||
restarted = True
|
||
break
|
||
except OSError:
|
||
continue
|
||
check("重启后被控端重新在监听", restarted)
|
||
time.sleep(1.0)
|
||
|
||
# 重启之后必须重新建连接(旧的 socket 已经断了)
|
||
client = Client("127.0.0.1", PORT)
|
||
|
||
reply = client.call({"cmd": "pair", "deviceId": "e2e-test",
|
||
"deviceName": "端到端测试", "code": code})
|
||
check("配对成功", reply.get("ok") is True, str(reply)[:120])
|
||
|
||
print()
|
||
print("二、★ 功能列表(这是之前完全失败的那一项)")
|
||
reply = client.call({"cmd": "feature_list"}, timeout=25)
|
||
ok = reply.get("ok") is True
|
||
features = reply.get("features") or []
|
||
check("feature_list 返回 ok", ok, str(reply.get("error"))[:120])
|
||
check("功能数量 > 100", len(features) > 100, f"{len(features)} 个")
|
||
if features:
|
||
groups = sorted({f.get("group", "?") for f in features})
|
||
print(f" 共 {len(features)} 个功能,{len(groups)} 个分组")
|
||
print(f" 分组: {'、'.join(groups)}")
|
||
|
||
print()
|
||
print("三、★ 执行一个功能")
|
||
reply = client.call({"cmd": "feature_run", "name": "world_clock",
|
||
"params": {}}, timeout=20)
|
||
check("feature_run 返回 ok", reply.get("ok") is True, str(reply)[:120])
|
||
note = str(reply.get("note") or "")
|
||
check("世界时钟返回了内容", "北京" in note, note[:80])
|
||
if note:
|
||
print(f" {note.replace(chr(10), ' | ')}")
|
||
|
||
print()
|
||
print("四、★ 文件功能 + 越权拒绝")
|
||
reply = client.call({"cmd": "feature_run", "name": "file_info",
|
||
"params": {"path": "C:\\Windows\\win.ini"}}, timeout=15)
|
||
check("越权路径被拒绝", reply.get("ok") is False,
|
||
f"竟然成功了: {str(reply)[:100]}")
|
||
|
||
reply = client.call({"cmd": "feature_run", "name": "note_add",
|
||
"params": {"text": "端到端测试笔记"}}, timeout=15)
|
||
check("记笔记成功", reply.get("ok") is True, str(reply)[:100])
|
||
|
||
print()
|
||
print("五、★ 相对路径不能拉走 settings.json")
|
||
reply = client.call({"cmd": "file_pull", "path": "data/settings.json"},
|
||
timeout=15)
|
||
check("拒绝拉取 data/settings.json", reply.get("ok") is False,
|
||
f"竟然成功了: {str(reply)[:100]}")
|
||
|
||
print()
|
||
print("七、★ 分块超限")
|
||
big = "A" * (300 * 1024)
|
||
try:
|
||
client.send({"cmd": "file_push", "seq": 0, "last": False,
|
||
"name": "big.bin", "data": big})
|
||
client.send({"cmd": "file_push", "seq": 1, "last": False,
|
||
"name": "big.bin", "data": big})
|
||
r = client.recv(timeout=8)
|
||
except Exception as exc: # noqa: BLE001
|
||
r = {"error": str(exc)}
|
||
check("超限不会让服务端崩(还活着)", True)
|
||
|
||
reply = client.call({"cmd": "ping"}, timeout=10)
|
||
check("服务端仍然响应 ping", reply.get("ok") is True, str(reply)[:80])
|
||
|
||
client.close()
|
||
|
||
finally:
|
||
if proc is not None:
|
||
try:
|
||
proc.terminate()
|
||
proc.wait(timeout=8)
|
||
except Exception: # noqa: BLE001
|
||
try:
|
||
proc.kill()
|
||
except Exception: # noqa: BLE001
|
||
pass
|
||
time.sleep(1.0)
|
||
# 还原真实 data
|
||
try:
|
||
test_data = exe_dir / "data"
|
||
if test_data.exists():
|
||
shutil.rmtree(test_data, ignore_errors=True)
|
||
if backup is not None and backup.exists():
|
||
shutil.move(str(backup), str(real_data))
|
||
print(f" 已还原真实 data")
|
||
except Exception as exc: # noqa: BLE001
|
||
print(f" !! 还原 data 失败: {exc}")
|
||
shutil.rmtree(work, ignore_errors=True)
|
||
|
||
print()
|
||
print("=" * 62)
|
||
print(f"通过 {PASSED} 项,失败 {FAILED} 项")
|
||
return 1 if FAILED else 0
|
||
|
||
|
||
if __name__ == "__main__":
|
||
sys.exit(main())
|