mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-01-08 23:58:34 +00:00
Add thread-safe, async-compatible event bus with read–write locking and handler dependency ordering. Remove blinker dependency and implement direct dispatch. Improve type safety, error handling, and deterministic event synchronization. Refactor tests to auto-wait for async handlers, ensure clean teardown, and add comprehensive concurrency coverage. Replace thread-local state in AgentEvaluator with instance-based locking for correct cross-thread access. Enhance tracing reliability and event finalization.
207 lines
6.0 KiB
Python
207 lines
6.0 KiB
Python
"""Tests for async event handling in CrewAI event bus.
|
|
|
|
This module tests async handler registration, execution, and the aemit method.
|
|
"""
|
|
|
|
import asyncio
|
|
|
|
import pytest
|
|
|
|
from crewai.events.base_events import BaseEvent
|
|
from crewai.events.event_bus import crewai_event_bus
|
|
|
|
|
|
class AsyncTestEvent(BaseEvent):
|
|
pass
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_async_handler_execution():
|
|
received_events = []
|
|
|
|
with crewai_event_bus.scoped_handlers():
|
|
|
|
@crewai_event_bus.on(AsyncTestEvent)
|
|
async def async_handler(source: object, event: BaseEvent) -> None:
|
|
await asyncio.sleep(0.01)
|
|
received_events.append(event)
|
|
|
|
event = AsyncTestEvent(type="async_test")
|
|
crewai_event_bus.emit("test_source", event)
|
|
|
|
await asyncio.sleep(0.1)
|
|
|
|
assert len(received_events) == 1
|
|
assert received_events[0] == event
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_aemit_with_async_handlers():
|
|
received_events = []
|
|
|
|
with crewai_event_bus.scoped_handlers():
|
|
|
|
@crewai_event_bus.on(AsyncTestEvent)
|
|
async def async_handler(source: object, event: BaseEvent) -> None:
|
|
await asyncio.sleep(0.01)
|
|
received_events.append(event)
|
|
|
|
event = AsyncTestEvent(type="async_test")
|
|
await crewai_event_bus.aemit("test_source", event)
|
|
|
|
assert len(received_events) == 1
|
|
assert received_events[0] == event
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_multiple_async_handlers():
|
|
received_events_1 = []
|
|
received_events_2 = []
|
|
|
|
with crewai_event_bus.scoped_handlers():
|
|
|
|
@crewai_event_bus.on(AsyncTestEvent)
|
|
async def handler_1(source: object, event: BaseEvent) -> None:
|
|
await asyncio.sleep(0.01)
|
|
received_events_1.append(event)
|
|
|
|
@crewai_event_bus.on(AsyncTestEvent)
|
|
async def handler_2(source: object, event: BaseEvent) -> None:
|
|
await asyncio.sleep(0.02)
|
|
received_events_2.append(event)
|
|
|
|
event = AsyncTestEvent(type="async_test")
|
|
await crewai_event_bus.aemit("test_source", event)
|
|
|
|
assert len(received_events_1) == 1
|
|
assert len(received_events_2) == 1
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_mixed_sync_and_async_handlers():
|
|
sync_events = []
|
|
async_events = []
|
|
|
|
with crewai_event_bus.scoped_handlers():
|
|
|
|
@crewai_event_bus.on(AsyncTestEvent)
|
|
def sync_handler(source: object, event: BaseEvent) -> None:
|
|
sync_events.append(event)
|
|
|
|
@crewai_event_bus.on(AsyncTestEvent)
|
|
async def async_handler(source: object, event: BaseEvent) -> None:
|
|
await asyncio.sleep(0.01)
|
|
async_events.append(event)
|
|
|
|
event = AsyncTestEvent(type="mixed_test")
|
|
crewai_event_bus.emit("test_source", event)
|
|
|
|
await asyncio.sleep(0.1)
|
|
|
|
assert len(sync_events) == 1
|
|
assert len(async_events) == 1
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_async_handler_error_handling():
|
|
successful_handler_called = []
|
|
|
|
with crewai_event_bus.scoped_handlers():
|
|
|
|
@crewai_event_bus.on(AsyncTestEvent)
|
|
async def failing_handler(source: object, event: BaseEvent) -> None:
|
|
raise ValueError("Async handler error")
|
|
|
|
@crewai_event_bus.on(AsyncTestEvent)
|
|
async def successful_handler(source: object, event: BaseEvent) -> None:
|
|
await asyncio.sleep(0.01)
|
|
successful_handler_called.append(True)
|
|
|
|
event = AsyncTestEvent(type="error_test")
|
|
await crewai_event_bus.aemit("test_source", event)
|
|
|
|
assert len(successful_handler_called) == 1
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_aemit_with_no_handlers():
|
|
with crewai_event_bus.scoped_handlers():
|
|
event = AsyncTestEvent(type="no_handlers")
|
|
await crewai_event_bus.aemit("test_source", event)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_async_handler_registration_via_register_handler():
|
|
received_events = []
|
|
|
|
with crewai_event_bus.scoped_handlers():
|
|
|
|
async def custom_async_handler(source: object, event: BaseEvent) -> None:
|
|
await asyncio.sleep(0.01)
|
|
received_events.append(event)
|
|
|
|
crewai_event_bus.register_handler(AsyncTestEvent, custom_async_handler)
|
|
|
|
event = AsyncTestEvent(type="register_test")
|
|
await crewai_event_bus.aemit("test_source", event)
|
|
|
|
assert len(received_events) == 1
|
|
assert received_events[0] == event
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_emit_async_handlers_fire_and_forget():
|
|
received_events = []
|
|
|
|
with crewai_event_bus.scoped_handlers():
|
|
|
|
@crewai_event_bus.on(AsyncTestEvent)
|
|
async def slow_async_handler(source: object, event: BaseEvent) -> None:
|
|
await asyncio.sleep(0.05)
|
|
received_events.append(event)
|
|
|
|
event = AsyncTestEvent(type="fire_forget_test")
|
|
crewai_event_bus.emit("test_source", event)
|
|
|
|
assert len(received_events) == 0
|
|
|
|
await asyncio.sleep(0.1)
|
|
|
|
assert len(received_events) == 1
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_scoped_handlers_with_async():
|
|
received_before = []
|
|
received_during = []
|
|
received_after = []
|
|
|
|
with crewai_event_bus.scoped_handlers():
|
|
|
|
@crewai_event_bus.on(AsyncTestEvent)
|
|
async def before_handler(source: object, event: BaseEvent) -> None:
|
|
received_before.append(event)
|
|
|
|
with crewai_event_bus.scoped_handlers():
|
|
|
|
@crewai_event_bus.on(AsyncTestEvent)
|
|
async def scoped_handler(source: object, event: BaseEvent) -> None:
|
|
received_during.append(event)
|
|
|
|
event1 = AsyncTestEvent(type="during_scope")
|
|
await crewai_event_bus.aemit("test_source", event1)
|
|
|
|
assert len(received_before) == 0
|
|
assert len(received_during) == 1
|
|
|
|
@crewai_event_bus.on(AsyncTestEvent)
|
|
async def after_handler(source: object, event: BaseEvent) -> None:
|
|
received_after.append(event)
|
|
|
|
event2 = AsyncTestEvent(type="after_scope")
|
|
await crewai_event_bus.aemit("test_source", event2)
|
|
|
|
assert len(received_before) == 1
|
|
assert len(received_during) == 1
|
|
assert len(received_after) == 1
|