11import sys
2+ import threading
23import unittest
34from test import support
45from test .support import import_helper
6+ from test .support import threading_helper
57
68_testlimitedcapi = import_helper .import_module ('_testlimitedcapi' )
79_testcapi = import_helper .import_module ('_testcapi' )
@@ -318,6 +320,10 @@ def test_join(self):
318320
319321class BaseWriterTest :
320322 RESULT_TYPE = NotImplementedError
323+ SMALL_BUFFER = 11 # bytes
324+ assert SMALL_BUFFER < _testcapi .PyBytesWriter_small_buffer
325+ LARGE_BUFFER = _testcapi .PyBytesWriter_small_buffer + 17 # bytes
326+ NEW_BYTE = b'\xff '
321327
322328 def create_writer (self , alloc = 0 , string = b'' ):
323329 raise NotImplementedError
@@ -334,23 +340,60 @@ def test_create(self):
334340 self .assertEqual (writer .get_size (), 0 )
335341 self .bytes_equal (writer .finish (), b'' )
336342
337- writer = self .create_writer (3 , b'abc' )
343+ writer = self .create_writer (3 )
344+ writer .write (0 , b'abc' )
338345 self .assertEqual (writer .get_size (), 3 )
339346 self .bytes_equal (writer .finish (), b'abc' )
340347
348+ @unittest .skipUnless (support .Py_DEBUG , 'need Py_DEBUG' )
349+ def test_get_data (self ):
350+ # Test PyBytesWriter_GetData()
351+ writer = self .create_writer (6 )
352+ NEW_BYTE = self .NEW_BYTE
353+ self .assertEqual (writer .get_data (), NEW_BYTE * 6 )
354+ writer .write (0 , b'abc' )
355+ self .assertEqual (writer .get_data (), b'abc' + NEW_BYTE * 3 )
356+ writer .write (3 , b'123' )
357+ self .assertEqual (writer .get_data (), b'abc123' )
358+
359+ # Switch from small buffer to large buffer
360+ small , large = self .SMALL_BUFFER , self .LARGE_BUFFER
361+ writer = self .create_writer (small )
362+ self .assertEqual (writer .get_data (), NEW_BYTE * small )
363+ writer .write (0 , b's' * small )
364+ self .assertEqual (writer .get_data (), b's' * small )
365+ writer .resize (large )
366+ self .assertEqual (writer .get_data (), b's' * small + NEW_BYTE * (large - small ))
367+ writer .write (small , b'L' * (large - small ))
368+ self .assertEqual (writer .get_data (), b's' * small + b'L' * (large - small ))
369+
370+ # Resize large buffer
371+ small , large = self .SMALL_BUFFER , self .LARGE_BUFFER
372+ writer = self .create_writer (large )
373+ self .assertEqual (writer .get_data (), NEW_BYTE * large )
374+ writer .write (0 , b'L' * large )
375+ self .assertEqual (writer .get_data (), b'L' * large )
376+ writer .resize (large + 10 )
377+ self .assertEqual (writer .get_data (), b'L' * large + NEW_BYTE * 10 )
378+ writer .write (large , b'#' * 10 )
379+ self .assertEqual (writer .get_data (), b'L' * large + b'#' * 10 )
380+
341381 def test_finish_with_size (self ):
342382 # Test PyBytesWriter_FinishWithSize()
343- writer = self .create_writer (10 , b'abcdef' )
383+ writer = self .create_writer (10 )
384+ writer .write (0 , b'abc123' )
344385 self .assertEqual (writer .get_size (), 10 )
345386 self .bytes_equal (writer .finish_with_size (3 ), b'abc' )
346387
347388 # Error if the size is negative
348- writer = self .create_writer (3 , b'abc' )
389+ writer = self .create_writer (3 , )
390+ writer .write (0 , b'abc' )
349391 with self .assertRaises (ValueError ):
350392 writer .finish_with_size (- 3 )
351393
352394 # Error if the requested size is larger than the allocated size
353- writer = self .create_writer (3 , b'abc' )
395+ writer = self .create_writer (3 )
396+ writer .write (0 , b'abc' )
354397 with self .assertRaises (ValueError ):
355398 writer .finish_with_size (4 )
356399
@@ -368,44 +411,118 @@ def test_write_bytes(self):
368411 def test_resize (self ):
369412 # Test PyBytesWriter_Resize()
370413 writer = self .create_writer ()
371- writer .resize (len (b'number=123456' ), b'number=123456' )
372- writer .resize (len (b'number=123456' ), b'' )
373- self .assertEqual (writer .get_size (), len (b'number=123456' ))
374- self .bytes_equal (writer .finish (), b'number=123456' )
414+ writer .resize (len (b'hello' ))
415+ writer .write (0 , b'hello' )
416+ self .assertEqual (writer .finish (), b'hello' )
375417
376418 writer = self .create_writer ()
377- writer .resize (0 , b'' )
378- writer .resize (len (b'number=123456' ), b'number=123456' )
379- self .bytes_equal (writer .finish (), b'number=123456' )
380-
419+ writer .resize (0 ) # noop
420+ writer .resize (len (b'number' ))
421+ writer .write (0 , b'number' )
422+ writer .resize (len (b'number=' ))
423+ writer .write (len (b'number' ), b'=' )
424+ writer .resize (len (b'number=123' ), )
425+ writer .write (len (b'number=' ), b'123' )
426+ writer .resize (len (b'number=123' )) # noop
427+ self .assertEqual (writer .finish (), b'number=123' )
428+
429+ # Switch from small buffer to large buffer
381430 writer = self .create_writer ()
382- writer .resize (len (b'number=' ), b'number=' )
383- writer .resize (len (b'number=123456' ), b'123456' )
384- self .bytes_equal (writer .finish (), b'number=123456' )
385-
431+ small , large = self .SMALL_BUFFER , self .LARGE_BUFFER
432+ writer .resize (small )
433+ writer .write (0 , b's' * small )
434+ writer .resize (large )
435+ writer .write (small , b'L' * (large - small ))
436+ self .assertEqual (writer .finish (),
437+ b's' * small + b'L' * (large - small ))
438+
439+ # Make sure that it's possible to write after a resize to zero
440+ # when a bytes/bytearray object is allocated.
386441 writer = self .create_writer ()
387- writer .resize (len (b'number=' ), b'number=' )
388- writer .resize (len (b'number=' ), b'' )
389- writer .resize (len (b'number=123456' ), b'123456' )
390- self .bytes_equal (writer .finish (), b'number=123456' )
442+ writer .resize (self .LARGE_BUFFER )
443+ writer .resize (0 )
444+ writer .write_bytes (b'abc' , 3 )
445+ self .assertEqual (writer .finish (), b'abc' )
446+
447+ # invalid size
448+ for size in (self .SMALL_BUFFER , self .LARGE_BUFFER ):
449+ with self .subTest (size = size ):
450+ writer = self .create_writer ()
451+ writer .write_bytes (b'x' * size , - 1 )
452+ with self .assertRaisesRegex (ValueError , 'size must be >= 0' ):
453+ writer .resize (- 1 )
454+ with self .assertRaises ((MemoryError , OverflowError )):
455+ writer .resize (_testcapi .PY_SSIZE_T_MAX )
456+ self .assertEqual (writer .finish (), b'x' * size )
457+
458+ def test_grow (self ):
459+ # Test PyBytesWriter_Grow()
460+ writer = self .create_writer (0 )
461+ writer .grow (len (b'number=123' ))
462+ writer .write (0 , b'number=123' )
463+ self .assertEqual (writer .finish (), b'number=123' )
391464
392465 writer = self .create_writer ()
393- writer .resize (len (b'number' ), b'number' )
394- writer .resize (len (b'number=' ), b'=' )
395- writer .resize (len (b'number=123' ), b'123' )
396- writer .resize (len (b'number=123456' ), b'456' )
397- self .bytes_equal (writer .finish (), b'number=123456' )
466+ writer .grow (0 ) # noop
467+ writer .grow (len (b'number' ))
468+ writer .write (0 , b'number' )
469+ writer .grow (len (b'=' ))
470+ writer .write (len (b'number' ), b'=' )
471+ writer .grow (len (b'123' ), )
472+ writer .write (len (b'number=' ), b'123' )
473+ writer .grow (0 ) # noop
474+ self .assertEqual (writer .finish (), b'number=123' )
475+
476+ for size in (self .SMALL_BUFFER , self .LARGE_BUFFER ):
477+ with self .subTest (size = size ):
478+ # Truncate the last byte
479+ data = b'x' * size
480+ writer = self .create_writer (size )
481+ writer .write (0 , data )
482+ self .assertEqual (writer .get_data (), data )
483+ writer .grow (- 1 )
484+ self .assertEqual (writer .get_data (), data [:- 1 ])
485+ self .assertEqual (writer .finish (), data [:- 1 ])
486+
487+ # Make the buffer empty
488+ writer = self .create_writer (size )
489+ writer .write (0 , data )
490+ writer .grow (- size )
491+ self .assertEqual (writer .get_data (), b'' )
492+ self .assertEqual (writer .finish (), b'' )
493+
494+ # Switch from small buffer to large buffer
495+ writer = self .create_writer ()
496+ small , large = self .SMALL_BUFFER , self .LARGE_BUFFER
497+ writer .grow (small )
498+ writer .write (0 , b's' * small )
499+ writer .grow (large - small )
500+ writer .write (small , b'L' * (large - small ))
501+ self .assertEqual (writer .finish (),
502+ b's' * small + b'L' * (large - small ))
503+
504+ # invalid size
505+ for size in (self .SMALL_BUFFER , self .LARGE_BUFFER ):
506+ with self .subTest (size = size ):
507+ writer = self .create_writer ()
508+ writer .write_bytes (b'x' * size , - 1 )
509+ with self .assertRaisesRegex (ValueError , 'invalid size' ):
510+ writer .grow (- size - 1 )
511+ with self .assertRaises (MemoryError ):
512+ writer .grow (_testcapi .PY_SSIZE_T_MAX )
513+ self .assertEqual (writer .finish (), b'x' * size )
398514
399515 @support .nomemtest
400516 def test_resize_error (self ):
401- small_buffer = _testcapi .PyBytesWriter_small_buffer
402- init = b'x' * (small_buffer * 2 )
403- writer = self .create_writer (len (init ), init )
517+ # Test PyBytesWriter_Resize() error
518+ init = b'x' * self .LARGE_BUFFER
519+ writer = self .create_writer (len (init ))
520+ writer .write (0 , init )
404521 size = len (init ) + 100
405522 try :
406523 with self .assertRaises (MemoryError ):
407524 _testcapi .set_nomemory (0 )
408- writer .resize (size , b'' )
525+ writer .resize (size )
409526 finally :
410527 _testcapi .remove_mem_hooks ()
411528 suffix = b'still working'
@@ -443,36 +560,71 @@ def test_format_s_memory_error(self):
443560 finally :
444561 _testcapi .remove_mem_hooks ()
445562 writer .write_bytes (b'after' , - 1 )
446- self .bytes_equal (writer .finish (), b'after' )
563+
564+ @threading_helper .requires_working_threading ()
565+ def test_thread (self ):
566+ # PyBytesWriter can be used by multiple threads: it's up to the caller
567+ # to implement a lock to prevent concurrent accesses.
568+ writer = self .create_writer (0 )
569+ size = None
570+ data = None
571+
572+ def thread_func (writer , LARGE_BUFFER ):
573+ nonlocal size , data
574+
575+ # create a bytes object for the buffer
576+ writer .write_bytes (b'x' * LARGE_BUFFER , LARGE_BUFFER )
577+
578+ # so we can check a write with a bytes object
579+ writer .write_bytes (b'yz' , 2 )
580+ writer .format_i (b'i=%i' , 5 )
581+ writer .resize (10 )
582+ data = writer .get_data ()
583+ size = writer .get_size ()
584+
585+ thread = threading .Thread (target = thread_func ,
586+ args = (writer , self .LARGE_BUFFER ))
587+ thread .start ()
588+ threading_helper .join_thread (thread )
589+
590+ self .assertEqual (size , 10 )
591+ self .assertEqual (data , b'x' * 10 )
592+ self .assertEqual (writer .finish (), b'x' * 10 )
447593
448594
449595class BytesWriterTest (BaseWriterTest , unittest .TestCase ):
450596 RESULT_TYPE = bytes
451597
452- def create_writer (self , alloc = 0 , string = b'' ):
598+ def create_writer (self , size = 0 ):
453599 # Test PyBytesWriter_Create()
454- return _testcapi .PyBytesWriter (alloc , string , 0 )
600+ return _testcapi .PyBytesWriter (size , 0 )
455601
456602 # Only PyBytesWriter_Create() returns singletons
457603 def test_singletons (self ):
458604 empty = b''
459605 singletons = {ch : bytes ((ch ,)) for ch in range (256 )}
460- small_buffer = _testcapi .PyBytesWriter_small_buffer
461606
462607 writer = self .create_writer ()
463608 self .assertIs (writer .finish (), empty )
464609
465- # Test writer larger than small_buffer
610+ # Large buffer
466611 writer = self .create_writer ()
467- unused_text = b'x' * ( small_buffer * 2 )
612+ unused_text = b'x' * self . LARGE_BUFFER
468613 writer .write_bytes (unused_text , len (unused_text ))
469614 self .assertIs (writer .finish_with_size (0 ), empty )
470615
616+ # Large buffer with resize
617+ writer = self .create_writer ()
618+ unused_text = b'x' * self .LARGE_BUFFER
619+ writer .write_bytes (unused_text , len (unused_text ))
620+ writer .resize (0 )
621+ self .assertIs (writer .finish (), empty )
622+
471623 for ch in range (256 ):
472- text = bytes ((ch ,))
624+ byte = bytes ((ch ,))
473625
474626 writer = self .create_writer ()
475- writer .write_bytes (text , 1 )
627+ writer .write_bytes (byte , len ( byte ) )
476628 self .assertIs (writer .finish (), singletons [ch ])
477629
478630 def test_example_abc (self ):
@@ -488,9 +640,9 @@ def test_example_highlevel(self):
488640class ByteArrayWriterTest (BaseWriterTest , unittest .TestCase ):
489641 RESULT_TYPE = bytearray
490642
491- def create_writer (self , alloc = 0 , string = b'' ):
643+ def create_writer (self , size = 0 ):
492644 # Test private _PyBytesWriter_CreateByteArray()
493- return _testcapi .PyBytesWriter (alloc , string , 1 )
645+ return _testcapi .PyBytesWriter (size , 1 )
494646
495647
496648if __name__ == "__main__" :
0 commit comments