Files
wehub-resource-sync a7d6d88f6f
CI / changes (push) Has been cancelled
CI / cd libs/checkpoint (push) Has been cancelled
CI / cd libs/checkpoint-conformance (push) Has been cancelled
CI / cd libs/checkpoint-postgres (push) Has been cancelled
CI / cd libs/checkpoint-sqlite (push) Has been cancelled
CI / cd libs/cli (push) Has been cancelled
CI / cd libs/prebuilt (push) Has been cancelled
CI / cd libs/sdk-py (push) Has been cancelled
CI / cd libs/langgraph (push) Has been cancelled
CI / Check SDK methods matching (push) Has been cancelled
CI / Check CLI schema hasn't changed #3.13 (push) Has been cancelled
CI / CLI integration test (push) Has been cancelled
CI / sdk-py integration test (push) Has been cancelled
CI / CI Success (push) Has been cancelled
baseline / benchmark (push) Has been cancelled
Deploy Redirects to GitHub Pages / deploy (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:37:18 +08:00

360 lines
12 KiB
Python

"""Tests for arrival-ordered interleave and push stamps."""
from __future__ import annotations
import operator
from typing import Annotated, Any
import pytest
from typing_extensions import TypedDict
from langgraph.constants import END, START
from langgraph.graph import StateGraph
from langgraph.stream import StreamChannel, StreamTransformer
from langgraph.stream._mux import StreamMux
from langgraph.stream._types import ProtocolEvent
from langgraph.stream.run_stream import GraphRunStream
from langgraph.stream.transformers import ValuesTransformer
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
class _TwoChannelTransformer(StreamTransformer):
"""Transformer that exposes two named channels for testing interleave."""
_native = True
def __init__(self, scope: tuple[str, ...] = ()) -> None:
super().__init__(scope)
self._alpha: StreamChannel[str] = StreamChannel("alpha")
self._beta: StreamChannel[str] = StreamChannel("beta")
def init(self) -> dict[str, Any]:
return {"alpha": self._alpha, "beta": self._beta}
def process(self, event: ProtocolEvent) -> bool:
return True
class SimpleState(TypedDict):
value: str
items: Annotated[list[str], operator.add]
def _build_simple_graph():
def node_a(state: SimpleState) -> dict:
return {"value": state["value"] + "A", "items": ["a"]}
def node_b(state: SimpleState) -> dict:
return {"value": state["value"] + "B", "items": ["b"]}
builder = StateGraph(SimpleState)
builder.add_node("node_a", node_a)
builder.add_node("node_b", node_b)
builder.add_edge(START, "node_a")
builder.add_edge("node_a", "node_b")
builder.add_edge("node_b", END)
return builder.compile()
# ---------------------------------------------------------------------------
# Unit tests: push stamps on StreamChannel
# ---------------------------------------------------------------------------
class TestPushStamps:
def test_stamps_are_monotonic_across_channels(self) -> None:
mux = StreamMux(
factories=[ValuesTransformer, _TwoChannelTransformer],
is_async=False,
)
alpha = mux.extensions["alpha"]
beta = mux.extensions["beta"]
alpha._subscribed = True
beta._subscribed = True
alpha.push("a1")
beta.push("b1")
alpha.push("a2")
beta.push("b2")
all_stamped = list(alpha._items) + list(beta._items)
stamps = [s for s, _ in all_stamped]
assert len(set(stamps)) == 4
items_by_arrival = [item for _, item in sorted(all_stamped)]
assert items_by_arrival == ["a1", "b1", "a2", "b2"]
def test_regular_iter_strips_stamps(self) -> None:
mux = StreamMux(
factories=[ValuesTransformer, _TwoChannelTransformer],
is_async=False,
)
alpha = mux.extensions["alpha"]
it = iter(alpha)
alpha.push("a1")
alpha.push("a2")
alpha.close()
items = list(it)
assert items == ["a1", "a2"]
assert all(isinstance(item, str) for item in items)
def test_events_channel_gets_real_stamps(self) -> None:
mux = StreamMux(
factories=[ValuesTransformer, _TwoChannelTransformer],
is_async=False,
)
alpha = mux.extensions["alpha"]
alpha._subscribed = True
alpha.push("a1")
mux._events._subscribed = True
mux._events.push({"method": "test", "data": "x"})
alpha.push("a2")
all_stamps = [s for s, _ in alpha._items] + [s for s, _ in mux._events._items]
assert len(set(all_stamps)) == len(all_stamps), "all stamps should be unique"
assert all(s > 0 for s in all_stamps), "no stamp should be zero"
def test_channel_without_mux_gets_zero_stamp(self) -> None:
ch: StreamChannel[str] = StreamChannel()
ch._bind(is_async=False)
ch._subscribed = True
ch.push("x")
assert list(ch._items) == [(0, "x")]
# ---------------------------------------------------------------------------
# Unit tests: interleave arrival order
# ---------------------------------------------------------------------------
class TestInterleaveArrivalOrder:
def test_arrival_order_not_round_robin(self) -> None:
mux = StreamMux(
factories=[ValuesTransformer, _TwoChannelTransformer],
is_async=False,
)
alpha = mux.extensions["alpha"]
beta = mux.extensions["beta"]
run = GraphRunStream(None, mux, wire_pump=False)
# interleave() subscribes channels directly and reads _items
# for stamp-ordered iteration. We simulate the pump by wiring
# a custom callback that pushes items in a known order.
push_script = [
("alpha", "a1"),
("alpha", "a2"),
("beta", "b1"),
("alpha", "a3"),
("beta", "b2"),
]
push_iter = iter(push_script)
channels = {"alpha": alpha, "beta": beta}
def fake_pump() -> bool:
try:
name, item = next(push_iter)
channels[name].push(item)
return True
except StopIteration:
mux.close()
return False
mux.bind_pump(fake_pump)
result = list(run.interleave("alpha", "beta"))
names = [name for name, _ in result]
items = [item for _, item in result]
assert items == ["a1", "a2", "b1", "a3", "b2"]
assert names == ["alpha", "alpha", "beta", "alpha", "beta"]
def test_single_projection(self) -> None:
mux = StreamMux(
factories=[ValuesTransformer, _TwoChannelTransformer],
is_async=False,
)
alpha = mux.extensions["alpha"]
run = GraphRunStream(None, mux, wire_pump=False)
push_script = [("alpha", "a1"), ("alpha", "a2")]
push_iter = iter(push_script)
def fake_pump() -> bool:
try:
_, item = next(push_iter)
alpha.push(item)
return True
except StopIteration:
mux.close()
return False
mux.bind_pump(fake_pump)
result = list(run.interleave("alpha"))
assert result == [("alpha", "a1"), ("alpha", "a2")]
def test_empty_projection(self) -> None:
mux = StreamMux(
factories=[ValuesTransformer, _TwoChannelTransformer],
is_async=False,
)
alpha = mux.extensions["alpha"]
run = GraphRunStream(None, mux, wire_pump=False)
push_script = [("alpha", "a1"), ("alpha", "a2")]
push_iter = iter(push_script)
channels = {"alpha": alpha}
def fake_pump() -> bool:
try:
name, item = next(push_iter)
channels[name].push(item)
return True
except StopIteration:
mux.close()
return False
mux.bind_pump(fake_pump)
result = list(run.interleave("alpha", "beta"))
assert result == [("alpha", "a1"), ("alpha", "a2")]
def test_unknown_projection_raises(self) -> None:
mux = StreamMux(
factories=[ValuesTransformer, _TwoChannelTransformer],
is_async=False,
)
run = GraphRunStream(None, mux, wire_pump=False)
mux.close()
with pytest.raises((KeyError, AttributeError)):
list(run.interleave("alpha", "does_not_exist"))
def test_all_empty(self) -> None:
mux = StreamMux(
factories=[ValuesTransformer, _TwoChannelTransformer],
is_async=False,
)
run = GraphRunStream(None, mux, wire_pump=False)
def fake_pump() -> bool:
mux.close()
return False
mux.bind_pump(fake_pump)
result = list(run.interleave("alpha", "beta"))
assert result == []
def test_error_propagation(self) -> None:
mux = StreamMux(
factories=[ValuesTransformer, _TwoChannelTransformer],
is_async=False,
)
alpha = mux.extensions["alpha"]
beta = mux.extensions["beta"]
run = GraphRunStream(None, mux, wire_pump=False)
err = RuntimeError("boom")
push_script = [
("alpha", "a1"),
("beta", "b1"),
]
push_iter = iter(push_script)
channels = {"alpha": alpha, "beta": beta}
def fake_pump() -> bool:
try:
name, item = next(push_iter)
channels[name].push(item)
return True
except StopIteration:
alpha.fail(err)
beta.close()
return False
mux.bind_pump(fake_pump)
collected = []
with pytest.raises(RuntimeError, match="boom"):
for pair in run.interleave("alpha", "beta"):
collected.append(pair)
assert ("alpha", "a1") in collected
assert ("beta", "b1") in collected
# ---------------------------------------------------------------------------
# Integration test: interleave with stream_events(version="v3")
# ---------------------------------------------------------------------------
class TestInterleaveIntegration:
def test_interleave_values_and_messages(self) -> None:
run = _build_simple_graph().stream_events(
{"value": "x", "items": []}, version="v3"
)
tagged = list(run.interleave("values", "messages"))
names = [name for name, _ in tagged]
assert set(names).issubset({"values", "messages"})
assert names.count("values") >= 1
def test_interleave_rejects_already_subscribed(self) -> None:
mux = StreamMux(
factories=[ValuesTransformer, _TwoChannelTransformer],
is_async=False,
)
alpha = mux.extensions["alpha"]
run = GraphRunStream(None, mux, wire_pump=False)
# Subscribe alpha via iter first
_ = iter(alpha)
mux.close()
with pytest.raises(RuntimeError, match="already has a subscriber"):
list(run.interleave("alpha"))
def test_interleave_releases_projections_on_completion(self) -> None:
run = _build_simple_graph().stream_events(
{"value": "x", "items": []}, version="v3"
)
list(run.interleave("values", "messages"))
# Subscriptions should be released after the generator completes,
# so the channels can be re-iterated (they'll be empty / closed).
assert run.extensions["values"]._subscribed is False
assert run.extensions["messages"]._subscribed is False
def test_interleave_releases_projections_on_early_break(self) -> None:
run = _build_simple_graph().stream_events(
{"value": "x", "items": []}, version="v3"
)
gen = run.interleave("values", "messages")
next(gen)
gen.close()
assert run.extensions["values"]._subscribed is False
assert run.extensions["messages"]._subscribed is False
def test_interleave_releases_projections_on_validation_failure(self) -> None:
mux = StreamMux(
factories=[ValuesTransformer, _TwoChannelTransformer],
is_async=False,
)
alpha = mux.extensions["alpha"]
# Pre-subscribe alpha so that interleave will fail validation when
# it gets to the second name. The first (already-validated) channel
# should still be released.
run = GraphRunStream(None, mux, wire_pump=False)
mux.close()
alpha._subscribed = True
with pytest.raises(RuntimeError, match="already has a subscriber"):
list(run.interleave("values", "alpha"))
assert mux.extensions["values"]._subscribed is False