@@ -614,3 +614,71 @@ async def test_snapshot_disk_async(self, async_sdk_client: AsyncRunloopSDK) -> N
614614 await snapshot .delete ()
615615 finally :
616616 await devbox .shutdown ()
617+
618+
619+ class TestAsyncDevboxExecutionPagination :
620+ """Test stdout/stderr pagination and streaming functionality."""
621+
622+ @pytest .mark .timeout (TWO_MINUTE_TIMEOUT )
623+ async def test_exec_with_large_stdout_streaming (self , shared_devbox : AsyncDevbox ) -> None :
624+ """Test that large stdout output is fully captured via streaming when truncated."""
625+ # Generate 1000 lines of output
626+ result = await shared_devbox .cmd .exec (
627+ command = 'for i in $(seq 1 1000); do echo "Line $i with some content to make it realistic"; done' ,
628+ )
629+
630+ assert result .exit_code == 0
631+ stdout = await result .stdout ()
632+ lines = stdout .strip ().split ("\n " )
633+
634+ # Verify we got all 1000 lines
635+ assert len (lines ) == 1000 , f"Expected 1000 lines, got { len (lines )} "
636+
637+ # Verify first and last lines
638+ assert "Line 1" in lines [0 ]
639+ assert "Line 1000" in lines [- 1 ]
640+
641+ @pytest .mark .timeout (TWO_MINUTE_TIMEOUT )
642+ async def test_exec_with_large_stderr_streaming (self , shared_devbox : AsyncDevbox ) -> None :
643+ """Test that large stderr output is fully captured via streaming when truncated."""
644+ # Generate 1000 lines of stderr output
645+ result = await shared_devbox .cmd .exec (
646+ command = 'for i in $(seq 1 1000); do echo "Error line $i" >&2; done' ,
647+ )
648+
649+ assert result .exit_code == 0
650+ stderr = await result .stderr ()
651+ lines = stderr .strip ().split ("\n " )
652+
653+ # Verify we got all 1000 lines
654+ assert len (lines ) == 1000 , f"Expected 1000 lines, got { len (lines )} "
655+
656+ # Verify first and last lines
657+ assert "Error line 1" in lines [0 ]
658+ assert "Error line 1000" in lines [- 1 ]
659+
660+ @pytest .mark .timeout (TWO_MINUTE_TIMEOUT )
661+ async def test_exec_with_truncated_stdout_num_lines (self , shared_devbox : AsyncDevbox ) -> None :
662+ """Test num_lines parameter works correctly with potentially truncated output."""
663+ # Generate 2000 lines of output
664+ result = await shared_devbox .cmd .exec (
665+ command = 'for i in $(seq 1 2000); do echo "Line $i"; done' ,
666+ )
667+
668+ assert result .exit_code == 0
669+
670+ # Request last 50 lines
671+ stdout = await result .stdout (num_lines = 50 )
672+ lines = stdout .strip ().split ("\n " )
673+
674+ # Verify we got exactly 50 lines
675+ assert len (lines ) == 50 , f"Expected 50 lines, got { len (lines )} "
676+
677+ # Verify these are the last 50 lines
678+ assert "Line 1951" in lines [0 ]
679+ assert "Line 2000" in lines [- 1 ]
680+
681+ # TODO: Add test_exec_stdout_line_counting test once empty line logic is fixed.
682+ # Currently there's an inconsistency where _count_non_empty_lines counts non-empty
683+ # lines but _get_last_n_lines returns N lines (including empty ones). This affects
684+ # both Python and TypeScript SDKs and needs to be fixed together.
0 commit comments