v1.0.10 两件大事:
1. 界面中英双语(英文界面无中文残留)
- 新增 web/i18n.js(约 4200 条词条),零依赖零构建
- 后端下发中文数据走 bt4() 查表翻译;独立模块(upstream/netdetail/
realtime/update)纳入翻译并注册切语言重绘钩子
- 后端「状态/聚合」接口只回稳定枚举+原始数值,不下发中文文案
- 顺带修一个真实 XSS:顶层 const 不挂 window,独立脚本 esc 静默退化成零转义
2. 修掉「文字竖着排/错位」(多分辨率反复暴露)
- 单元格 min-content 只有 1 字宽 → word-break:keep-all + 表头 nowrap
- 依赖卡片正文被标签/按钮挤成 27px → .dep-body{flex:1 1 200px} + flex-wrap
- 裸 flex:1(下限为0)给正文 → 一律改 flex:1 1 <px>
- .dep-item 横向容器直接塞块级子项 → 退回块级
- 关键陷阱:auto-fill 布局屏越宽列数越多单卡越窄,窄屏反而测不出来,
真机审计推进到「逐视图点按钮 + 1920/2560 宽视口 + 中英双语」
- 守卫 t-layout.py 扩到 35 条,12 处反向验证全红
3. 修真缺陷:_link_since() 重启检测被误改成 if False:(注释还写着「已修」),
uptime 归零后相减得负数 → 恢复比对 boot_id
附带:新增 25 个检查器(t-*.py)、排版审计工具链(shot-*/ocr-*/live-*)、
版本号兜底值同步到 1.0.10、.gitignore 收纳 i18n/排版排查的一次性脚本。
98 lines
3.7 KiB
Python
98 lines
3.7 KiB
Python
"""drouter 静态回归驱动器(Windows 开发机用)。
|
||
|
||
为什么不用 shell 脚本跑:
|
||
这个环境里 bash 会话有 SIGTERM 风险,把整轮结果一起带走,
|
||
表现为「跑全量回归 → 命令莫名死掉」,看不出跑到第几个。
|
||
改成 Python逐个跑 + 每项前后写日志文件,崩了也能定位到具体那一项。
|
||
|
||
用法:
|
||
python _dev/run-static.py 跑全部
|
||
python _dev/run-static.py t-107 t-ca 只跑指定的
|
||
"""
|
||
import glob
|
||
import os
|
||
import re
|
||
import subprocess
|
||
import sys
|
||
|
||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||
ROOT = os.path.dirname(HERE)
|
||
|
||
# 需要真机 / 网络 / 交互 / 长时间压测的,不在静态回归范围内。
|
||
# ⚠️ 必须带 `t-` 前缀 —— ls 出来的名字就是 t-xxx。写裸名的话一个都
|
||
# 排除不掉,交互脚本照样跑,其中一个会把整个会话干掉。
|
||
EXCL = {
|
||
't-live', 't-smoke', 't-ulog-perf', 't-ulog-detail', 't-perf',
|
||
't-print', 't-cachebust', 't-cleanup', 't-webshell', 't-pty',
|
||
't-daemon-restart',
|
||
# ⚠️ 反向验证脚本**会真的改源文件**(注入 bug → 跑测试 → 还原)。
|
||
# 名字前缀也是 t-,会被 glob 自动收进来;一旦被自动执行,
|
||
# 它就会在回归过程中反复改写 backend/ 与 web/ —— 后果是
|
||
# 别的检查器读到被注入污染的代码,报出一堆假红,
|
||
# 更糟的是中途被打断会留下污染(已经发生过一次)。
|
||
# 只能手动跑:python _dev/t-107-inject-wg.py --batch 0
|
||
't-107-inject-wg',
|
||
}
|
||
|
||
|
||
def main():
|
||
args = sys.argv[1:]
|
||
if args:
|
||
names = [a if a.startswith('t-') else 't-' + a for a in args]
|
||
else:
|
||
names = sorted(os.path.basename(p)[:-3]
|
||
for p in glob.glob(os.path.join(HERE, 't-*.py')))
|
||
names = [n for n in names if n not in EXCL]
|
||
|
||
logpath = os.path.join(HERE, '.run-static.log')
|
||
log = open(logpath, 'w', encoding='utf-8')
|
||
|
||
def say(msg):
|
||
sys.stdout.write(msg + '\n')
|
||
sys.stdout.flush()
|
||
log.write(msg + '\n')
|
||
log.flush()
|
||
|
||
passed, failed = [], []
|
||
for n in names:
|
||
f = os.path.join(HERE, n + '.py')
|
||
if not os.path.isfile(f):
|
||
say(' %-20s 跳过(不存在)' % n)
|
||
continue
|
||
say(' %-20s ... ' % n)
|
||
try:
|
||
p = subprocess.run([sys.executable, f], capture_output=True,
|
||
text=True, encoding='utf-8', errors='replace',
|
||
timeout=180)
|
||
rc, out = p.returncode, (p.stdout or '')
|
||
except subprocess.TimeoutExpired:
|
||
rc, out = 124, 'TIMEOUT'
|
||
if rc == 0:
|
||
m = re.findall(r'通过 (\d+) 项', out)
|
||
say('ok%s' % ((' (%s 项)' % m[-1]) if m else ''))
|
||
passed.append(n)
|
||
else:
|
||
say('失败 rc=%s' % rc)
|
||
# 把红的那几行打出来 —— 只写 rc 的话,排查时只能一个个单独跑
|
||
# (2026-10-06:t-msg-en 在全量里红、单独跑绿,顺序依赖查不出来,
|
||
# 就是因为日志里只有「失败 rc=1」,看不到是哪条判据。)
|
||
# 各检查器的红标记不统一:有的用 [NG]、有的用 [FAIL]、有的打 ✘。
|
||
for line in out.splitlines():
|
||
if re.search(r'\[NG\]|\[FAIL\]|Traceback|Error|✘|异常',
|
||
line):
|
||
say(' ' + line[:150])
|
||
failed.append(n)
|
||
|
||
log.close()
|
||
print()
|
||
print('通过 %d 个检查器,失败 %d 个' % (len(passed), len(failed)))
|
||
if failed:
|
||
print('失败:%s' % ' '.join(failed))
|
||
return 1
|
||
print('(明细见 _dev/.run-static.log)')
|
||
return 0
|
||
|
||
|
||
if __name__ == '__main__':
|
||
sys.exit(main())
|