From d3350fd5029831d4b09d9dbe98fcd92f9020e684 Mon Sep 17 00:00:00 2001 From: Nico Date: Fri, 3 Apr 2026 18:35:48 +0200 Subject: [PATCH] Add 4 red tests for Phase 2: shared node pool + contextvar HUD Tests that will pass once implemented: - pool_creates_shared_nodes: NodePool has shared stateless nodes - pool_excludes_stateful: sensor/memorizer/ui not shared - pool_reuses_instances: same pool returns same objects - contextvar_hud_isolation: concurrent tasks get isolated HUD Co-Authored-By: Claude Opus 4.6 (1M context) --- tests/test_engine.py | 77 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 76 insertions(+), 1 deletion(-) diff --git a/tests/test_engine.py b/tests/test_engine.py index 9414125..5911759 100644 --- a/tests/test_engine.py +++ b/tests/test_engine.py @@ -526,6 +526,76 @@ def test_model_override_per_request(): assert result["trace"]["path"] == "reflex" +# --- Phase 2: Shared Node Pool (RED — will fail until implemented) --- + +def test_pool_creates_shared_nodes(): + """NodePool creates shared instances for stateless nodes.""" + from agent.node_pool import NodePool + pool = NodePool("v4-eras") + # Shared nodes should exist + assert "input" in pool.shared, "input should be shared" + assert "output" in pool.shared, "output should be shared" + assert "pa" in pool.shared, "pa should be shared" + assert "expert_eras" in pool.shared, "expert_eras should be shared" + assert "interpreter" in pool.shared, "interpreter should be shared" + + +def test_pool_excludes_stateful(): + """NodePool excludes stateful nodes (sensor, memorizer, ui).""" + from agent.node_pool import NodePool + pool = NodePool("v4-eras") + assert "sensor" not in pool.shared, "sensor should NOT be shared" + assert "memorizer" not in pool.shared, "memorizer should NOT be shared" + assert "ui" not in pool.shared, "ui should NOT be shared" + + +def test_pool_reuses_instances(): + """Two Runtimes using the same pool share node objects.""" + from agent.node_pool import NodePool + pool = NodePool("v4-eras") + # Same pool → same node instances + input1 = pool.shared["input"] + input2 = pool.shared["input"] + assert input1 is input2, "pool should return same instance" + + +def test_contextvar_hud_isolation(): + """Contextvars isolate HUD events between concurrent tasks.""" + from agent.nodes.base import _current_hud + + results_a = [] + results_b = [] + + async def hud_a(data): + results_a.append(data) + + async def hud_b(data): + results_b.append(data) + + async def task_a(): + _current_hud.set(hud_a) + # Simulate work with a yield point + await asyncio.sleep(0) + hud_fn = _current_hud.get() + await hud_fn({"from": "a"}) + + async def task_b(): + _current_hud.set(hud_b) + await asyncio.sleep(0) + hud_fn = _current_hud.get() + await hud_fn({"from": "b"}) + + async def run_both(): + await asyncio.gather(task_a(), task_b()) + + asyncio.get_event_loop().run_until_complete(run_both()) + + assert len(results_a) == 1 and results_a[0]["from"] == "a", \ + f"task_a HUD leaked: {results_a}" + assert len(results_b) == 1 and results_b[0]["from"] == "b", \ + f"task_b HUD leaked: {results_b}" + + # --- Test registry (for run_tests.py) --- TESTS = { @@ -538,8 +608,13 @@ TESTS = { 'frame_trace_reflex': test_frame_trace_reflex, 'frame_trace_expert': test_frame_trace_expert, 'frame_trace_expert_with_interpreter': test_frame_trace_expert_with_interpreter, - # Red — Phase 1: config-driven models + # Phase 1: config-driven models 'graph_has_models': test_graph_has_models, 'instantiate_applies_graph_models': test_instantiate_applies_graph_models, 'model_override_per_request': test_model_override_per_request, + # Phase 2: shared node pool + 'pool_creates_shared_nodes': test_pool_creates_shared_nodes, + 'pool_excludes_stateful': test_pool_excludes_stateful, + 'pool_reuses_instances': test_pool_reuses_instances, + 'contextvar_hud_isolation': test_contextvar_hud_isolation, }