|
2 | 2 |
|
3 | 3 | from __future__ import annotations |
4 | 4 |
|
| 5 | +import asyncio |
5 | 6 | from types import SimpleNamespace |
6 | 7 | from typing import TYPE_CHECKING, Any |
7 | 8 |
|
@@ -31,6 +32,21 @@ async def execute(self, unit: Unit) -> AsyncIterator[StepResult | RunComplete]: |
31 | 32 | yield RunComplete(outputs=[self.output]) |
32 | 33 |
|
33 | 34 |
|
| 35 | +class _NeverEndingExecutor(Executor): |
| 36 | + kind = "sandbox" |
| 37 | + |
| 38 | + def __init__(self) -> None: |
| 39 | + self.closed = False |
| 40 | + |
| 41 | + async def execute(self, unit: Unit) -> AsyncIterator[StepResult | RunComplete]: # noqa: ARG002 |
| 42 | + try: |
| 43 | + while True: |
| 44 | + yield StepResult(payload="tick") |
| 45 | + await asyncio.sleep(0) |
| 46 | + finally: |
| 47 | + self.closed = True |
| 48 | + |
| 49 | + |
34 | 50 | @pytest.mark.asyncio |
35 | 51 | async def test_coordinator_routes_untrusted_runs_to_capability_executor() -> None: |
36 | 52 | sandbox = _RecordingExecutor("sandbox", "sandbox-output") |
@@ -118,3 +134,33 @@ async def test_coordinator_skips_capability_service_when_passthrough() -> None: |
118 | 134 | assert outputs == ["default-output"] |
119 | 135 | assert default.units[0].executor_kind is None |
120 | 136 | assert default.units[0].runtime_options == {} |
| 137 | + |
| 138 | + |
| 139 | +@pytest.mark.asyncio |
| 140 | +async def test_stream_close_finalizes_capability_selected_executor() -> None: |
| 141 | + sandbox = _NeverEndingExecutor() |
| 142 | + registry = ExecutorRegistry() |
| 143 | + registry.register(_RecordingExecutor("in-process", "in-process-output")) |
| 144 | + registry.register(sandbox) |
| 145 | + |
| 146 | + class _Classifier: |
| 147 | + def trust_of_flow(self, _context: CapabilityContext) -> Trust: |
| 148 | + return Trust.UNTRUSTED |
| 149 | + |
| 150 | + def is_untrusted_node(self, _node: dict[str, Any], _context: CapabilityContext | None = None) -> bool: |
| 151 | + return True |
| 152 | + |
| 153 | + capability_service = CapabilityService(settings_service=_StubSettings()) |
| 154 | + capability_service.install(classifier=_Classifier(), untrusted_executor_kind="sandbox") |
| 155 | + coordinator = Coordinator( |
| 156 | + registry=registry, |
| 157 | + executor_kind="in-process", |
| 158 | + capability_service=capability_service, |
| 159 | + ) |
| 160 | + |
| 161 | + stream = coordinator.stream(SimpleNamespace(), inputs=[{}]) |
| 162 | + assert await anext(stream) == "tick" |
| 163 | + await stream.aclose() |
| 164 | + await asyncio.sleep(0) |
| 165 | + |
| 166 | + assert sandbox.closed is True |
0 commit comments