test: update smoke and regression suites for dual-renderer paths
Offscreen harnesses now resize+show before load, use per-instance webengine profile dirs and explicit exit codes; compaction and bash stream suites follow the renamed internals.
This commit is contained in:
+136
-13
@@ -17,18 +17,12 @@ os.environ.setdefault("HAOCODE_RENDER", "software")
|
||||
os.environ["QTWEBENGINE_CHROMIUM_FLAGS"] = "--disable-gpu" # 绕过 AMD 核显 context lost
|
||||
os.environ.setdefault("PYTHONIOENCODING", "utf-8")
|
||||
|
||||
# 铁律:测试不得污染真实 data/chat_history.db
|
||||
import core.db_manager as _dbm # noqa: E402
|
||||
_DB_TMP = os.path.join(tempfile.gettempdir(), f"haocode_test_bashpanel_{os.getpid()}.db")
|
||||
if os.path.exists(_DB_TMP):
|
||||
os.remove(_DB_TMP)
|
||||
_dbm._DEFAULT_DB = _DB_TMP
|
||||
|
||||
# 铁律:宽度记录会写 config.json → 测试指向临时配置,绝不碰真实 data/config.json
|
||||
_CFG_TMP = os.path.join(tempfile.gettempdir(), f"haocode_test_cfg_{os.getpid()}.json")
|
||||
with open(_CFG_TMP, "w", encoding="utf-8") as _f:
|
||||
_f.write('{"providers": {}, "mode_switch": true}')
|
||||
os.environ["HAOCODE_CONFIG_FILE"] = _CFG_TMP
|
||||
# 铁律(P0-01):临时数据库 + 临时配置必须在 import MainWindow 之前完成
|
||||
# (临时配置保留本套件的 mode_switch=true 语义)
|
||||
from tests._test_env import isolate # noqa: E402
|
||||
_TMP = isolate("bashpanel", config={"providers": {}, "mode_switch": True})
|
||||
_DB_TMP = _TMP["db"]
|
||||
_CFG_TMP = _TMP["config"]
|
||||
|
||||
from PyQt6.QtWidgets import QApplication # noqa: E402
|
||||
from PyQt6.QtTest import QTest # noqa: E402
|
||||
@@ -606,9 +600,138 @@ for s in (sr, sd):
|
||||
settle(450)
|
||||
check("P9.36 收尾:两栏均展开", (not sr.folded) and (not sd.folded))
|
||||
|
||||
# ======================================================================
|
||||
# 11) 🆕 P2-01:两栏按【启动顺序倒序】显示 + 重排复用实例/状态保持
|
||||
# · 排序键恒为启动序号(绝不用完成时间重排)
|
||||
# · 重排复用同一批 BashLayer:展开态、实时输出、代码框滚动、栏滚动位置保持
|
||||
# ======================================================================
|
||||
panel.clear_all()
|
||||
settle(120)
|
||||
|
||||
# ---- 11.1 运行中栏:最新启动在第一项 ----
|
||||
panel.on_started("s1", "bash", {"command": "cmd-s1"})
|
||||
panel.on_started("s2", "bash", {"command": "cmd-s2"})
|
||||
panel.on_started("s3", "bash", {"command": "cmd-s3"})
|
||||
settle(150)
|
||||
check("P11.1 运行中栏按启动倒序(s3 最新在第一项)",
|
||||
panel.layer_ids("running") == ["s3", "s2", "s1"],
|
||||
str(panel.layer_ids("running")))
|
||||
_rendered_run = [w for w in (panel.sec_running.lay.itemAt(i).widget()
|
||||
for i in range(panel.sec_running.lay.count()))
|
||||
if w is not None]
|
||||
check("P11.2 实际布局顺序与显示顺序一致(widget 复用、顺序反转)",
|
||||
[w.call_id for w in _rendered_run] == ["s3", "s2", "s1"],
|
||||
str([w.call_id for w in _rendered_run]))
|
||||
|
||||
# ---- 11.2 新任务触发重排:实例/展开态/实时输出/代码框滚动 全部保持 ----
|
||||
lay2 = panel._layers["s2"]
|
||||
lay2.toggle() # 展开 s2
|
||||
panel.on_output("s2", "x" * 2000 + "\n") # 实时输出(NoWrap → 有水平滚动范围)
|
||||
settle(120) # 等布局落定(真实用户滚动必然在渲染后;
|
||||
# 否则首帧布局 flush 会把水平滚动归零——测试时序伪影,非产品 bug)
|
||||
hbar = lay2.out_box.horizontalScrollBar()
|
||||
hbar.setValue(500)
|
||||
v0 = hbar.value()
|
||||
dv0 = panel.sec_done.scroll.verticalScrollBar().value()
|
||||
check("P11.3 前置:水平滚动处于非 0 位置", v0 > 0, f"v0={v0}")
|
||||
panel.on_started("s4", "bash", {"command": "cmd-s4"}) # 新任务 → 两栏重排
|
||||
settle(150)
|
||||
lay2b = panel._layers["s2"]
|
||||
check("P11.4 重排后 s2 仍是同一个 BashLayer 实例(不重建)", lay2b is lay2)
|
||||
check("P11.5 展开/折叠状态保持", lay2b.expanded is True and not lay2b.body.isHidden())
|
||||
check("P11.6 实时输出保持(未因重排丢失/重渲染)", lay2b._live == "x" * 2000 + "\n",
|
||||
repr(lay2b._live[:20]))
|
||||
check("P11.7 代码框水平滚动值保持", hbar.value() == v0, f"{hbar.value()} vs {v0}")
|
||||
check("P11.8 已完成栏 section 滚动位置保持",
|
||||
panel.sec_done.scroll.verticalScrollBar().value() == dv0)
|
||||
check("P11.9 运行中栏重排后仍启动倒序(s4 顶到第一项)",
|
||||
panel.layer_ids("running") == ["s4", "s3", "s2", "s1"],
|
||||
str(panel.layer_ids("running")))
|
||||
|
||||
# ---- 11.3 完成时间【不是】排序键:s2 启动更晚却先完成 → 仍在 s1 之上 ----
|
||||
panel.on_finished("s2", "bash", True, "$ cmd-s2\nok2\n[exit 0] (0.1s)")
|
||||
panel.on_finished("s1", "bash", True, "$ cmd-s1\nok1\n[exit 0] (0.2s)")
|
||||
settle(150)
|
||||
check("P11.10 已完成栏按启动倒序(s2 先完成仍在第一项,不按完成时间)",
|
||||
panel.layer_ids("done") == ["s2", "s1"], str(panel.layer_ids("done")))
|
||||
check("P11.11 完成后从运行中栏消失(启动位置不变,仅换栏)",
|
||||
panel.layer_ids("running") == ["s4", "s3"], str(panel.layer_ids("running")))
|
||||
|
||||
# ---- 11.4 运行中→已完成 仍占原启动位置(晚完成不顶到最上) ----
|
||||
panel.on_started("s6", "bash", {"command": "cmd-s6"})
|
||||
panel.on_finished("s6", "bash", True, "$ cmd-s6\nok6\n[exit 0] (0.1s)")
|
||||
check("P11.12 s6 最后启动 → 顶到已完成栏第一项",
|
||||
panel.layer_ids("done") == ["s6", "s2", "s1"], str(panel.layer_ids("done")))
|
||||
panel.on_finished("s3", "bash", True, "$ cmd-s3\nok3\n[exit 0] (0.3s)") # s3 最后完成
|
||||
settle(150)
|
||||
check("P11.13 s3 最后完成但按启动位置插入第二项(不顶到最上)",
|
||||
panel.layer_ids("done") == ["s6", "s3", "s2", "s1"], str(panel.layer_ids("done")))
|
||||
|
||||
# ---- 11.4b 已完成栏 section 滚动位置保持(有实际滚动范围时) ----
|
||||
_long = "\n".join(f"line-{j}" for j in range(20)) # 20 行 → 撑满 out_box 230px 上限
|
||||
panel._layers["s6"].set_finished(True, f"$ cmd-s6\n{_long}\n[exit 0] (0.1s)")
|
||||
panel._layers["s3"].set_finished(True, f"$ cmd-s3\n{_long}\n[exit 0] (0.2s)")
|
||||
panel._layers["s6"].toggle() # 展开两层 → 内容必然超出栏高,产生真实滚动范围
|
||||
panel._layers["s3"].toggle()
|
||||
settle(150)
|
||||
sbar = panel.sec_done.scroll.verticalScrollBar()
|
||||
sbar.setValue(30)
|
||||
sv0 = sbar.value()
|
||||
check("P11.13b 前置:已完成栏存在真实滚动范围(v>0)", sv0 > 0, f"sv0={sv0} max={sbar.maximum()}")
|
||||
panel.on_started("t0", "bash", {"command": "cmd-t0"}) # 新任务 → 两栏重排
|
||||
panel.on_finished("t0", "bash", True, "$ cmd-t0\nok\n[exit 0] (0.1s)")
|
||||
settle(150)
|
||||
check("P11.13c 重排后已完成栏 section 滚动值保持", sbar.value() == sv0,
|
||||
f"{sbar.value()} vs {sv0}")
|
||||
check("P11.13d 展开态在重排后仍保持", panel._layers["s6"].expanded is True
|
||||
and panel._layers["s3"].expanded is True)
|
||||
|
||||
# ---- 11.5 限量窗口:窗口成员不变,仅显示顺序反转(顶层 = 最新启动) ----
|
||||
for i in range(1, 32):
|
||||
cid = f"t{i}"
|
||||
panel.on_started(cid, "bash", {"command": f"cmd-{cid}"})
|
||||
panel.on_finished(cid, "bash", True, f"$ cmd-{cid}\nok\n[exit 0] (0.1s)")
|
||||
settle(200)
|
||||
check("P11.14 内部仍保留全部 36 个已完成(5 手工 + 31 批量)", len(panel.layer_ids("done")) == 36,
|
||||
str(len(panel.layer_ids("done"))))
|
||||
check("P11.15 限量显示仍为 30 层", panel.sec_done.count.text() == str(LAYER_LIMIT),
|
||||
panel.sec_done.count.text())
|
||||
check("P11.16 「仅显示最近 N 层」提示保留", "仅显示最近 30 层" in panel.sec_done.hint.text(),
|
||||
panel.sec_done.hint.text())
|
||||
_rendered_done = [w for w in (panel.sec_done.lay.itemAt(i).widget()
|
||||
for i in range(panel.sec_done.lay.count()))
|
||||
if w is not None]
|
||||
check("P11.17 渲染窗口 = 最近启动的 30 个(不含最早启动的 s1/s2/s3/s6/t0/t1)",
|
||||
[w.call_id for w in _rendered_done][0] == "t31"
|
||||
and "s1" not in [w.call_id for w in _rendered_done]
|
||||
and "s2" not in [w.call_id for w in _rendered_done]
|
||||
and len(_rendered_done) == 30,
|
||||
str([w.call_id for w in _rendered_done][:5]))
|
||||
check("P11.18 已完成栏顶层 = 最新启动(t31),底层 = 窗口内最早(t2)",
|
||||
[w.call_id for w in _rendered_done][0] == "t31"
|
||||
and [w.call_id for w in _rendered_done][-1] == "t2",
|
||||
str([w.call_id for w in _rendered_done][:2] + [w.call_id for w in _rendered_done][-1:]))
|
||||
|
||||
# ---- 11.6 DB 重建(切换会话):启动序号 = 消息链顺序 + 时间线内顺序 → 显示倒序 ----
|
||||
sid5 = window.db.create_session("P2-01 启动倒序")["id"]
|
||||
leaf5 = window.db.get_session_leaf(sid5)
|
||||
tl5 = json.dumps([{"t": "tool", "id": f"db{i}", "name": "bash",
|
||||
"args": json.dumps({"command": f"db-cmd{i}"}),
|
||||
"ok": True, "result": f"$ db-cmd{i}\nok\n[exit 0] (0.1s)"}
|
||||
for i in range(3)], ensure_ascii=False)
|
||||
window.db.add_message(sid5, "assistant", "a", leaf5, timeline=tl5)
|
||||
window.load_messages_to_web(sid5)
|
||||
settle(300)
|
||||
check("P11.19 DB 重建后已完成栏按启动倒序(db2 顶层、db0 底层)",
|
||||
panel.layer_ids("done") == ["db2", "db1", "db0"], str(panel.layer_ids("done")))
|
||||
check("P11.20 「all」仍返回原始启动序号(正序,调试口径不变)",
|
||||
panel.layer_ids() == ["db0", "db1", "db2"], str(panel.layer_ids()))
|
||||
|
||||
print("\n===== " + ("ALL PASS" if ok else "HAS FAILURES") + " =====", flush=True)
|
||||
if os.path.exists(_DB_TMP):
|
||||
os.remove(_DB_TMP)
|
||||
if os.path.exists(_CFG_TMP):
|
||||
os.remove(_CFG_TMP)
|
||||
sys.exit(0 if ok else 1)
|
||||
# offscreen 铁律:QtWebEngine 渲染/GPU 子进程在解释器退出时可能不回收 → 挂起;
|
||||
# 与 smoke_offscreen 等 offscreen harness 一致,用 os._exit 强制收尾(stdout 已 flush)
|
||||
os._exit(0 if ok else 1)
|
||||
|
||||
Reference in New Issue
Block a user