kiln_ai.sandbox.test_sandbox_shared
Tests for sandbox shared helpers — spawn lock identity and call_entrypoint.
1"""Tests for sandbox shared helpers — spawn lock identity and call_entrypoint.""" 2 3import asyncio 4 5import pytest 6 7from kiln_ai.adapters.eval.sandbox_worker import execute_scorer_bridged 8from kiln_ai.datamodel.project import Project 9from kiln_ai.sandbox.entrypoint import call_entrypoint 10from kiln_ai.sandbox.spawn import _spawn_lock, start_process_with_light_main 11from kiln_ai.tools.sandbox_bridge import NestedToolServer, run_bridged_child 12 13 14class TestSpawnLockIdentity: 15 def test_spawn_lock_shared_with_eval(self): 16 """Code evals and code tools share the same _spawn_lock (PyInstaller #7410).""" 17 from kiln_ai.sandbox import spawn as spawn_mod 18 19 assert spawn_mod._spawn_lock is _spawn_lock 20 21 def test_bridge_delegates_to_shared_spawn_helper(self): 22 """The shared bridge spawns via start_process_with_light_main from sandbox.spawn.""" 23 from kiln_ai.tools import sandbox_bridge 24 25 assert ( 26 sandbox_bridge.start_process_with_light_main 27 is start_process_with_light_main 28 ) 29 30 @pytest.mark.asyncio 31 async def test_scorer_runs_through_bridge(self): 32 """Regression: a scorer executes through the shared bridge and returns its dict.""" 33 code = ( 34 "def score(output, trace, reference_data, task_input):\n" 35 " return {'ok': 1.0}\n" 36 ) 37 server = NestedToolServer( 38 allowlist=[], project=Project(name="shared_test"), task=None, context=None 39 ) 40 res = await run_bridged_child( 41 target=execute_scorer_bridged, 42 args=( 43 code, 44 { 45 "output": "x", 46 "trace": None, 47 "reference_data": None, 48 "task_input": "y", 49 }, 50 ), 51 timeout_s=10, 52 server=server, 53 ) 54 assert res.result_msg is not None 55 assert res.result_msg["ok"] == {"ok": 1.0} 56 57 58class TestCallEntrypoint: 59 def test_sync_function(self): 60 def fn(x): 61 return x * 2 62 63 assert call_entrypoint(fn, {"x": 5}) == 10 64 65 def test_async_function(self): 66 async def fn(x): 67 return x + 1 68 69 assert call_entrypoint(fn, {"x": 5}) == 6 70 71 def test_async_with_gather(self): 72 async def fn(values): 73 async def double(v): 74 return v * 2 75 76 return await asyncio.gather(*(double(v) for v in values)) 77 78 result = call_entrypoint(fn, {"values": [1, 2, 3]}) 79 assert result == [2, 4, 6] 80 81 def test_sync_returning_non_coroutine(self): 82 def fn(): 83 return "plain" 84 85 assert call_entrypoint(fn, {}) == "plain" 86 87 def test_propagates_exception(self): 88 def fn(): 89 raise ValueError("boom") 90 91 with pytest.raises(ValueError, match="boom"): 92 call_entrypoint(fn, {}) 93 94 def test_async_propagates_exception(self): 95 async def fn(): 96 raise RuntimeError("async boom") 97 98 with pytest.raises(RuntimeError, match="async boom"): 99 call_entrypoint(fn, {})
class
TestSpawnLockIdentity:
15class TestSpawnLockIdentity: 16 def test_spawn_lock_shared_with_eval(self): 17 """Code evals and code tools share the same _spawn_lock (PyInstaller #7410).""" 18 from kiln_ai.sandbox import spawn as spawn_mod 19 20 assert spawn_mod._spawn_lock is _spawn_lock 21 22 def test_bridge_delegates_to_shared_spawn_helper(self): 23 """The shared bridge spawns via start_process_with_light_main from sandbox.spawn.""" 24 from kiln_ai.tools import sandbox_bridge 25 26 assert ( 27 sandbox_bridge.start_process_with_light_main 28 is start_process_with_light_main 29 ) 30 31 @pytest.mark.asyncio 32 async def test_scorer_runs_through_bridge(self): 33 """Regression: a scorer executes through the shared bridge and returns its dict.""" 34 code = ( 35 "def score(output, trace, reference_data, task_input):\n" 36 " return {'ok': 1.0}\n" 37 ) 38 server = NestedToolServer( 39 allowlist=[], project=Project(name="shared_test"), task=None, context=None 40 ) 41 res = await run_bridged_child( 42 target=execute_scorer_bridged, 43 args=( 44 code, 45 { 46 "output": "x", 47 "trace": None, 48 "reference_data": None, 49 "task_input": "y", 50 }, 51 ), 52 timeout_s=10, 53 server=server, 54 ) 55 assert res.result_msg is not None 56 assert res.result_msg["ok"] == {"ok": 1.0}
@pytest.mark.asyncio
async def
test_scorer_runs_through_bridge(self):
31 @pytest.mark.asyncio 32 async def test_scorer_runs_through_bridge(self): 33 """Regression: a scorer executes through the shared bridge and returns its dict.""" 34 code = ( 35 "def score(output, trace, reference_data, task_input):\n" 36 " return {'ok': 1.0}\n" 37 ) 38 server = NestedToolServer( 39 allowlist=[], project=Project(name="shared_test"), task=None, context=None 40 ) 41 res = await run_bridged_child( 42 target=execute_scorer_bridged, 43 args=( 44 code, 45 { 46 "output": "x", 47 "trace": None, 48 "reference_data": None, 49 "task_input": "y", 50 }, 51 ), 52 timeout_s=10, 53 server=server, 54 ) 55 assert res.result_msg is not None 56 assert res.result_msg["ok"] == {"ok": 1.0}
Regression: a scorer executes through the shared bridge and returns its dict.
class
TestCallEntrypoint:
59class TestCallEntrypoint: 60 def test_sync_function(self): 61 def fn(x): 62 return x * 2 63 64 assert call_entrypoint(fn, {"x": 5}) == 10 65 66 def test_async_function(self): 67 async def fn(x): 68 return x + 1 69 70 assert call_entrypoint(fn, {"x": 5}) == 6 71 72 def test_async_with_gather(self): 73 async def fn(values): 74 async def double(v): 75 return v * 2 76 77 return await asyncio.gather(*(double(v) for v in values)) 78 79 result = call_entrypoint(fn, {"values": [1, 2, 3]}) 80 assert result == [2, 4, 6] 81 82 def test_sync_returning_non_coroutine(self): 83 def fn(): 84 return "plain" 85 86 assert call_entrypoint(fn, {}) == "plain" 87 88 def test_propagates_exception(self): 89 def fn(): 90 raise ValueError("boom") 91 92 with pytest.raises(ValueError, match="boom"): 93 call_entrypoint(fn, {}) 94 95 def test_async_propagates_exception(self): 96 async def fn(): 97 raise RuntimeError("async boom") 98 99 with pytest.raises(RuntimeError, match="async boom"): 100 call_entrypoint(fn, {})