From e5d0db268b6f5d82de3a9c5778fe3fcd6b38440f Mon Sep 17 00:00:00 2001 From: "Mr.Xia" <1424473282@qq.com> Date: Sat, 4 Apr 2026 21:36:58 +0800 Subject: [PATCH] =?UTF-8?q?fix(test):=20=E4=BF=AE=E5=A4=8D=E5=AE=8C?= =?UTF-8?q?=E5=85=A8=E6=A8=A1=E6=8B=9F=E8=B5=9B=E9=A9=AC=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E7=9A=84=E5=B9=B6=E5=8F=91=E4=B8=8E=E8=B6=85=E6=97=B6=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将锁获取改为带超时的等待,避免因锁占用导致测试卡死 - 增加模拟任务的超时控制,防止无限等待 - 添加异常捕获与详细错误信息输出,便于问题定位 - 确保在测试异常或超时后正确清理资源 --- .../group_horse_racing/test_commands.py | 167 ++++++++++-------- 1 file changed, 93 insertions(+), 74 deletions(-) diff --git a/danding_bot/plugins/group_horse_racing/test_commands.py b/danding_bot/plugins/group_horse_racing/test_commands.py index fb6a69b..56cba00 100644 --- a/danding_bot/plugins/group_horse_racing/test_commands.py +++ b/danding_bot/plugins/group_horse_racing/test_commands.py @@ -8,6 +8,7 @@ from .models import Horse, HorseState, RoomState, Bet, RaceResult import asyncio import random from datetime import datetime +import traceback from . import commands as commands_mod @@ -205,98 +206,116 @@ async def handle_test_simulate_race(bot: Bot, event: Event): scope = get_scope(event) lock = room_store.get_lock(scope) + try: + await asyncio.wait_for(lock.acquire(), timeout=3) + except TimeoutError: + await test_simulate_race_cmd.finish("完全模拟失败:房间锁被占用(可能有卡住的赛马/测试任务),请稍后重试或先重启 Bot") + return - async with lock: + try: race_engine.stop_race(scope) room_store.delete_room(scope) + finally: + lock.release() - original_room_store = commands_mod.room_store - original_points_service = commands_mod.points_service - original_message_service = commands_mod.message_service - original_tick_interval = commands_mod.config.RACE_TICK_INTERVAL + original_room_store = commands_mod.room_store + original_points_service = commands_mod.points_service + original_message_service = commands_mod.message_service + original_tick_interval = commands_mod.config.RACE_TICK_INTERVAL - fake_room_store = _InMemoryRoomStore() - fake_points_service = _InMemoryPointsService() - fake_message_service = _NoopMessageService() - fake_bot = _FakeBot() + fake_room_store = _InMemoryRoomStore() + fake_points_service = _InMemoryPointsService() + fake_message_service = _NoopMessageService() + fake_bot = _FakeBot() - try: - commands_mod.room_store = fake_room_store - commands_mod.points_service = fake_points_service - commands_mod.message_service = fake_message_service - commands_mod.config.RACE_TICK_INTERVAL = 0 + start_task: asyncio.Task | None = None + room = None - room = fake_room_store.create_room(scope) - horse_names = _generate_random_horse_names(8) - for idx, horse_name in enumerate(horse_names, start=1): - owner_id = f"sim_user_{idx}" - room.horses[horse_name] = Horse(owner_id=owner_id, name=horse_name, state=HorseState.RACING) + try: + commands_mod.room_store = fake_room_store + commands_mod.points_service = fake_points_service + commands_mod.message_service = fake_message_service + commands_mod.config.RACE_TICK_INTERVAL = 0 - bet_amount = max(commands_mod.config.MIN_BET, 10) - room.bets.append(Bet(user_id="bettor_1", horse_name=horse_names[0], amount=bet_amount)) - room.bets.append(Bet(user_id="bettor_2", horse_name=horse_names[1], amount=bet_amount * 2)) + room = fake_room_store.create_room(scope) + horse_names = _generate_random_horse_names(8) + for idx, horse_name in enumerate(horse_names, start=1): + owner_id = f"sim_user_{idx}" + room.horses[horse_name] = Horse(owner_id=owner_id, name=horse_name, state=HorseState.RACING) - room.state = RoomState.WAITING + bet_amount = max(commands_mod.config.MIN_BET, 10) + room.bets.append(Bet(user_id="bettor_1", horse_name=horse_names[0], amount=bet_amount)) + room.bets.append(Bet(user_id="bettor_2", horse_name=horse_names[1], amount=bet_amount * 2)) - for horse in room.horses.values(): - horse.state = HorseState.RACING + room.state = RoomState.WAITING - start_task = asyncio.create_task(commands_mod.run_race_with_settlement(fake_bot, room, scope)) - commands_mod.race_engine.register_task(scope, start_task) - await start_task + for horse in room.horses.values(): + horse.state = HorseState.RACING - messages = [m.get("message", "") for m in fake_bot.messages] - if not messages: - await test_simulate_race_cmd.finish("完全模拟失败:未捕获到任何消息") - return + start_task = asyncio.create_task(commands_mod.run_race_with_settlement(fake_bot, room, scope)) + commands_mod.race_engine.register_task(scope, start_task) + await asyncio.wait_for(start_task, timeout=15) - if "比赛开始!" not in messages[0]: - await test_simulate_race_cmd.finish("完全模拟失败:未发送开赛消息") - return + messages = [m.get("message", "") for m in fake_bot.messages] + if not messages: + await test_simulate_race_cmd.finish("完全模拟失败:未捕获到任何消息") + return - if not any("【第" in msg and "回合】" in msg for msg in messages): - await test_simulate_race_cmd.finish("完全模拟失败:未发送回合进度消息") - return + if "比赛开始!" not in messages[0]: + await test_simulate_race_cmd.finish("完全模拟失败:未发送开赛消息") + return - result_msg = messages[-1] - if "比赛结束!冠军:" not in result_msg: - await test_simulate_race_cmd.finish("完全模拟失败:未发送结束结算消息") - return + if not any("【第" in msg and "回合】" in msg for msg in messages): + await test_simulate_race_cmd.finish("完全模拟失败:未发送回合进度消息") + return - if not fake_room_store.saved_results: - await test_simulate_race_cmd.finish("完全模拟失败:未写入赛史结果(内存)") - return + result_msg = messages[-1] + if "比赛结束!冠军:" not in result_msg: + await test_simulate_race_cmd.finish("完全模拟失败:未发送结束结算消息") + return - saved = fake_room_store.saved_results[-1] - if saved.champion_name not in room.horses: - await test_simulate_race_cmd.finish("完全模拟失败:赛史冠军不在参赛马匹中") - return + if not fake_room_store.saved_results: + await test_simulate_race_cmd.finish("完全模拟失败:未写入赛史结果(内存)") + return - champion_owner_id = room.horses[saved.champion_name].owner_id - reward_champion_calls = [c for c in fake_points_service.calls if c[0] == "reward_champion"] - if not reward_champion_calls or reward_champion_calls[0][1]["user_id"] != champion_owner_id: - await test_simulate_race_cmd.finish("完全模拟失败:未正确发放冠军奖励(内存记录)") - return + saved = fake_room_store.saved_results[-1] + if saved.champion_name not in room.horses: + await test_simulate_race_cmd.finish("完全模拟失败:赛史冠军不在参赛马匹中") + return - participant_calls = [c for c in fake_points_service.calls if c[0] == "reward_participant"] - if len(participant_calls) != max(0, len(room.horses) - 1): - await test_simulate_race_cmd.finish("完全模拟失败:参赛奖励次数不匹配(内存记录)") - return + champion_owner_id = room.horses[saved.champion_name].owner_id + reward_champion_calls = [c for c in fake_points_service.calls if c[0] == "reward_champion"] + if not reward_champion_calls or reward_champion_calls[0][1]["user_id"] != champion_owner_id: + await test_simulate_race_cmd.finish("完全模拟失败:未正确发放冠军奖励(内存记录)") + return - await test_simulate_race_cmd.finish( - "\n".join( - [ - "完全模拟赛马完成(无真实积分/数据库副作用)", - f"参赛马匹:{', '.join(horse_names)}", - f"冠军:{saved.champion_name}(马主:{saved.champion_owner})", - f"总回合:{saved.duration_ticks}", - f"消息条数:{len(messages)}(开赛/进度/结算均已覆盖)", - f"结算调用:{len(fake_points_service.calls)}(冠军/参赛/下注派奖)", - ] - ) + participant_calls = [c for c in fake_points_service.calls if c[0] == "reward_participant"] + if len(participant_calls) != max(0, len(room.horses) - 1): + await test_simulate_race_cmd.finish("完全模拟失败:参赛奖励次数不匹配(内存记录)") + return + + await test_simulate_race_cmd.finish( + "\n".join( + [ + "完全模拟赛马完成(无真实积分/数据库副作用)", + f"参赛马匹:{', '.join(horse_names)}", + f"冠军:{saved.champion_name}(马主:{saved.champion_owner})", + f"总回合:{saved.duration_ticks}", + f"消息条数:{len(messages)}(开赛/进度/结算均已覆盖)", + f"结算调用:{len(fake_points_service.calls)}(冠军/参赛/下注派奖)", + ] ) - finally: - commands_mod.room_store = original_room_store - commands_mod.points_service = original_points_service - commands_mod.message_service = original_message_service - commands_mod.config.RACE_TICK_INTERVAL = original_tick_interval + ) + except asyncio.TimeoutError: + ticks = room.tick_count if room else 0 + await test_simulate_race_cmd.finish(f"完全模拟失败:超时未完成(当前回合:{ticks})") + except Exception as e: + tail = "\n".join(traceback.format_exc().splitlines()[-8:]) + await test_simulate_race_cmd.finish(f"完全模拟异常:{type(e).__name__}: {e}\n{tail}") + finally: + if start_task and not start_task.done(): + start_task.cancel() + commands_mod.room_store = original_room_store + commands_mod.points_service = original_points_service + commands_mod.message_service = original_message_service + commands_mod.config.RACE_TICK_INTERVAL = original_tick_interval