# coding: utf-8 """收发日志上限与队列消费回归自检(无窗口, 离屏渲染)。 背景: v0.4.3 之前收发日志用 textCursor 手工裁剪, 其中 `cursor.Start` / `cursor.BlockUnderCursor` 是实例级枚举访问 —— PySide6 6.11 移除了该形式, 日志一旦超过 500 行, 每次 _dlpc_log 追加后必抛 AttributeError; 且异常会 逃出 _poll_dlpc 的 drain 循环(旧 try 只捕获 queue.Empty), 每 tick 最多 消费到第一条 event, `_dlpc_log_q` 棘轮积压、日志裁剪与自动滚动全部失效, 电源自检/功率检测序列还会被异常打断在启动处(之后所有命令被"序列进行中" 拦截)。v0.4.3 改为 setMaximumBlockCount 环形裁剪 + 逐事件异常兜底。 本测试保证: 1. 连续追加 600 行日志不抛异常, document().blockCount() 环形封顶 500, 末行在; 2. 单条异常事件(模拟未来引入的坏数据)不中断 _poll_dlpc 本轮队列消费 —— 其后的事件仍全部入日志、队列清空。 运行: python selftest_log.py (依赖 PySide6-Fluent-Widgets 与 pyserial —— 串口卡构建后 txt_dlpc 才存在) """ import os import sys os.environ.setdefault("QT_QPA_PLATFORM", "offscreen") sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) from PySide6.QtWidgets import QApplication from qfluentwidgets import Theme, setTheme import app_fluent import dlpc_rs485 def check_cap(win): for i in range(600): win._dlpc_log(f"cap-line-{i}") blocks = win.txt_dlpc.document().blockCount() has_last = "cap-line-599" in win.txt_dlpc.toPlainText() print(f" 追加 600 行: blockCount={blocks}(封顶 500), 末行在: {has_last}") return blocks <= 500 and has_last def check_drain_resilience(win): win._timer.stop() # 排除 80ms 轮询定时器干扰 win._dlpc_log_q.put(("pdframe", "poison-not-a-frame")) # 该条分发必抛异常 win._dlpc_log_q.put(("event", "after-poison-1")) win._dlpc_log_q.put(("event", "after-poison-2")) win._poll_dlpc() # 单次手动驱动(不进事件循环) left = win._dlpc_log_q.qsize() text = win.txt_dlpc.toPlainText() drained = left == 0 and "after-poison-1" in text and "after-poison-2" in text print(f" 坏条目后消费不中断: 残留队列={left}, 后续事件全部写入: {drained}") return drained def main(): if not dlpc_rs485.HAS_SERIAL: print("FAILED 缺少 pyserial: 串口卡未构建, txt_dlpc 不存在") return 1 app = QApplication.instance() or QApplication(sys.argv) setTheme(Theme.AUTO) win = app_fluent.App(0) passed = check_cap(win) passed &= check_drain_resilience(win) win.close() print("ALL PASS" if passed else "FAILED") return 0 if passed else 1 if __name__ == "__main__": sys.exit(main())