-
Notifications
You must be signed in to change notification settings - Fork 655
fix(anam): make AnamAvatarPublisher.start() idempotent #550
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,5 @@ | ||
| import asyncio | ||
|
|
||
| import pytest | ||
| from getstream.video.rtc import audio_track | ||
| from vision_agents.core.events import EventManager | ||
|
|
@@ -77,3 +79,78 @@ async def test_attach_agent_subscribes_to_events(self): | |
| assert agent.events.has_subscribers(RealtimeAudioOutputEvent) | ||
| assert agent.events.has_subscribers(RealtimeAudioOutputDoneEvent) | ||
| assert agent.events.has_subscribers(TurnStartedEvent) | ||
|
|
||
| async def test_start_is_idempotent(self): | ||
| """Repeated `start()` runs `_connect()` once and short-circuits after. | ||
|
|
||
| Callers warm up the publisher before `agent.join()` to keep Anam's | ||
| ~3s session creation off the join critical path. The framework | ||
| then calls `start()` again from `_apply("start")` — that second | ||
| call must be a no-op. | ||
| """ | ||
| pub = _make_publisher() | ||
|
|
||
| call_count = 0 | ||
|
|
||
| async def fake_connect(): | ||
| nonlocal call_count | ||
| call_count += 1 | ||
|
|
||
| pub._connect = fake_connect # type: ignore[method-assign] | ||
|
|
||
| await pub.start() | ||
| await pub.start() | ||
| await pub.start() | ||
|
|
||
| assert call_count == 1 | ||
| assert pub._started is True | ||
|
Comment on lines
+95
to
+106
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Replace call-path spying with behavior/state assertions. These tests validate As per coding guidelines: "Never mock in tests" and "ALWAYS test behavior, not calling a path. Assert on outputs and state, not method calls." Also applies to: 119-133, 142-157 |
||
|
|
||
| async def test_start_serializes_concurrent_callers(self): | ||
| """Two tasks calling `start()` at the same time produce one connect. | ||
|
|
||
| Without the lock, both would see `_started is False` and race into | ||
| `_connect()`, double-opening the Anam session. | ||
| """ | ||
| pub = _make_publisher() | ||
|
|
||
| call_count = 0 | ||
| proceed = asyncio.Event() | ||
|
|
||
| async def slow_connect(): | ||
| nonlocal call_count | ||
| call_count += 1 | ||
| await proceed.wait() | ||
|
|
||
| pub._connect = slow_connect # type: ignore[method-assign] | ||
|
|
||
| task_a = asyncio.create_task(pub.start()) | ||
| task_b = asyncio.create_task(pub.start()) | ||
| await asyncio.sleep(0) # let both tasks acquire/queue on the lock | ||
| proceed.set() | ||
| await asyncio.gather(task_a, task_b) | ||
|
|
||
| assert call_count == 1 | ||
|
|
||
| async def test_start_failure_allows_retry(self): | ||
| """A failing first `start()` leaves `_started=False` so the next | ||
| attempt can retry instead of silently no-op'ing on a half-connected | ||
| publisher.""" | ||
| pub = _make_publisher() | ||
|
|
||
| attempts = 0 | ||
|
|
||
| async def flaky_connect(): | ||
| nonlocal attempts | ||
| attempts += 1 | ||
| if attempts == 1: | ||
| raise RuntimeError("anam unreachable") | ||
|
|
||
| pub._connect = flaky_connect # type: ignore[method-assign] | ||
|
|
||
| with pytest.raises(RuntimeError, match="anam unreachable"): | ||
| await pub.start() | ||
| assert pub._started is False | ||
|
|
||
| await pub.start() | ||
| assert pub._started is True | ||
| assert attempts == 2 | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove
# type: ignore[method-assign]from tests.Please avoid
# type: ignorehere; use a typed test double approach (fixture/class) so the assignment is type-safe without suppressions.As per coding guidelines: "Avoid
# type: ignorecomments."Also applies to: 124-124, 148-148