Skip to content

Commit 45eb41f

Browse files
authored
More tests for cancelling streamed run (#590)
1 parent e11b822 commit 45eb41f

File tree

2 files changed

+100
-3
lines changed

2 files changed

+100
-3
lines changed

tests/fake_model.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,10 @@ async def stream_response(
127127
)
128128

129129

130-
def get_response_obj(output: list[TResponseOutputItem], response_id: str | None = None) -> Response:
130+
def get_response_obj(
131+
output: list[TResponseOutputItem],
132+
response_id: str | None = None,
133+
) -> Response:
131134
return Response(
132135
id=response_id or "123",
133136
created_at=123,

tests/test_cancel_streaming.py

+96-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
1+
import json
2+
13
import pytest
24

35
from agents import Agent, Runner
46

57
from .fake_model import FakeModel
8+
from .test_responses import get_function_tool, get_function_tool_call, get_text_message
69

710

811
@pytest.mark.asyncio
9-
async def test_joker_streamed_jokes_with_cancel():
12+
async def test_simple_streaming_with_cancel():
1013
model = FakeModel()
1114
agent = Agent(name="Joker", model=model)
1215

@@ -16,7 +19,98 @@ async def test_joker_streamed_jokes_with_cancel():
1619

1720
async for _event in result.stream_events():
1821
num_events += 1
19-
if num_events == 1:
22+
if num_events == stop_after:
2023
result.cancel()
2124

2225
assert num_events == 1, f"Expected {stop_after} visible events, but got {num_events}"
26+
27+
28+
@pytest.mark.asyncio
29+
async def test_multiple_events_streaming_with_cancel():
30+
model = FakeModel()
31+
agent = Agent(
32+
name="Joker",
33+
model=model,
34+
tools=[get_function_tool("foo", "tool_result")],
35+
)
36+
37+
model.add_multiple_turn_outputs(
38+
[
39+
# First turn: a message and tool call
40+
[
41+
get_text_message("a_message"),
42+
get_function_tool_call("foo", json.dumps({"a": "b"})),
43+
],
44+
# Second turn: text message
45+
[get_text_message("done")],
46+
]
47+
)
48+
49+
result = Runner.run_streamed(agent, input="Please tell me 5 jokes.")
50+
num_events = 0
51+
stop_after = 2
52+
53+
async for _ in result.stream_events():
54+
num_events += 1
55+
if num_events == stop_after:
56+
result.cancel()
57+
58+
assert num_events == stop_after, f"Expected {stop_after} visible events, but got {num_events}"
59+
60+
61+
@pytest.mark.asyncio
62+
async def test_cancel_prevents_further_events():
63+
model = FakeModel()
64+
agent = Agent(name="Joker", model=model)
65+
result = Runner.run_streamed(agent, input="Please tell me 5 jokes.")
66+
events = []
67+
async for event in result.stream_events():
68+
events.append(event)
69+
result.cancel()
70+
break # Cancel after first event
71+
# Try to get more events after cancel
72+
more_events = [e async for e in result.stream_events()]
73+
assert len(events) == 1
74+
assert more_events == [], "No events should be yielded after cancel()"
75+
76+
77+
@pytest.mark.asyncio
78+
async def test_cancel_is_idempotent():
79+
model = FakeModel()
80+
agent = Agent(name="Joker", model=model)
81+
result = Runner.run_streamed(agent, input="Please tell me 5 jokes.")
82+
events = []
83+
async for event in result.stream_events():
84+
events.append(event)
85+
result.cancel()
86+
result.cancel() # Call cancel again
87+
break
88+
# Should not raise or misbehave
89+
assert len(events) == 1
90+
91+
92+
@pytest.mark.asyncio
93+
async def test_cancel_before_streaming():
94+
model = FakeModel()
95+
agent = Agent(name="Joker", model=model)
96+
result = Runner.run_streamed(agent, input="Please tell me 5 jokes.")
97+
result.cancel() # Cancel before streaming
98+
events = [e async for e in result.stream_events()]
99+
assert events == [], "No events should be yielded if cancel() is called before streaming."
100+
101+
102+
@pytest.mark.asyncio
103+
async def test_cancel_cleans_up_resources():
104+
model = FakeModel()
105+
agent = Agent(name="Joker", model=model)
106+
result = Runner.run_streamed(agent, input="Please tell me 5 jokes.")
107+
# Start streaming, then cancel
108+
async for _ in result.stream_events():
109+
result.cancel()
110+
break
111+
# After cancel, queues should be empty and is_complete True
112+
assert result.is_complete, "Result should be marked complete after cancel."
113+
assert result._event_queue.empty(), "Event queue should be empty after cancel."
114+
assert result._input_guardrail_queue.empty(), (
115+
"Input guardrail queue should be empty after cancel."
116+
)

0 commit comments

Comments
 (0)