Skip to content

Commit 013d4dd

Browse files
sxlijinclaude
andcommitted
test(streaming): keyless replay + e2e stream tests (python + typescript)
Python (conftest/replay/e2e/class-e2e) and TypeScript (replay/e2e) streaming tests driving the BAML-implemented keyless replay server, plus the StreamE2E fixtures and the env-driven replay server fixture. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent d1393fb commit 013d4dd

10 files changed

Lines changed: 514 additions & 520 deletions

File tree

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
"""Keyless replay harness shared by the streaming tests.
2+
3+
`@replay_server(recording_path=...)` is a decorator that runs the wrapped test
4+
(sync or async) against an in-process BAML server replaying a checked-in SSE
5+
recording, with the env-driven `StreamStub` client pointed at it — so the test
6+
exercises the full streaming path with **no `OPENAI_API_KEY`**. Inside a
7+
decorated test the server address is available as
8+
`os.environ["BAML_REPLAY_BASE_URL"]`.
9+
"""
10+
import contextlib
11+
import functools
12+
import inspect
13+
import os
14+
import threading
15+
import time
16+
import urllib.request
17+
from pathlib import Path
18+
19+
20+
def recording_path(name: str) -> Path:
21+
"""Absolute path to a checked-in SSE recording under sdk_tests/fixtures."""
22+
for parent in Path(__file__).resolve().parents:
23+
if parent.name == "sdk_tests":
24+
rec = parent / "fixtures" / "llm_functions" / "recordings" / f"{name}.snap.sse"
25+
assert rec.exists(), f"missing recording {rec}"
26+
return rec
27+
raise RuntimeError("could not locate the sdk_tests/ ancestor directory")
28+
29+
30+
@contextlib.contextmanager
31+
def _running_server(recording: str):
32+
"""Serve `recording` on a background thread, set the replay client env, and
33+
tear it down on exit. Yields the bound `host:port`."""
34+
from baml_sdk import replay
35+
36+
rec = recording_path(recording)
37+
addr_file = Path(os.environ.get("TMPDIR", "/tmp")) / (
38+
f"baml_replay_{os.getpid()}_{threading.get_ident()}_{recording}"
39+
)
40+
with contextlib.suppress(FileNotFoundError):
41+
addr_file.unlink()
42+
43+
result: list = []
44+
error: list = []
45+
46+
def _serve():
47+
try:
48+
result.append(replay.replay_serve_until_shutdown(str(rec), str(addr_file)))
49+
except Exception as e: # surface bridge/engine failures to the poller
50+
error.append(e)
51+
52+
thread = threading.Thread(target=_serve, daemon=True)
53+
thread.start()
54+
55+
addr = None
56+
deadline = time.time() + 10.0
57+
while time.time() < deadline:
58+
if error:
59+
raise RuntimeError(f"replay server thread failed: {error[0]!r}")
60+
if addr_file.exists() and (text := addr_file.read_text().strip()):
61+
addr = text
62+
break
63+
time.sleep(0.02)
64+
if addr is None:
65+
raise TimeoutError("replay server did not bind within 10s")
66+
67+
os.environ["BAML_REPLAY_BASE_URL"] = f"http://{addr}"
68+
os.environ["BAML_REPLAY_API_KEY"] = "replay-test-key"
69+
try:
70+
yield addr
71+
finally:
72+
with contextlib.suppress(Exception):
73+
urllib.request.urlopen(
74+
f"http://{addr}/__replay__/shutdown", data=b"", timeout=5
75+
)
76+
thread.join(timeout=10)
77+
with contextlib.suppress(FileNotFoundError):
78+
addr_file.unlink()
79+
for key in ("BAML_REPLAY_BASE_URL", "BAML_REPLAY_API_KEY"):
80+
os.environ.pop(key, None)
81+
82+
83+
def replay_server(*, recording_path: str):
84+
"""Decorator: run the wrapped test against the keyless replay server for the
85+
`recording_path` recording (a name under `recordings/`, resolved by
86+
`recording_path()`). Keyword-only. Works on both sync and `async` tests."""
87+
88+
def decorator(fn):
89+
if inspect.iscoroutinefunction(fn):
90+
91+
@functools.wraps(fn)
92+
async def awrapper(*args, **kwargs):
93+
with _running_server(recording_path):
94+
return await fn(*args, **kwargs)
95+
96+
return awrapper
97+
98+
@functools.wraps(fn)
99+
def wrapper(*args, **kwargs):
100+
with _running_server(recording_path):
101+
return fn(*args, **kwargs)
102+
103+
return wrapper
104+
105+
return decorator

baml_language/sdk_tests/crates/python_pydantic2/llm_functions/customizable/test_main.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,29 @@ def test_classify_sentiment_factory_bindings():
124124
assert callable(ipsum.ClassifySentiment_async)
125125

126126

127+
# ---------------------------------------------------------------------------
128+
# Replay-harness surface (bridge-generics/streaming/02). Codegen-shape only;
129+
# the keyless behavioral tests live in `test_streaming_e2e.py` (string- and
130+
# class-typed `T`). The replay client is the unified env-driven `StreamStub`
131+
# (no separate Replay* functions).
132+
# ---------------------------------------------------------------------------
133+
134+
135+
def test_replay_server_namespace_bindings():
136+
# The BAML-implemented replay server lives in the `replay` namespace
137+
# (ns_replay/). Both invocation entry points get sync + async siblings.
138+
from baml_sdk import replay
139+
140+
for name in (
141+
"replay_serve_until_shutdown",
142+
"replay_serve_until_shutdown_async",
143+
"replay_serve_detached",
144+
"replay_serve_detached_async",
145+
):
146+
binding = getattr(replay, name)
147+
assert callable(binding), f"missing replay-server binding {name}"
148+
149+
127150
# ---------------------------------------------------------------------------
128151
# Shorthand-client api_key wiring
129152
#

baml_language/sdk_tests/crates/python_pydantic2/llm_functions/customizable/test_streaming_class_e2e.py

Lines changed: 0 additions & 151 deletions
This file was deleted.

0 commit comments

Comments
 (0)