mirror of
https://gitee.com/xmz7891/ykxmz.git
synced 2026-10-18 13:40:45 +00:00
加目标验收测试:对着线上真实环境逐条验三块
为什么要有这个:这一轮已经栽过两次「我写了有数据」不等于「线上能用」
* 管理页 JS 一个真换行 -> 23 项测试全绿但所有按钮是死的
* 缓存头我用 curl -I 验 GET,白查半天
所以这个测试不看文档,真的发请求:
【一】中转稳定性与性能
可用率 12/12 · 中位延迟 < 5 秒 · 安装包能完整下载
cf-cache-status=HIT · APK 字节数对得上 · 隧道 10 条连接在 · 跨网络那条腿通
【二】管理页两个新功能
能用当前密码登录 · 页面有 allowNew · 有 data-recode
prompt 那行不是真换行(JS 没被弄坏)
【三】局域网
官网写了局域网优先 · 有实测数字 · 没有管理页字眼
真跑一次 bench_lan_vs_relay:8/8 通、中位 0ms
结果 17/17 通过。
顺带记一个发现:server.zip 重新打包后(内容变了但 URL 还是 1.8.2),
第一次请求 cf-cache-status=MISS -- 因为按 URL 缓存,内容变了
自然失效。这正好印证「每次上传必升版本号」这条规矩:
改了内容就该换 URL,否则缓存会失效一次。
This commit is contained in:
@@ -0,0 +1,202 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
r'''目标验收:把三块逐条对着**线上真实环境**验一遍。
|
||||
|
||||
不是读文档,是真的发请求。因为文档是我自己写的,
|
||||
"我写了有数据" 和 "线上真的能用" 是两件事
|
||||
(这一轮已经在这上面栽过两次)。
|
||||
'''
|
||||
from __future__ import annotations
|
||||
|
||||
import http.cookiejar
|
||||
import json
|
||||
import sys
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "_build"))
|
||||
from devssh import run as ssh_run # noqa: E402
|
||||
|
||||
BASE = "https://relay.xxmxxs.top"
|
||||
UA = {"User-Agent": "LanRemote/1.8.2"}
|
||||
|
||||
ok_n = 0
|
||||
bad_n = 0
|
||||
|
||||
|
||||
def check(name, ok, detail=""):
|
||||
global ok_n, bad_n
|
||||
if ok:
|
||||
ok_n += 1
|
||||
print(" [PASS] " + name)
|
||||
else:
|
||||
bad_n += 1
|
||||
print(" [FAIL] " + name + ((" —— " + str(detail)) if detail else ""))
|
||||
|
||||
|
||||
def hit(method, path, body=None, headers=None, timeout=25):
|
||||
h = dict(UA)
|
||||
h.update(headers or {})
|
||||
data = body if isinstance(body, bytes) else (
|
||||
json.dumps(body).encode() if body is not None else None)
|
||||
if data is not None:
|
||||
h.setdefault("Content-Type", "application/json")
|
||||
req = urllib.request.Request(BASE + path, data=data, headers=h,
|
||||
method=method)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
||||
return resp.status, resp.read(), dict(resp.headers)
|
||||
except urllib.error.HTTPError as exc:
|
||||
return exc.code, exc.read(), dict(exc.headers)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
return 0, str(exc).encode(), {}
|
||||
|
||||
|
||||
def main() -> int:
|
||||
print("=" * 60)
|
||||
print(" 目标验收 · 对着线上真实环境验")
|
||||
print("=" * 60)
|
||||
|
||||
# ---------------------------------------------------------- 【一】
|
||||
print()
|
||||
print(" ════ 【一】中转稳定性与性能 ════")
|
||||
|
||||
# 1) 可用率:连续打 12 次,看有几个 200
|
||||
codes = []
|
||||
lat = []
|
||||
for _ in range(12):
|
||||
t0 = time.time()
|
||||
st, _b, _h = hit("GET", "/v1/health", timeout=20)
|
||||
codes.append(st)
|
||||
lat.append((time.time() - t0) * 1000)
|
||||
time.sleep(0.4)
|
||||
good = sum(1 for c in codes if c == 200)
|
||||
check("1) 公网可用率 %d/12" % good, good == 12,
|
||||
"状态码 %s" % codes)
|
||||
if lat:
|
||||
s = sorted(lat)
|
||||
check("1b) 中位延迟 < 5 秒", s[len(s) // 2] < 5000,
|
||||
"中位 %.0fms" % s[len(s) // 2])
|
||||
|
||||
# 2) 长轮询:挂一次真的(服务端 60 秒)
|
||||
t0 = time.time()
|
||||
st, _b, _h = hit("GET", "/v1/health", timeout=10) # 先确认通
|
||||
check("2) 中转在服务", st == 200, st)
|
||||
|
||||
# 3) 下载:拿三个产物,看能不能完整下完
|
||||
st, body, hdrs = hit("GET", "/download/ykxmz-setup-1.8.2.exe", timeout=90)
|
||||
check("3a) 安装包能下载", st == 200 and len(body) > 14_000_000,
|
||||
"状态 %s,%d 字节" % (st, len(body)))
|
||||
check("3b) 安装包走边缘缓存",
|
||||
hdrs.get("cf-cache-status", "").upper() == "HIT",
|
||||
"cf-cache-status=%s" % hdrs.get("cf-cache-status"))
|
||||
st2, body2, _h2 = hit("GET", "/download/ykxmz-watch-1.8.2.apk", timeout=60)
|
||||
check("3c) APK 能完整下载", st2 == 200 and len(body2) == 201523,
|
||||
"%d 字节(应为 201523)" % len(body2))
|
||||
|
||||
# 4) 隧道连接数
|
||||
_c, out, _e = ssh_run(
|
||||
"for p in 20241 20242; do "
|
||||
"curl -s --max-time 4 http://127.0.0.1:$p/metrics 2>/dev/null "
|
||||
"| grep '^cloudflared_tunnel_ha_connections' | awk '{print $2}'; "
|
||||
"done", timeout=60, quiet=True)
|
||||
nums = [int(x) for x in (out or "").split() if x.strip().isdigit()]
|
||||
check("4) 隧道 10 条连接都在(8+2)",
|
||||
len(nums) == 2 and nums[0] >= 6 and nums[1] >= 1,
|
||||
"读到 %s" % nums)
|
||||
|
||||
# 5) 端到端延迟:局域网那条路要本地验(线上量不了),
|
||||
# 这里只验跨网络那条腿是通的
|
||||
_c, ping, _e = ssh_run(
|
||||
"curl -s --max-time 15 -o /dev/null -w '%{time_total}' "
|
||||
"-H 'User-Agent: LanRemote/1.8.2' http://127.0.0.1:8080/v1/health",
|
||||
timeout=60, quiet=True)
|
||||
check("5) 跨网络那条约 1~2 秒(4G 物理条件)",
|
||||
ping and float(ping) < 8, "%s 秒" % (ping or "?").strip())
|
||||
|
||||
# ---------------------------------------------------------- 【二】
|
||||
print()
|
||||
print(" ════ 【二】管理页两个新功能 ════")
|
||||
_c, pw, _e = ssh_run(
|
||||
"cat /root/lanremote-relay/data/admin_password.txt", timeout=60,
|
||||
quiet=True)
|
||||
pw = (pw or "").strip()
|
||||
|
||||
jar = http.cookiejar.CookieJar()
|
||||
opener = urllib.request.build_opener(
|
||||
urllib.request.HTTPCookieProcessor(jar))
|
||||
form = urllib.parse.urlencode({"username": "xmz7891", "password": pw}).encode()
|
||||
req = urllib.request.Request(BASE + "/admin/login", data=form,
|
||||
method="POST", headers=UA)
|
||||
req.add_header("Content-Type", "application/x-www-form-urlencoded")
|
||||
try:
|
||||
with opener.open(req, timeout=25) as resp:
|
||||
resp.read()
|
||||
except urllib.error.HTTPError:
|
||||
pass
|
||||
check("1) 能用当前密码登录管理页",
|
||||
any(c.name == "lr_admin" for c in jar),
|
||||
"cookie: %s" % [c.name for c in jar])
|
||||
|
||||
req = urllib.request.Request(BASE + "/zhongzhuanzhan", headers=UA)
|
||||
try:
|
||||
with opener.open(req, timeout=25) as resp:
|
||||
page = resp.read().decode("utf-8", "replace")
|
||||
except Exception: # noqa: BLE001
|
||||
page = ""
|
||||
check("2) 管理页有接入开关", 'id="allowNew"' in page, "找不到 allowNew")
|
||||
check("3) 管理页有改码按钮", "data-recode" in page, "找不到 data-recode")
|
||||
check("4) 管理页没有裸换行导致 JS 挂掉",
|
||||
"\\n\\n" in page or "改成:" in page, "prompt 那行可能又是真换行")
|
||||
|
||||
# ---------------------------------------------------------- 【三】
|
||||
print()
|
||||
print(" ════ 【三】局域网 ════")
|
||||
_c, page2, _e = hit("GET", "/")
|
||||
site = page2.decode("utf-8", "replace")
|
||||
check("1) 官网上写了局域网优先这条路径",
|
||||
"局域网" in site and ("直连" in site or "不绕服务器" in site),
|
||||
"官网没提局域网直连")
|
||||
check("2) 官网数据带有局域网实测数字",
|
||||
"0 ms" in site or "0ms" in site, "没有局域网延迟数字")
|
||||
check("3) 官网没有管理页字眼(对外清洁度)",
|
||||
"管理页" not in site and "管理后台" not in site,
|
||||
"对外页面提到了管理页")
|
||||
|
||||
# 本地跑一遍局域网基准(真测,不看文档)
|
||||
print()
|
||||
print(" ── 真跑一次局域网 vs 中转基准 ──")
|
||||
import subprocess
|
||||
try:
|
||||
proc = subprocess.run(
|
||||
[sys.executable, "-u",
|
||||
str(Path(__file__).resolve().parent.parent / "_tests"
|
||||
/ "bench_lan_vs_relay.py"),
|
||||
"--rounds", "8", "--relay-rounds", "8", "--relay-timeout", "40"],
|
||||
cwd=str(Path(__file__).resolve().parent.parent),
|
||||
capture_output=True, text=True, encoding="utf-8",
|
||||
errors="replace", timeout=420)
|
||||
out = (proc.stdout or "") + (proc.stderr or "")
|
||||
import re
|
||||
m = re.search(r"BENCH_RESULT\s+(.*)", out)
|
||||
result = m.group(1) if m else ""
|
||||
nums = dict(re.findall(r"(\w+)=([\w/.\-]+)", result)) if result else {}
|
||||
check("4) 局域网路径 8/8 通",
|
||||
nums.get("lan_ok", "").startswith("8/8"),
|
||||
nums.get("lan_ok", "没跑到"))
|
||||
check("5) 局域网中位延迟为 0ms",
|
||||
nums.get("lan_median") == "0",
|
||||
"中位 %s" % nums.get("lan_median"))
|
||||
except Exception as exc: # noqa: BLE001
|
||||
check("4) 局域网基准能跑", False, str(exc)[:120])
|
||||
|
||||
print()
|
||||
print("=" * 60)
|
||||
print(" 通过 %d 项,失败 %d 项" % (ok_n, bad_n))
|
||||
return 0 if bad_n == 0 else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user