1313import threading
1414import time
1515from datetime import datetime
16+ from io import BytesIO , StringIO
1617from unittest import SkipTest , mock
1718
1819import ddt
4142 join_path ,
4243 remove_password_if_present ,
4344 rmtree ,
45+ stream_copy ,
4446)
4547from test .cleanup import cleanup_directory
4648from test .lib import TestBase , requires_symlinks , with_rw_repo
@@ -67,6 +69,45 @@ def test_join_path_keeps_later_tokens_relative(parts, expected):
6769 assert join_path (* parts ) == expected
6870
6971
72+ @pytest .mark .parametrize (
73+ "parts, expected" ,
74+ [([b"ab" , b"c" , b"def" ], b"abcdef" ), (["ab" , "c" , "def" ], "abcdef" ), ([], b"" ), ([], "" )],
75+ )
76+ def test_stream_copy_waits_for_an_empty_read (parts , expected ):
77+ chunks = iter (parts )
78+
79+ class Reader :
80+ def read (self , size ):
81+ chunk = next (chunks , expected [:0 ])
82+ assert len (chunk ) <= size
83+ return chunk
84+
85+ destination = BytesIO () if isinstance (expected , bytes ) else StringIO ()
86+ assert stream_copy (Reader (), destination , chunk_size = 4 ) == len (expected )
87+ assert destination .getvalue () == expected
88+
89+
90+ @pytest .mark .skipif (sys .platform == "win32" , reason = "requires POSIX unbuffered pipe reads" )
91+ def test_stream_copy_keeps_reading_an_unbuffered_pipe ():
92+ read_fd , write_fd = os .pipe ()
93+
94+ class Destination (BytesIO ):
95+ def write (self , data ):
96+ result = super ().write (data )
97+ if data and not writer .closed :
98+ # Make the second chunk available only after the first short read is copied.
99+ writer .write (b"second" )
100+ writer .close ()
101+ return result
102+
103+ destination = Destination ()
104+ with os .fdopen (read_fd , "rb" , buffering = 0 ) as source , os .fdopen (write_fd , "wb" , buffering = 0 ) as writer :
105+ writer .write (b"first-" )
106+ copied = stream_copy (source , destination , chunk_size = 1024 )
107+ assert copied == len (b"first-second" )
108+ assert destination .getvalue () == b"first-second"
109+
110+
70111@pytest .fixture
71112def tmp_path (request ):
72113 # Unreadable files and symlinks cannot be copied by native fixture tooling.
0 commit comments