Files
ykxmz/_tests/test_no_orphan_methods.py
T
xmz7891 566d9e384f 阶段1+2:恢复 HTTP/2 加速 + 重做官网
阶段1 网络加速
- 发现隧道在跑 QUIC(ExecStart 丢了 --protocol http2),
  实测 QUIC 可用率 48% vs HTTP/2 80%+,是重大性能回退。
  恢复后公网延迟从 4141ms 降到 690ms(快 6 倍),可用率 92%。
- 优选 IP 结论:无效。hosts 覆盖确实生效了(日志里连的就是指定 IP),
  但落点仍是 lax —— 运营商把所有 Cloudflare anycast 都路由到美西。
  已清掉这段无效配置。
- BBR 不可用:设备是自定义内核 6.4.0-rc4-jsbsbxjxh66,
  没编 tcp_bbr 模块也没配置项。改用 cubic + 调优:
  TCP 缓冲 4MB、tcp_fastopen=3、tcp_slow_start_after_idle=0
  (长轮询场景关键)、tcp_tw_reuse=1。

阶段2 官网重做
- 拆出独立模块 relay/landing.py(relay.py 已两千多行,
  再塞 HTML 没法看)
- 信息结构参考成熟产品站:吸顶导航 / Hero / 九宫格功能 /
  三步上手 / 下载 / FAQ / 页脚
- 保持白色简洁:底白、浅灰面板、细边框、大量留白
- logo 用内联 SVG(ICO 放 img 里实测是破图)
- 管理入口从官网移除(管理不对外开放)

过程中被自己的测试抓到两个 bug:
- 模块叫 site.py 会和 Python 标准库重名,import site 拿到的是标准库
- _logo() 里把 % 格式化和字符串拼接混用,直接抛 TypeError
2026-10-06 23:53:19 +08:00

