@@ -11,12 +11,17 @@ def __init__(
1111 * ,
1212 container_id : str ,
1313 image : str ,
14+ image_id : str = "sha256:latest" ,
1415 status : str = "running" ,
1516 mounts : list [dict [str , str ]] | None = None ,
1617 ) -> None :
1718 self .id = container_id
1819 self .status = status
19- self .attrs = {"Config" : {"Image" : image }, "Mounts" : mounts or []}
20+ self .attrs = {
21+ "Image" : image_id ,
22+ "Config" : {"Image" : image },
23+ "Mounts" : mounts or [],
24+ }
2025
2126
2227class _FakeManager :
@@ -25,6 +30,7 @@ def __init__(self) -> None:
2530 self .removed : list [str ] = []
2631 self .start_should_fail = False
2732 self .exec_should_fail = False
33+ self .expected_image_id : str | None = "sha256:latest"
2834 self ._containers : dict [str , _FakeContainer ] = {}
2935 self ._by_name : dict [str , str ] = {}
3036
@@ -35,7 +41,9 @@ async def create_container(self, name=None, environment=None) -> str:
3541 container_id = f"c{ len (self .created ) + 1 } "
3642 self .created .append (container_id )
3743 container = _FakeContainer (
38- container_id = container_id , image = "ash-sandbox:latest"
44+ container_id = container_id ,
45+ image = "ash-sandbox:latest" ,
46+ image_id = self .expected_image_id or "sha256:latest" ,
3947 )
4048 self ._containers [container_id ] = container
4149 if name :
@@ -89,6 +97,10 @@ async def remove_container(self, container_id: str, force: bool = True) -> None:
8997 if cid == container_id :
9098 self ._by_name .pop (name , None )
9199
100+ async def get_image_id (self , image_ref : str ) -> str | None :
101+ _ = image_ref
102+ return self .expected_image_id
103+
92104
93105def _patch_runtime_paths (monkeypatch , tmp_path : Path ) -> None :
94106 monkeypatch .setattr ("ash.sandbox.executor.get_run_path" , lambda : tmp_path / "run" )
@@ -181,7 +193,36 @@ async def test_reuse_container_prunes_image_mismatch_then_recreates(
181193 manager = _FakeManager ()
182194 shared_name = "ash-sandbox-deadbeef"
183195 manager ._containers ["stale" ] = _FakeContainer (
184- container_id = "stale" , image = "old-image-id" , status = "running"
196+ container_id = "stale" ,
197+ image = "old-image-id" ,
198+ image_id = "sha256:stale" ,
199+ status = "running" ,
200+ )
201+ manager ._by_name [shared_name ] = "stale"
202+
203+ executor = SandboxExecutor ()
204+ executor ._manager = manager
205+ executor ._initialized = True
206+ monkeypatch .setattr (executor , "_managed_container_name" , lambda : shared_name )
207+
208+ result = await executor .execute ("echo hi" , reuse_container = True )
209+ assert result .success is True
210+ assert "stale" in manager .removed
211+ assert manager .created == ["c1" ]
212+
213+
214+ async def test_reuse_container_prunes_stale_latest_image_id_then_recreates (
215+ tmp_path : Path , monkeypatch
216+ ) -> None :
217+ _patch_runtime_paths (monkeypatch , tmp_path )
218+ manager = _FakeManager ()
219+ manager .expected_image_id = "sha256:newlatest"
220+ shared_name = "ash-sandbox-deadbeef"
221+ manager ._containers ["stale" ] = _FakeContainer (
222+ container_id = "stale" ,
223+ image = "ash-sandbox:latest" ,
224+ image_id = "sha256:oldlatest" ,
225+ status = "running" ,
185226 )
186227 manager ._by_name [shared_name ] = "stale"
187228
0 commit comments