mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-06-13 14:23:58 +00:00
test: harden timing-fragile test and add cross-tool ContextVar isolation test
Replace asyncio.sleep(0.05) with an asyncio.Event + patched Lock.acquire to guarantee the waiting task has reached the lock before asserting. Add a test confirming LongTaskTool and CompleteGoalTool ContextVars are isolated, and document the design intent in _GoalToolsMixin.
This commit is contained in:
parent
0df60416ba
commit
84428136e6
@ -46,6 +46,9 @@ class _GoalToolsMixin(ContextAware):
|
||||
def __init__(self, sessions: SessionManager, bus: Any | None = None) -> None:
|
||||
self._sessions = sessions
|
||||
self._bus = bus
|
||||
# Each subclass gets its own ContextVar so concurrent tasks across
|
||||
# different tool types (LongTaskTool vs CompleteGoalTool) do not
|
||||
# interfere with each other.
|
||||
self._request_ctx: ContextVar[RequestContext | None] = ContextVar(
|
||||
f"{self.__class__.__name__}_request_ctx",
|
||||
default=None,
|
||||
|
||||
@ -566,10 +566,21 @@ async def test_waiting_dispatch_does_not_replace_active_pending_queue(tmp_path):
|
||||
active_pending = asyncio.Queue(maxsize=1)
|
||||
loop._pending_queues[session_key] = active_pending
|
||||
|
||||
waiting = asyncio.create_task(
|
||||
loop._dispatch(InboundMessage(channel="cli", sender_id="u", chat_id="c", content="queued"))
|
||||
)
|
||||
await asyncio.sleep(0.05)
|
||||
waiting_at_lock = asyncio.Event()
|
||||
original_acquire = asyncio.Lock.acquire
|
||||
|
||||
async def _patched_acquire(self, *args, **kwargs):
|
||||
if self is lock:
|
||||
waiting_at_lock.set()
|
||||
return await original_acquire(self, *args, **kwargs)
|
||||
|
||||
with patch.object(asyncio.Lock, "acquire", _patched_acquire):
|
||||
waiting = asyncio.create_task(
|
||||
loop._dispatch(
|
||||
InboundMessage(channel="cli", sender_id="u", chat_id="c", content="queued")
|
||||
)
|
||||
)
|
||||
await asyncio.wait_for(waiting_at_lock.wait(), timeout=2.0)
|
||||
|
||||
assert loop._pending_queues[session_key] is active_pending
|
||||
|
||||
|
||||
@ -100,6 +100,22 @@ async def test_goal_tools_keep_request_context_per_task(tmp_path):
|
||||
assert sm.get_or_create("websocket:b").metadata[GOAL_STATE_KEY]["recap"] == "Done B"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_goal_tools_context_isolated_across_tool_types(tmp_path):
|
||||
"""LongTaskTool and CompleteGoalTool must not share routing context."""
|
||||
sm = SessionManager(tmp_path)
|
||||
lt = LongTaskTool(sessions=sm)
|
||||
cg = CompleteGoalTool(sessions=sm)
|
||||
ctx = RequestContext(channel="websocket", chat_id="a", session_key="websocket:a")
|
||||
|
||||
lt.set_context(ctx)
|
||||
assert cg._request_ctx.get() is None
|
||||
|
||||
cg.set_context(ctx)
|
||||
assert lt._request_ctx.get() is ctx
|
||||
assert cg._request_ctx.get() is ctx
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_long_task_publishes_goal_state_ws_after_save(tmp_path):
|
||||
bus = MagicMock()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user