chore: import original project baseline

Import the pre-repair source tree as the history baseline.
Runtime data (data/), virtualenvs, bytecode caches and logs are
gitignored so local secrets and user state stay out of the repo.
This commit is contained in:
2026-09-17 16:40:01 +08:00
commit a7412824e0
124 changed files with 26747 additions and 0 deletions
+201
View File
@@ -0,0 +1,201 @@
"""offscreen 冒烟测试:主窗口实例化 + 核心链路(不启动真实 LLM)
运行: QT_QPA_PLATFORM=offscreen python tests/smoke_offscreen.py
"""
import os
import sys
import json
import time
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
os.environ.setdefault("HAOCODE_RENDER", "software")
os.environ["QTWEBENGINE_CHROMIUM_FLAGS"] = "--disable-gpu" # 绕过 AMD 核显 context lost # 软渲染,离屏最稳
# 铁律:测试不得污染真实 data/chat_history.db → DBManager 默认路径重定向到临时文件
import tempfile as _tf # noqa: E402
import core.db_manager as _dbm # noqa: E402
_dbm._DEFAULT_DB = os.path.join(_tf.gettempdir(), f"haocode_test_smoke_offscreen_{os.getpid()}.db")
import ctypes # noqa: E402
from PyQt6.QtWidgets import QApplication # noqa: E402
from PyQt6.QtCore import QTimer # noqa: E402
# QtWebEngine 必须在 QApplication 创建前 import
from ui.views.main_window import MainWindow # noqa: E402
app = QApplication(sys.argv)
results = []
def check(name, fn):
try:
fn()
print(f" PASS {name}")
results.append(True)
except Exception as e:
import traceback
traceback.print_exc()
print(f" FAIL {name}: {e}")
results.append(False)
window = None
page_ready = {"ok": False}
def on_page_load_progress(v):
pass
def on_js_console(level, msg, line, src):
pass
def try_load():
global window
try:
window = MainWindow()
page_ready["ok"] = True
except Exception:
import traceback
traceback.print_exc()
finish(False)
return
# MainWindow 构造完成时 HTML/JS 已就绪(loadFinished 可能早于连接)
QTimer.singleShot(6000, run_checks) # 等 JS 引擎 + 历史渲染完成
def run_checks():
# 1) 窗口已创建
check("MainWindow 实例化", lambda: (_ for _ in ()).throw(AssertionError("no window")) if window is None else None)
# 2) DB 链路
def db_chain():
assert window.db is not None
sessions = window.db.get_all_sessions()
assert isinstance(sessions, list)
check("DB 会话列表", db_chain)
# 3) build_api_contextP0 修复验证:不再有 reasoning 字段)
def ctx_build():
sid = window.current_session_id
if not sid:
return
msgs = window.build_api_context(sid)
assert isinstance(msgs, list)
for m in msgs:
assert "reasoning" not in m, f"reasoning 字段仍在 API payload 里: {m.get('role')}"
check("build_api_context 无 reasoning 字段", ctx_build)
# 4) CJK token 估算
def est():
n = window._estimate_token_count([
{"role": "user", "content": "你好,世界!这是一段中文测试。"},
{"role": "assistant", "content": "hello world " * 20},
])
assert isinstance(n, int) and n > 0
check("CJK token 估算", est)
# 5) AgentWorker 可构造 + 信号齐全
def worker():
from core.llm_engine import AgentWorker, TitleWorker
w = AgentWorker(window.current_provider, window.current_model,
[{"role": "user", "content": "x"}])
for sig in ("chunk_received", "reasoning_received", "error_occurred",
"tool_execution_started", "tool_execution_updated",
"tool_execution_finished", "context_compacted"):
assert hasattr(w, sig), sig
t = TitleWorker(window.current_provider, window.current_model,
[{"role": "user", "content": "x"}])
assert hasattr(t, "chunk_received")
check("AgentWorker/TitleWorker 构造", worker)
# 6) bridge 工具方法
def bridge():
b = window.chat_bridge
for m in ("tool_execution_started", "tool_execution_updated",
"tool_execution_finished", "show_note"):
assert hasattr(b, m), m
check("ChatBridge 工具事件方法", bridge)
# 7) 离屏渲染 JS 就绪
check("Web 页面加载完成", lambda: (_ for _ in ()).throw(AssertionError("page not ready")) if not page_ready["ok"] else None)
# 8) 🌟 KaTeX 公式渲染(真实页面上下文:资源加载 + [...] 供应商格式 + 行内 $)
def _run_js(js, timeout_s=10):
result = {"val": None, "done": False}
def on_ret(val):
if isinstance(val, str):
try:
val = json.loads(val)
except Exception:
pass
result["val"] = val
result["done"] = True
if hasattr(window.browser, "execute_js_async"): # WebView2 路径(cb 收 JSON 字符串)
window.browser.execute_js_async(js, on_ret)
else: # QtWebEngine 路径(cb 收 Python 对象)
window.browser.page().runJavaScript(js, on_ret)
t0 = time.time()
while not result["done"] and time.time() - t0 < timeout_s:
app.processEvents()
time.sleep(0.05)
assert result["done"], "JS 执行超时"
return result["val"]
def katex_render():
# 等页面 JS 就绪(WV2 冷启动可能慢;app.js 就绪时置 window.jsReady=true
for _ in range(30):
if _run_js("window.jsReady === true ? 1 : 0", timeout_s=3) == 1:
break
time.sleep(0.5)
else:
raise AssertionError("页面 JS 未就绪(jsReady")
ver = _run_js("typeof katex !== 'undefined' ? katex.version : null")
assert ver, "katex 全局缺失(资源加载失败?)"
js = ("(function(){var s = '结果:" + chr(92) + "n[" + chr(92) + "nP_4=" + chr(92)*2 + "operatorname{BRF}(M_4,M_5)" + chr(92) + "n]" + chr(92) + "n" + chr(92) + "n行内 $E=mc^2$ 结束。';"
"var html = safeHtml(marked.parse(s));return {"
"has: html.indexOf('katex') !== -1,"
"disp: (html.match(/katex-display/g) || []).length,"
'inline: (html.match(/class=\"katex\"/g) || []).length,'
"leaked: html.indexOf('@@K') !== -1,"
"vis: html.indexOf('katex-html') !== -1,"
"tex: html.indexOf('\\operatorname{BRF}') !== -1};})()")
r = _run_js(js)
assert isinstance(r, dict), f"意外返回: {r!r}"
assert r.get("has"), "未产生 KaTeX HTML"
assert r.get("disp", 0) >= 1, "块公式未渲染为 katex-display"
assert r.get("inline", 0) >= 1, "行内公式未渲染"
assert not r.get("leaked"), "占位符泄漏"
assert r.get("vis"), "缺少 katex-html 可视层(KaTeX 未真正渲染)"
assert r.get("tex"), "tex 未正确传入(转义错误)"
print(f" [info] KaTeX {ver} | display={r.get('disp')} inline={r.get('inline')}")
check("KaTeX 公式渲染([...] 供应商格式 + 行内 $)", katex_render)
print(f"\n===== {'ALL PASS' if all(results) else 'HAS FAILURES'}: {sum(results)}/{len(results)} =====")
try:
window.close()
except Exception:
pass
app.quit()
def finish(ok=None):
app.quit()
QTimer.singleShot(500, try_load)
QTimer.singleShot(90000, finish) # 总超时
app.exec()
sys.exit(0 if all(results) else 1)