# -*- coding: utf-8 -*- """只读回归测试:开关的方向性 + 能开就要能关。 **重要:这个测试不执行任何破坏性功能。** 之前的版本会真的跑每一个 toggle,包括「锁键盘鼠标」(要管理员才能解) 和「任务栏自动隐藏」(会杀 explorer)——在用户真机上那是闯祸。 现在改成: * 系统状态只用只读方式核对(注册表 / 窗口枚举) * 只真正执行明确安全的开关(循环播放、随机播放) * 其余开关只核对实现里两个方向都在 * 关窗口那条链路真的执行(它本来就是把东西关掉,安全) """ import ctypes import os import sys import tempfile import time import winreg from pathlib import Path ROOT = Path(__file__).resolve().parent.parent sys.path.insert(0, str(ROOT / "windows")) from lanremote.actions import Actions # noqa: E402 from lanremote.config import Store # noqa: E402 from lanremote.features import Features # noqa: E402 from lanremote.headless import HeadlessUI # noqa: E402 FEATURES_PY = ROOT / "windows" / "lanremote" / "features.py" MAIN_JAVA = (ROOT / "android" / "src" / "com" / "lanremote" / "watch" / "MainActivity.java") QUOTE = chr(34) ON_ARG = "params.get(" + QUOTE + "on" + QUOTE + ")" 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 visible_windows(keyword): user32 = ctypes.windll.user32 proc = ctypes.WINFUNCTYPE(ctypes.c_bool, ctypes.c_void_p, ctypes.c_void_p) found = [] def callback(hwnd, _l): pid = ctypes.c_ulong() user32.GetWindowThreadProcessId(hwnd, ctypes.byref(pid)) if pid.value != os.getpid() or not user32.IsWindowVisible(hwnd): return True length = user32.GetWindowTextLengthW(hwnd) buf = ctypes.create_unicode_buffer(length + 2) user32.GetWindowTextW(hwnd, buf, length + 2) if keyword in buf.value: found.append(buf.value) return True user32.EnumWindows(proc(callback), 0) return found def main() -> int: store = Store(Path(tempfile.mkdtemp())) ui = HeadlessUI(store) actions = Actions(ui, store) features = Features(actions) ui.root # --------------------------------------------------------------- print("一、功能表完整性") defs = features.defs() check("功能数量 > 200", len(defs) > 200, str(len(defs)) + " 个") missing = [d["name"] for d in defs if not hasattr(features, "_do_" + d["name"])] check("没有缺实现的功能", not missing, str(missing)[:120]) groups = sorted({d["group"] for d in defs}) check("分组数 = 14", len(groups) == 14, str(groups)) # --------------------------------------------------------------- print() print("二、每个开关都实现了两个方向") toggles = [d for d in defs if d.get("kind") == "toggle"] check("有 toggle 类功能", len(toggles) >= 8, str(len(toggles)) + " 个") source = FEATURES_PY.read_text(encoding="utf-8") one_way = [] for item in toggles: name = item["name"] index = source.find("def _do_" + name + "(") body = source[index:index + 1400] if index > 0 else source if ON_ARG not in body and "_truthy(params" not in body: one_way.append(name) check("所有开关都用到了 on 参数", not one_way, "只往一个方向的: " + str(one_way)) main_java = MAIN_JAVA.read_text(encoding="utf-8") has_put = "feature.put(" + QUOTE + "on" + QUOTE in main_java check("手表 toggle 后会翻转本地缓存", has_put, "没找到 feature.put(on, ...)") # --------------------------------------------------------------- print() print("三、安全开关真的往返一次") for name in ("loop_play", "shuffle_play"): try: on = features.run(name, {"on": True}) off = features.run(name, {"on": False}) check(name + " 两个方向话术不同", str(on) != str(off), "开=" + repr(on) + " 关=" + repr(off)) except Exception as exc: # noqa: BLE001 check(name + " 往返", False, str(exc)[:80]) # --------------------------------------------------------------- print() print("四、手表开的画面必须能关掉") ui.notify("测试通知一", "内容") ui.notify("测试通知二", "内容") time.sleep(1.2) before = visible_windows("通知") check("通知确实弹出来了", len(before) >= 2, str(len(before)) + " 个") result = features.run("close_visuals", {}) time.sleep(1.2) after = visible_windows("通知") check("close_visuals 关掉了通知", len(after) == 0, "关前 " + str(len(before)) + ",关后 " + str(len(after)) + "(" + str(result) + ")") features.run("big_text", {"text": "测试大字XYZ"}) time.sleep(1.0) check("全屏大字弹出来了", len(visible_windows("测试大字XYZ")) >= 1) features.run("close_visuals", {}) time.sleep(1.0) check("close_visuals 关掉了全屏大字", len(visible_windows("测试大字XYZ")) == 0) again = features.run("close_visuals", {}) check("没东西可关时也不报错", "没有" in str(again), str(again)) # --------------------------------------------------------------- print() print("五、系统状态用只读方式核对") with winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"Software\Microsoft\Windows\CurrentVersion" r"\Explorer\StuckRects3") as key: raw, _kind = winreg.QueryValueEx(key, "Settings") blob = bytes(raw) check("Settings 能读到且够长", len(blob) > 8, str(len(blob)) + " 字节") check("_taskbar_hidden() 与注册表一致", features._taskbar_hidden() == bool(blob[8] & 1), "函数=" + str(features._taskbar_hidden()) + " 注册表=" + str(bool(blob[8] & 1))) from lanremote import autostart try: with winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"Software\Microsoft\Windows\CurrentVersion\Run" ) as key: winreg.QueryValueEx(key, "LanRemoteAgent") registry_on = True except FileNotFoundError: registry_on = False check("is_enabled() 与注册表一致", autostart.is_enabled() == registry_on, "函数=" + str(autostart.is_enabled()) + " 注册表=" + str(registry_on)) ui.stop() print() print("=" * 66) print("通过 " + str(passed) + " 项,失败 " + str(failed) + " 项") if failed: print("失败项:") return 0 if failed == 0 else 1 if __name__ == "__main__": sys.exit(main())