# -*- coding: utf-8 -*- """独立调试器窗口 + 调试日志协议 回归测试(离屏,临时文件,不碰真实 DB/日志) 运行: PYTHONIOENCODING=utf-8 QT_QPA_PLATFORM=offscreen python tests/test_debug_window.py """ import sys, os, re, tempfile sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) _TMPDIR = tempfile.mkdtemp(prefix="haocode_dbg_") os.environ["HAOCODE_DEBUG_LOG"] = os.path.join(_TMPDIR, "debug_session.log") os.environ["HAOCODE_DEBUG_CMD"] = os.path.join(_TMPDIR, "debug_window.cmd") os.environ.setdefault("QT_QPA_PLATFORM", "offscreen") os.environ["QTWEBENGINE_CHROMIUM_FLAGS"] = "--disable-gpu" from core import debug_log as dl PASS, FAIL = 0, 0 def check(name, cond, extra=""): global PASS, FAIL if cond: PASS += 1 print(f" PASS {name}") else: FAIL += 1 print(f" FAIL {name} {extra}") def read_log(): if not os.path.exists(dl.DEBUG_LOG_PATH): return "" with open(dl.DEBUG_LOG_PATH, "r", encoding="utf-8") as f: return f.read() LINE_RE = re.compile(r"^\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}\] " r"\[(USER|AGENT|APP|SYS)\] .+$") # ============ T1: debug_log 三方写入 + 行格式 ============ print("T1: debug_log 行格式与三方 TAG") dl.debug_log("hello app", "APP") dl.debug_log("代理注入一条备注", "AGENT") dl.debug_log("用户观察到标签 40.5k", "USER") lines = [l for l in read_log().splitlines() if l] check("三行全部写入", len(lines) == 3, lines) check("行格式 [ts] [TAG] msg", all(LINE_RE.match(l) for l in lines), lines) check("TAG 顺序 APP/AGENT/USER", [re.search(r"\[(USER|AGENT|APP|SYS)\]", l).group(1) for l in lines] == ["APP", "AGENT", "USER"]) # ============ T2: poll_debug_cmd 控制协议 ============ print("T2: poll_debug_cmd 消费语义") with open(dl.DEBUG_CMD_PATH, "w", encoding="utf-8") as f: f.write("show\n") check("show 被识别", dl.poll_debug_cmd() == "show") check("文件被消费(再读为 None)", dl.poll_debug_cmd() is None) with open(dl.DEBUG_CMD_PATH, "w", encoding="utf-8") as f: f.write(" HIDE ") check("hide 大小写/空白容忍", dl.poll_debug_cmd() == "hide") with open(dl.DEBUG_CMD_PATH, "w", encoding="utf-8") as f: f.write("garbage") check("非法内容忽略", dl.poll_debug_cmd() is None) # ============ T3-T6: DebugWindow 行为(离屏) ============ print("T3: DebugWindow 用户输入 → [USER] 落盘") from PyQt6.QtWidgets import QApplication app = QApplication.instance() or QApplication(sys.argv) import ui.views.debug_window as dw # 应用日志 tab 指向临时文件(不读真实 diag.log) _tmp_diag = os.path.join(_TMPDIR, "diag.log") dw._APP_LOGS[:] = [("DIAG", _tmp_diag)] win = dw.DebugWindow() check("窗口标题独立", win.windowTitle() == "Haocode 调试器") win._input.setText(" 发送后标签跳到 80k ") win._on_submit() check("输入框被清空", win._input.text() == "") check("[USER] 已落盘且去首尾空白", any(l.endswith("[USER] 发送后标签跳到 80k") for l in read_log().splitlines())) print("T4: 首次 tick 记 [SYS] 开启事件") win._tick() check("[SYS] 调试窗口开启 已写入", any("[SYS] 调试窗口开启" in l for l in read_log().splitlines())) print("T5: 实时 tail 会话日志 + 应用日志") dl.debug_log("tick 前注入的 APP 事件", "APP") dl.debug_log("AGENT: 现在检查 compaction_diag.log", "AGENT") win._tick() txt = win._view_session.toPlainText() check("会话视图含 APP 事件", "tick 前注入的 APP 事件" in txt, txt[-300:]) check("会话视图含 AGENT 注入", "AGENT: 现在检查 compaction_diag.log" in txt) with open(_tmp_diag, "w", encoding="utf-8") as f: f.write("[12:00:00.000] DIAG_TEST_LINE\n") win._tick() check("应用日志视图含 DIAG tail", "DIAG_TEST_LINE" in win._view_app.toPlainText()) check("应用日志带文件头", "[DIAG]" in win._view_app.toPlainText()) print("T6: 暂停显示 + 截断重置") win._chk_pause.setChecked(True) # 暂停 dl.debug_log("暂停期间的行不应上屏", "APP") win._tick() check("暂停期间不上屏", "暂停期间的行不应上屏" not in win._view_session.toPlainText()) # 文件截断(模拟「清空会话日志」)→ 偏移重置,新行仍可读取 win._chk_pause.setChecked(False) with open(dl.DEBUG_LOG_PATH, "w", encoding="utf-8") as f: f.write("[2026-01-01 00:00:00.000] [SYS] 截断后新内容\n") win._tick() check("截断后偏移重置、新行上屏", "截断后新内容" in win._view_session.toPlainText()) win.close() # ============ T4: 调试窗口随程序启动(autostart_debug_window) ============ print("T4: 调试窗口随程序启动") p = dl.DEBUG_CMD_PATH if os.path.exists(p): os.remove(p) # 显式 false → 不写控制文件 check("T4.autostart=False 不写控制文件", dl.autostart_debug_window({"debug_window_autostart": False}) is False and not os.path.exists(p)) # 缺省(键不存在)→ 默认开 check("T4.缺省(无键)写入 show", dl.autostart_debug_window({}) is True and os.path.exists(p)) check("T4.轮询消费 show", dl.poll_debug_cmd() == "show" and not os.path.exists(p)) # 显式 true check("T4.autostart=true 写入 show", dl.autostart_debug_window({"debug_window_autostart": True}) is True) check("T4.轮询再消费 show", dl.poll_debug_cmd() == "show") print(f"\n===== {PASS} PASS / {FAIL} FAIL =====") sys.exit(1 if FAIL else 0)