gengx
This commit is contained in:
@@ -0,0 +1,193 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""双引擎并存集成测试。
|
||||
|
||||
覆盖:发送互斥锁、B 引擎投递(enqueue_detected)、队列字段透传(load/persist
|
||||
往返)、has_active_pending 去重判断、detected_by 合并、DataEngine 真实集成。
|
||||
用法:python test_dual_engine.py
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
import threading
|
||||
import time
|
||||
|
||||
_ROOT = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.insert(0, _ROOT)
|
||||
|
||||
import send_lock
|
||||
import engine_b
|
||||
import wechat_bot
|
||||
|
||||
_PASS = 0
|
||||
_FAIL = 0
|
||||
|
||||
|
||||
def check(name, condition, detail=""):
|
||||
global _PASS, _FAIL
|
||||
if condition:
|
||||
_PASS += 1
|
||||
print(f" [PASS] {name}")
|
||||
else:
|
||||
_FAIL += 1
|
||||
print(f" [FAIL] {name} {detail}")
|
||||
|
||||
|
||||
def make_mini_bot():
|
||||
"""构造最小 WeChatBot 对象:__new__ 绕过 1.3 万行 __init__,只补队列所需字段。"""
|
||||
bot = wechat_bot.WeChatBot.__new__(wechat_bot.WeChatBot)
|
||||
bot._pending_lock = threading.RLock()
|
||||
bot._pending_reply_sessions = {}
|
||||
bot._pending_reply_path = os.path.join(
|
||||
tempfile.gettempdir(), f"test_pending_{os.getpid()}.json"
|
||||
)
|
||||
bot._cancelled_reply_sessions = set()
|
||||
bot.identity_by_name = True
|
||||
|
||||
def fake_mark_reply_pending(fp, **kwargs):
|
||||
"""模拟真实 _mark_reply_pending_unlocked 的 setdefault + display_name 语义。"""
|
||||
key = fp.hex()
|
||||
if key in bot._cancelled_reply_sessions:
|
||||
return False
|
||||
state = bot._pending_reply_sessions.setdefault(key, {
|
||||
"batch_ready": False,
|
||||
"confirmed_unread": False,
|
||||
"requires_visual_proof": False,
|
||||
"visual_rejection_count": 0,
|
||||
"created_at": time.time(),
|
||||
"stage": "queued",
|
||||
"stage_started_at": time.time(),
|
||||
"stage_history": [],
|
||||
})
|
||||
name = str(kwargs.get("display_name") or "").strip()
|
||||
if name and not state.get("display_name"):
|
||||
state["display_name"] = name
|
||||
return True
|
||||
|
||||
bot._mark_reply_pending = fake_mark_reply_pending
|
||||
return bot
|
||||
|
||||
|
||||
def test_send_lock():
|
||||
print("[1] 发送互斥锁")
|
||||
assert send_lock.try_acquire("test_a", 0), "应能拿到锁"
|
||||
held = send_lock.try_acquire("test_b", 0)
|
||||
check("互斥:持锁期间他人抢不到", not held)
|
||||
send_lock.release()
|
||||
check("释放后他人可抢到", send_lock.try_acquire("test_c", 0))
|
||||
send_lock.release()
|
||||
# 上下文管理器
|
||||
with send_lock.lock("test_d") as ok:
|
||||
check("上下文管理器拿锁", ok)
|
||||
check("持锁期间互斥", not send_lock.try_acquire("test_e", 0))
|
||||
check("退出上下文已释放", send_lock.try_acquire("test_f", 0))
|
||||
send_lock.release()
|
||||
|
||||
|
||||
def test_enqueue_and_persist():
|
||||
print("[2] B 引擎投递 + 队列字段透传")
|
||||
bot = make_mini_bot()
|
||||
fp_hex = "ab" * 40 # 40 字节会话指纹
|
||||
ok, reason = bot.enqueue_detected(
|
||||
fp_hex=fp_hex,
|
||||
dedup_key=f"{fp_hex}:1234.5",
|
||||
detected_by="engine_b",
|
||||
chat_text="我想挂号",
|
||||
display_name="测试客户",
|
||||
last_lines=["我想挂号"],
|
||||
)
|
||||
check("首次投递成功", ok, reason)
|
||||
state = bot._pending_reply_sessions.get(fp_hex)
|
||||
check("条目已创建", state is not None)
|
||||
if state:
|
||||
check("dedup_key 已写入", state.get("dedup_key") == f"{fp_hex}:1234.5")
|
||||
check("detected_by=engine_b", state.get("detected_by") == "engine_b")
|
||||
check("chat_text 已写入", state.get("chat_text") == "我想挂号")
|
||||
check("display_name 已写入", state.get("display_name") == "测试客户")
|
||||
check("stage=queued", state.get("stage") == "queued")
|
||||
|
||||
# 重复投递同一会话:只补来源标记,不覆盖
|
||||
ok, reason = bot.enqueue_detected(
|
||||
fp_hex=fp_hex,
|
||||
dedup_key=f"{fp_hex}:1234.5",
|
||||
detected_by="engine_a",
|
||||
chat_text="",
|
||||
)
|
||||
check("重复投递仍成功", ok, reason)
|
||||
state = bot._pending_reply_sessions.get(fp_hex)
|
||||
check("detected_by 合并为双引擎", state.get("detected_by") == "engine_b|engine_a", state.get("detected_by"))
|
||||
check("chat_text 未被覆盖", state.get("chat_text") == "我想挂号")
|
||||
|
||||
# 持久化往返:新字段不丢
|
||||
ok = bot._persist_pending_replies()
|
||||
check("持久化成功", ok)
|
||||
reloaded = wechat_bot.WeChatBot._load_pending_replies(bot)
|
||||
restored = reloaded.get(fp_hex)
|
||||
check("磁盘恢复条目", restored is not None)
|
||||
if restored:
|
||||
check("恢复后 dedup_key 保留", restored.get("dedup_key") == f"{fp_hex}:1234.5")
|
||||
check("恢复后 detected_by 保留", restored.get("detected_by") == "engine_b|engine_a")
|
||||
check("恢复后 detection_ts>0", float(restored.get("detection_ts") or 0) > 0)
|
||||
check("恢复后 sending_owner 为空", restored.get("sending_owner") == "")
|
||||
check("恢复后 send_lock_ts=0", float(restored.get("send_lock_ts") or 0) == 0)
|
||||
|
||||
# has_active_pending
|
||||
check("活跃任务判断 True", bot.has_active_pending(fp_hex))
|
||||
bot._pending_reply_sessions[fp_hex]["stage"] = "sent"
|
||||
check("终态任务判断 False", not bot.has_active_pending(fp_hex))
|
||||
bot._pending_reply_sessions[fp_hex]["stage"] = "queued"
|
||||
|
||||
# merge_detected_by
|
||||
bot.merge_detected_by(fp_hex, "engine_a")
|
||||
state = bot._pending_reply_sessions.get(fp_hex)
|
||||
check("merge 幂等(不重复追加)", state.get("detected_by") == "engine_b|engine_a")
|
||||
try:
|
||||
os.unlink(bot._pending_reply_path)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
def test_engine_b_integration():
|
||||
print("[3] DataEngine 与真实 bot 集成")
|
||||
bot = make_mini_bot()
|
||||
now = time.time()
|
||||
conv_path = os.path.join(tempfile.gettempdir(), f"test_convs_{os.getpid()}.json")
|
||||
with open(conv_path, "w", encoding="utf-8") as handle:
|
||||
json.dump({
|
||||
"cd" * 40: {
|
||||
"display_name": "集成客户",
|
||||
"history": [
|
||||
{"role": "user", "content": "在吗", "ts": now - 30},
|
||||
{"role": "assistant", "content": "在的", "ts": now - 29},
|
||||
{"role": "user", "content": "帮我查下挂号的号源", "ts": now - 3},
|
||||
],
|
||||
"last_lines": ["帮我查下挂号的号源"],
|
||||
}
|
||||
}, handle, ensure_ascii=False)
|
||||
engine = engine_b.DataEngine(bot=bot, conversations_path=conv_path, poll_interval=0.5)
|
||||
engine.poll_once()
|
||||
fp_hex = "cd" * 40
|
||||
state = bot._pending_reply_sessions.get(fp_hex)
|
||||
check("B 引擎投递到真实队列", state is not None)
|
||||
if state:
|
||||
check("dedup_key 含时间戳", state.get("dedup_key") == f"{fp_hex}:{now - 3}")
|
||||
check("detected_by=engine_b", state.get("detected_by") == "engine_b")
|
||||
check("chat_text 为增量文本", state.get("chat_text") == "帮我查下挂号的号源")
|
||||
check("display_name 来自档案", state.get("display_name") == "集成客户")
|
||||
# 再次轮询不重复投递
|
||||
engine.poll_once()
|
||||
check("重复轮询不重复投递", engine.enqueued_count == 1)
|
||||
engine.stop()
|
||||
try:
|
||||
os.unlink(conv_path)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_send_lock()
|
||||
test_enqueue_and_persist()
|
||||
test_engine_b_integration()
|
||||
print(f"\n结果: {_PASS} 通过, {_FAIL} 失败")
|
||||
sys.exit(1 if _FAIL else 0)
|
||||
Reference in New Issue
Block a user