@@ -230,6 +230,93 @@ def test_read(self):
230230 self .assertEqual (type (s ), bytes )
231231 self .assertEqual (s , b"spam" )
232232
233+ def test_readinto (self ):
234+ with open (os_helper .TESTFN , "w+b" ) as fobj :
235+ fobj .write (b"spam" )
236+ fobj .flush ()
237+ fd = fobj .fileno ()
238+ os .lseek (fd , 0 , 0 )
239+ # Oversized so readinto without hitting end.
240+ buffer = bytearray (7 )
241+ s = os .readinto (fd , buffer )
242+ self .assertEqual (type (s ), int )
243+ self .assertEqual (s , 4 )
244+ # Should overwrite the first 4 bytes of the buffer.
245+ self .assertEqual (buffer [:4 ], b"spam" )
246+
247+ # Readinto at EOF should return 0 and not touch buffer.
248+ buffer [:] = b"notspam"
249+ s = os .readinto (fd , buffer )
250+ self .assertEqual (type (s ), int )
251+ self .assertEqual (s , 0 )
252+ self .assertEqual (bytes (buffer ), b"notspam" )
253+ s = os .readinto (fd , buffer )
254+ self .assertEqual (s , 0 )
255+ self .assertEqual (bytes (buffer ), b"notspam" )
256+
257+ # Readinto a 0 length bytearray when at EOF should return 0
258+ self .assertEqual (os .readinto (fd , bytearray ()), 0 )
259+
260+ # Readinto a 0 length bytearray with data available should return 0.
261+ os .lseek (fd , 0 , 0 )
262+ self .assertEqual (os .readinto (fd , bytearray ()), 0 )
263+
264+ @unittest .skipUnless (hasattr (os , 'get_blocking' ),
265+ 'needs os.get_blocking() and os.set_blocking()' )
266+ @unittest .skipUnless (hasattr (os , "pipe" ), "requires os.pipe()" )
267+ def test_readinto_non_blocking (self ):
268+ # Verify behavior of a readinto which would block on a non-blocking fd.
269+ r , w = os .pipe ()
270+ try :
271+ os .set_blocking (r , False )
272+ with self .assertRaises (BlockingIOError ):
273+ os .readinto (r , bytearray (5 ))
274+
275+ # Pass some data through
276+ os .write (w , b"spam" )
277+ self .assertEqual (os .readinto (r , bytearray (4 )), 4 )
278+
279+ # Still don't block or return 0.
280+ with self .assertRaises (BlockingIOError ):
281+ os .readinto (r , bytearray (5 ))
282+
283+ # At EOF should return size 0
284+ os .close (w )
285+ w = None
286+ self .assertEqual (os .readinto (r , bytearray (5 )), 0 )
287+ self .assertEqual (os .readinto (r , bytearray (5 )), 0 ) # Still EOF
288+
289+ finally :
290+ os .close (r )
291+ if w is not None :
292+ os .close (w )
293+
294+ def test_readinto_badarg (self ):
295+ with open (os_helper .TESTFN , "w+b" ) as fobj :
296+ fobj .write (b"spam" )
297+ fobj .flush ()
298+ fd = fobj .fileno ()
299+ os .lseek (fd , 0 , 0 )
300+
301+ for bad_arg in ("test" , bytes (), 14 ):
302+ with self .subTest (f"bad buffer { type (bad_arg )} " ):
303+ with self .assertRaises (TypeError ):
304+ os .readinto (fd , bad_arg )
305+
306+ with self .subTest ("doesn't work on file objects" ):
307+ with self .assertRaises (TypeError ):
308+ os .readinto (fobj , bytearray (5 ))
309+
310+ # takes two args
311+ with self .assertRaises (TypeError ):
312+ os .readinto (fd )
313+
314+ # No data should have been read with the bad arguments.
315+ buffer = bytearray (4 )
316+ s = os .readinto (fd , buffer )
317+ self .assertEqual (s , 4 )
318+ self .assertEqual (buffer , b"spam" )
319+
233320 @support .cpython_only
234321 # Skip the test on 32-bit platforms: the number of bytes must fit in a
235322 # Py_ssize_t type
@@ -249,6 +336,29 @@ def test_large_read(self, size):
249336 # operating system is free to return less bytes than requested.
250337 self .assertEqual (data , b'test' )
251338
339+
340+ @support .cpython_only
341+ # Skip the test on 32-bit platforms: the number of bytes must fit in a
342+ # Py_ssize_t type
343+ @unittest .skipUnless (INT_MAX < PY_SSIZE_T_MAX ,
344+ "needs INT_MAX < PY_SSIZE_T_MAX" )
345+ @support .bigmemtest (size = INT_MAX + 10 , memuse = 1 , dry_run = False )
346+ def test_large_readinto (self , size ):
347+ self .addCleanup (os_helper .unlink , os_helper .TESTFN )
348+ create_file (os_helper .TESTFN , b'test' )
349+
350+ # Issue #21932: For readinto the buffer contains the length rather than
351+ # a length being passed explicitly to read, should still get capped to a
352+ # valid size / not raise an OverflowError for sizes larger than INT_MAX.
353+ buffer = bytearray (INT_MAX + 10 )
354+ with open (os_helper .TESTFN , "rb" ) as fp :
355+ length = os .readinto (fp .fileno (), buffer )
356+
357+ # The test does not try to read more than 2 GiB at once because the
358+ # operating system is free to return less bytes than requested.
359+ self .assertEqual (length , 4 )
360+ self .assertEqual (buffer [:4 ], b'test' )
361+
252362 def test_write (self ):
253363 # os.write() accepts bytes- and buffer-like objects but not strings
254364 fd = os .open (os_helper .TESTFN , os .O_CREAT | os .O_WRONLY )
@@ -2467,6 +2577,10 @@ def test_lseek(self):
24672577 def test_read (self ):
24682578 self .check (os .read , 1 )
24692579
2580+ @unittest .skipUnless (hasattr (os , 'readinto' ), 'test needs os.readinto()' )
2581+ def test_readinto (self ):
2582+ self .check (os .readinto , bytearray (5 ))
2583+
24702584 @unittest .skipUnless (hasattr (os , 'readv' ), 'test needs os.readv()' )
24712585 def test_readv (self ):
24722586 buf = bytearray (10 )
0 commit comments