178 lines
7.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
'''防回归:抓"定义了但从没被调用"的方法。
为什么值得单独一个测试:
1.7.0 交付后用户装上发现**一片白屏**。根因是
private void initTabs() { ... } // 定义在第 937 行
而 onCreate 里只调了 buildControlPanel(),**从头到尾没人调 initTabs()**。
结果 tabDefs 为 null -> selectTab() 直接 return -> 内容区空白。
这种错误编译能过、别的测试也能过,只有真机跑起来才看得见,
所以必须静态查出来。
判定方法:
统计方法名在文件里出现的次数(定义那一处也算)。
只出现 1 次 = 只有定义没调用。
分两档:
* **硬性失败**:关键路径上的方法(initTabs / 三个面板 / selectTab)。
这些漏了就是白屏或功能全无,必须拦住。
* **仅报告**:其它可疑方法。有些确实是历史遗留的死代码
(下面的 KNOWN_DEAD 记录了已确认的),有些是接口回调
(由框架调用,本文件里当然只有定义)。
报告出来供清理,但不拦构建 —— 免得噪音太大没人看。
'''
from __future__ import annotations
import re
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
MAIN = (ROOT / "android" / "src" / "com" / "lanremote" / "watch"
/ "MainActivity.java")
# 框架/接口回调:由 Android 或 RemoteClient 调用,本文件里只有定义是正常的
WHITELIST = {
"onCreate", "onDestroy", "onResume", "onPause", "onStart", "onStop",
"onRestart", "onNewIntent", "onSaveInstanceState", "onRestoreInstanceState",
"onBackPressed", "onKeyDown", "onKeyUp", "onWindowFocusChanged",
"onRequestPermissionsResult", "onActivityResult", "onOptionsItemSelected",
"onCreateOptionsMenu", "onPrepareOptionsMenu", "onConfigurationChanged",
"onTouchEvent", "onClick", "onLongClick", "onTouch", "onAnimationUpdate",
"onAnimationStart", "onAnimationEnd", "onAnimationCancel", "onAnimationRepeat",
"onPreDraw", "onGlobalLayout", "onScrollChanged", "onItemClick",
"onItemSelected", "onNothingSelected", "onFocusChange", "onEditorAction",
"run", "call", "get", "set", "equals", "hashCode", "toString",
"uncaughtException", "afterTextChanged", "beforeTextChanged",
"onTextChanged", "onProgressChanged", "onStartTrackingTouch",
"onStopTrackingTouch", "onDateSet", "onTimeSet", "onDismiss",
"onCancel", "onShow", "onKey", "onCreateDialog", "onPrepareDialog",
"onReceive", "onServiceConnected", "onServiceDisconnected",
"onBind", "onCreateView", "onViewCreated", "onDestroyView",
"onAttach", "onDetach", "onActivityCreated", "onOptionsMenuClosed",
"onContextItemSelected", "onCreateContextMenu", "handleMessage",
"onChildViewAdded", "onChildViewRemoved", "onGlobalFocusChanged",
"onLayoutChange", "onScrollStateChanged", "onScrolled",
"onPageStarted", "onPageFinished", "shouldOverrideUrlLoading",
"onReceivedError", "onDownloadStart", "onChildDraw",
# RemoteClient.Listener 的实现
"onDisconnected", "onEvent", "onFrame", "onProgress", "onLog",
}
# 已确认的历史遗留死代码:早期版本的实现,后来换成了别的写法,
# 函数留着但没人调。不影响功能,列出来是为了有朝一日清掉。
KNOWN_DEAD = {
"buildFeatureTab": "旧的分页构建方式,已被 renderGroups() 取代",
"pickFolder": "已被 pickFile() 取代",
"pickClipHistory": "功能已移到服务端注册表",
"confirmWallpaper": "功能已移到服务端注册表",
"confirmMonitor": "功能已移到服务端注册表",
"sendMedia": "已被 media() 取代",
"promptRun": "已被 runFeature() 取代",
"showInfo": "已被 feature_run 的 info 取代",
"findSavedToken": "旧的重连逻辑,已被 RelayTransport 取代",
"glassBackground": "旧的美化辅助函数,样式改成 glass() 后没人用了",
"sp": "旧的字号辅助,已被 dp() 取代",
}
# 关键路径:这些漏了就是白屏 / 功能全无,必须硬性拦住
CRITICAL = ("buildConnectPanel", "buildControlPanel", "initTabs",
"buildMirrorPanel", "buildLanPanel")
passed = 0
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 defined_methods(text: str) -> list[str]:
pattern = re.compile(
r"(?:private|public|protected|static|final|\s)+"
r"[\w<>\[\],.\s]+\s+(\w+)\s*\([^)]*\)\s*(?:throws [\w.,\s]+)?\{")
names = []
for match in pattern.finditer(text):
name = match.group(1)
if name in ("if", "for", "while", "switch", "catch", "synchronized",
"try", "return", "new"):
continue
names.append(name)
return names
def main() -> int:
check("MainActivity.java 在", MAIN.is_file())
if not MAIN.is_file():
return 1
text = MAIN.read_text(encoding="utf-8")
print()
print("一、关键路径必须被调用(漏了就是白屏)")
# initTabs 专项:白屏的根因就是它
init_def = len(re.findall(r"private void initTabs\s*\(", text))
init_use = len(re.findall(r"\binitTabs\s*\(\s*\)", text))
check("initTabs() 有定义", init_def >= 1, f"定义 {init_def} 处")
check("initTabs() 被调用(否则连上后一片白屏)",
init_use >= 2, f"出现 {init_use} 次(1 次是定义,>1 才算被调用)")
check("initTabs() 紧跟 buildControlPanel()",
re.search(r"buildControlPanel\(\);\s*(?://[^\n]*\n\s*)*initTabs\(\);",
text) is not None,
"必须在 buildControlPanel() 之后立刻调用")
for method in CRITICAL:
n = len(re.findall(r"\b" + method + r"\s*\(", text))
check(f"{method}() 被调用", n > 1, f"出现 {n} 次")
print()
print("二、分页定义完整性")
tab_names = re.findall(r'new TabDef\("([^"]+)"', text)
check("定义了 10 个分页", len(tab_names) == 10, f"实际 {len(tab_names)} 个")
print(f" 分页: {' '.join(tab_names)}")
check("selectTab 有定义", "private void selectTab(" in text)
check("selectTab 被调用", len(re.findall(r"\bselectTab\s*\(", text)) > 1)
print()
print("三、未调用方法扫描")
names = defined_methods(text)
orphans = []
for name in sorted(set(names)):
if name in WHITELIST or name in KNOWN_DEAD:
continue
n = len(re.findall(r"\b" + re.escape(name) + r"\s*\(", text))
if n <= 1:
orphans.append((name, n))
check("没有**未知**的未调用方法", not orphans,
"\n ".join(f"{n}({c} 次)" for n, c in orphans)
if orphans else "")
if orphans:
print()
print(" 未知的未调用方法(可能是漏接的事件处理,也可能是新的死代码):")
for name, cnt in orphans:
print(f" {name} (出现 {cnt} 次)")
if KNOWN_DEAD:
print()
print(f" 已知死代码 {len(KNOWN_DEAD)} 个(不影响功能,待清理):")
for name, why in sorted(KNOWN_DEAD.items()):
still = len(re.findall(r"\b" + re.escape(name) + r"\s*\(", text))
mark = "" if still else " [已删除]"
print(f" {name}: {why}{mark}")
print()
print("=" * 58)
print(f"通过 {passed} 项,失败 {failed} 项")
return 0 if failed == 0 else 1
if __name__ == "__main__":
sys.exit(main())