Files
wehub-resource-sync e30e75b5d4
Code Quality / Oxlint + Oxfmt (push) Waiting to run
Code Quality / Template Sync (push) Waiting to run
Code Quality / Build Changed Packages (push) Waiting to run
Code Quality / Test Changed Packages (push) Waiting to run
Deploy Expo Example / Deploy Production (push) Waiting to run
Deploy Ink Example / Deploy Production (push) Waiting to run
Python Tests / pytest (assistant-stream, 3.10) (push) Waiting to run
Python Tests / pytest (assistant-stream, 3.12) (push) Waiting to run
Python Tests / pytest (assistant-ui-sync-server-api, 3.10) (push) Waiting to run
Python Tests / pytest (assistant-ui-sync-server-api, 3.12) (push) Waiting to run
Deploy Shadcn Registry / Deploy Production (push) Waiting to run
Template Metrics / LOC + Bundle Size (push) Waiting to run
Changesets / Create Version PR (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:40:13 +08:00

179 lines
5.4 KiB
Python

import asyncio
import pytest
from assistant_stream import RunController, create_run
@pytest.mark.anyio
async def test_controller_exposes_cancel_signal():
observed: dict[str, object] = {}
async def run_callback(controller: RunController):
observed["cancel_signal"] = controller.cancelled_event
observed["initial_is_cancelled"] = controller.is_cancelled
controller.append_text("hello")
chunks = [chunk async for chunk in create_run(run_callback)]
assert len(chunks) == 1
assert chunks[0].type == "text-delta"
cancel_signal = observed["cancel_signal"]
assert callable(getattr(cancel_signal, "wait", None))
assert callable(getattr(cancel_signal, "is_set", None))
assert getattr(cancel_signal, "set", None) is None
assert observed["initial_is_cancelled"] is False
@pytest.mark.anyio
async def test_early_stream_close_sets_cancel_signal():
callback_done = asyncio.Event()
observed: dict[str, object] = {}
async def run_callback(controller: RunController):
controller.append_text("start")
for _ in range(100):
if controller.is_cancelled:
break
await asyncio.sleep(0.01)
observed["is_cancelled_after_close"] = controller.is_cancelled
callback_done.set()
stream = create_run(run_callback)
first_chunk = await anext(stream)
assert first_chunk.type == "text-delta"
await stream.aclose()
await asyncio.wait_for(callback_done.wait(), timeout=2)
assert observed["is_cancelled_after_close"] is True
@pytest.mark.anyio
async def test_early_stream_close_stops_background_task():
callback_done = asyncio.Event()
async def run_callback(controller: RunController):
controller.append_text("start")
try:
for _ in range(100):
if controller.is_cancelled:
break
await asyncio.sleep(0.01)
finally:
callback_done.set()
stream = create_run(run_callback)
first_chunk = await anext(stream)
assert first_chunk.type == "text-delta"
await stream.aclose()
await asyncio.wait_for(callback_done.wait(), timeout=0.2)
@pytest.mark.anyio
async def test_normal_completion_does_not_set_cancel_signal():
observed: dict[str, object] = {}
async def run_callback(controller: RunController):
controller.append_text("done")
observed["is_cancelled_on_complete"] = controller.is_cancelled
chunks = [chunk async for chunk in create_run(run_callback)]
assert len(chunks) == 1
assert chunks[0].type == "text-delta"
assert observed["is_cancelled_on_complete"] is False
@pytest.mark.anyio
async def test_normal_completion_surfaces_callback_exception():
chunk_types: list[str] = []
async def run_callback(controller: RunController):
controller.append_text("start")
raise RuntimeError("boom")
with pytest.raises(RuntimeError, match="boom"):
async for chunk in create_run(run_callback):
chunk_types.append(chunk.type)
assert chunk_types == ["text-delta", "error"]
@pytest.mark.anyio
async def test_early_stream_close_forces_background_task_cancellation():
callback_cancelled = asyncio.Event()
callback_finished = asyncio.Event()
async def run_callback(controller: RunController):
controller.append_text("start")
try:
# Intentionally ignore `is_cancelled` to exercise forced cancellation.
while True:
await asyncio.sleep(1)
except asyncio.CancelledError:
callback_cancelled.set()
raise
finally:
callback_finished.set()
stream = create_run(run_callback)
first_chunk = await anext(stream)
assert first_chunk.type == "text-delta"
await stream.aclose()
await asyncio.wait_for(callback_cancelled.wait(), timeout=2)
await asyncio.wait_for(callback_finished.wait(), timeout=2)
@pytest.mark.anyio
async def test_early_stream_close_does_not_raise_callback_exception():
async def run_callback(controller: RunController):
controller.append_text("start")
await asyncio.sleep(0.01)
raise RuntimeError("boom")
stream = create_run(run_callback)
first_chunk = await anext(stream)
assert first_chunk.type == "text-delta"
await stream.aclose()
@pytest.mark.anyio
async def test_early_stream_close_does_not_swallow_close_task_cancellation():
callback_finished = asyncio.Event()
loop = asyncio.get_running_loop()
async def run_callback(controller: RunController):
controller.append_text("start")
deadline = loop.time() + 0.5
try:
while loop.time() < deadline:
try:
await asyncio.sleep(0.01)
except asyncio.CancelledError:
# Simulate non-cooperative callback behavior: ignore cancellation.
continue
finally:
callback_finished.set()
stream = create_run(run_callback)
first_chunk = await anext(stream)
assert first_chunk.type == "text-delta"
close_task = asyncio.create_task(stream.aclose())
await asyncio.sleep(0)
close_task.cancel()
try:
done, _ = await asyncio.wait({close_task}, timeout=0.2)
assert close_task in done
assert close_task.cancelled()
finally:
await asyncio.wait_for(callback_finished.wait(), timeout=2)
if not close_task.done():
await asyncio.wait({close_task}, timeout=1)