@@ -241,13 +241,43 @@ async def test_stage_directory(self):
241241 assert (dest / "file.txt" ).read_text () == "content"
242242 assert (dest / "sub" / "nested.txt" ).read_text () == "nested"
243243
244+ @pytest .mark .asyncio
245+ async def test_stage_directory_link_mode (self ):
246+ src_dir = Path (self .tmpdir ) / "source_link"
247+ src_dir .mkdir ()
248+ (src_dir / "file.txt" ).write_text ("content" )
249+ (src_dir / "sub" ).mkdir ()
250+ (src_dir / "sub" / "nested.txt" ).write_text ("nested" )
251+
252+ await self .fs .stage_directory (self .ws , str (src_dir ), "dest_link" , WorkspaceStageOptions (mode = "link" ))
253+
254+ dest = Path (self .tmpdir ) / "dest_link"
255+ assert dest .is_dir ()
256+ assert (dest / "file.txt" ).is_symlink ()
257+ assert (dest / "sub" ).is_symlink ()
258+ assert (dest / "file.txt" ).read_text () == "content"
259+ assert (dest / "sub" / "nested.txt" ).read_text () == "nested"
260+
261+ @pytest .mark .asyncio
262+ async def test_stage_directory_link_mode_keeps_root_mutable_for_stager_links (self ):
263+ src_dir = Path (self .tmpdir ) / "source_link_root"
264+ src_dir .mkdir ()
265+ (src_dir / "file.txt" ).write_text ("content" )
266+
267+ await self .fs .stage_directory (self .ws , str (src_dir ), "dest_link_root" , WorkspaceStageOptions (mode = "link" ))
268+
269+ dest = Path (self .tmpdir ) / "dest_link_root"
270+ (dest / "out" ).symlink_to ("../out" )
271+ assert (dest / "out" ).is_symlink ()
272+ assert not (src_dir / "out" ).exists ()
273+
244274 @pytest .mark .asyncio
245275 async def test_stage_directory_read_only (self ):
246276 src_dir = Path (self .tmpdir ) / "src_ro"
247277 src_dir .mkdir ()
248278 (src_dir / "file.txt" ).write_text ("readonly" )
249279
250- await self .fs .stage_directory (self .ws , str (src_dir ), "dest_ro" , WorkspaceStageOptions (read_only = True ))
280+ await self .fs .stage_directory (self .ws , str (src_dir ), "dest_ro" , WorkspaceStageOptions (read_only = True , mode = "copy" ))
251281 dest_file = Path (self .tmpdir ) / "dest_ro" / "file.txt"
252282 mode = dest_file .stat ().st_mode
253283 assert not (mode & 0o222 ) # no write bits
@@ -259,7 +289,7 @@ async def test_stage_directory_read_only_via_fs_flag(self):
259289 (src_dir / "file.txt" ).write_text ("fs_readonly" )
260290
261291 fs_ro = LocalWorkspaceFS (read_only_staged_skill = True )
262- await fs_ro .stage_directory (self .ws , str (src_dir ), "dest_fs_ro" , WorkspaceStageOptions ())
292+ await fs_ro .stage_directory (self .ws , str (src_dir ), "dest_fs_ro" , WorkspaceStageOptions (mode = "copy" ))
263293 dest_file = Path (self .tmpdir ) / "dest_fs_ro" / "file.txt"
264294 mode = dest_file .stat ().st_mode
265295 assert not (mode & 0o222 )
@@ -462,25 +492,25 @@ async def test_fetch_bytes_budget_above_size(self):
462492 assert data == b"hello world"
463493 assert raw == 11
464494
465- # --- _copy_directory ---
466- def test_copy_directory (self ):
495+ # --- _put_directory ---
496+ def test_put_directory_copy_mode (self ):
467497 src = Path (self .tmpdir ) / "copy_src"
468498 src .mkdir ()
469499 (src / "a.txt" ).write_text ("a" )
470500 (src / "sub" ).mkdir ()
471501 (src / "sub" / "b.txt" ).write_text ("b" )
472502
473503 dst = Path (self .tmpdir ) / "copy_dst"
474- self .fs ._copy_directory ( str (src ), str ( dst ) )
504+ self .fs ._put_directory ( self . ws , str (src ), "copy_dst" , mode = "copy" )
475505
476506 assert (dst / "a.txt" ).read_text () == "a"
477507 assert (dst / "sub" / "b.txt" ).read_text () == "b"
478508
479- def test_copy_directory_empty (self ):
509+ def test_put_directory_copy_mode_empty (self ):
480510 src = Path (self .tmpdir ) / "empty_src"
481511 src .mkdir ()
482512 dst = Path (self .tmpdir ) / "empty_dst"
483- self .fs ._copy_directory ( str (src ), str ( dst ) )
513+ self .fs ._put_directory ( self . ws , str (src ), "empty_dst" , mode = "copy" )
484514 assert dst .exists ()
485515
486516 # --- _make_tree_read_only ---
@@ -551,8 +581,9 @@ async def test_stage_inputs_host_copy(self):
551581 specs = [WorkspaceInputSpec (src = f"host://{ host_dir } " , dst = "work/inputs/data" , mode = "copy" )]
552582 await self .fs .stage_inputs (self .ws , specs )
553583
554- copied = Path (self .tmpdir ) / "work" / "inputs"
555- assert copied .exists ()
584+ copied = Path (self .tmpdir ) / "work" / "inputs" / "data"
585+ assert copied .is_dir ()
586+ assert (copied / "data.txt" ).read_text () == "host data"
556587
557588 @pytest .mark .asyncio
558589 async def test_stage_inputs_host_link (self ):
@@ -564,7 +595,9 @@ async def test_stage_inputs_host_link(self):
564595 await self .fs .stage_inputs (self .ws , specs )
565596
566597 linked = Path (self .tmpdir ) / "work" / "inputs" / "linked"
567- assert linked .is_symlink () or linked .exists ()
598+ assert linked .is_dir ()
599+ assert (linked / "link.txt" ).is_symlink ()
600+ assert (linked / "link.txt" ).read_text () == "link data"
568601
569602 @pytest .mark .asyncio
570603 async def test_stage_inputs_workspace (self ):
0 commit comments