11import threading
22import unittest
33from collections import UserList
4+ from test import support
45from test .support import import_helper
56from test .support import threading_helper
67
1112NULL = None
1213PY_SSIZE_T_MIN = _testcapi .PY_SSIZE_T_MIN
1314PY_SSIZE_T_MAX = _testcapi .PY_SSIZE_T_MAX
15+ LIST_SMALL_ALLOCATED = _testcapi .LIST_SMALL_ALLOCATED
1416
1517class ListSubclass (list ):
1618 pass
@@ -360,7 +362,7 @@ def test_uninitialized_list_repr(self):
360362
361363
362364class ListWriterTests (unittest .TestCase ):
363- def create_writer (self , size = 0 ):
365+ def create_writer (self , size ):
364366 # Test PyListWriter_Create()
365367 return _testcapi .PyListWriter (size )
366368
@@ -385,6 +387,16 @@ def check_append(self, method):
385387 append (item )
386388 self .list_equal (writer .finish (), items )
387389
390+ # test MemoryError
391+ writer = self .create_writer (0 )
392+ append = getattr (writer , method )
393+ with self .assertRaises (MemoryError ):
394+ with support .inject_memory_error_cm ():
395+ append ("alloc failed" )
396+ for item in items :
397+ append (item )
398+ self .list_equal (writer .finish (), items )
399+
388400 def test_append (self ):
389401 # Test PyListWriter_Append()
390402 self .check_append ('append' )
@@ -402,6 +414,16 @@ def check_extend(self, method):
402414 extend (items )
403415 self .list_equal (writer .finish (), items )
404416
417+ # test MemoryError
418+ writer = self .create_writer (0 )
419+ extend = getattr (writer , method )
420+ items_error = ("alloc failed" ,)
421+ with self .assertRaises (MemoryError ):
422+ with support .inject_memory_error_cm ():
423+ extend (items_error )
424+ extend (items )
425+ self .list_equal (writer .finish (), items )
426+
405427 def test_extend (self ):
406428 # Test PyListWriter_Extend()
407429 self .check_extend ('extend' )
@@ -414,27 +436,94 @@ def test_extend_array_steal(self):
414436 # Test PyListWriter_ExtendArraySteal()
415437 self .check_extend ('extend_array_steal' )
416438
439+ def test_empty (self ):
440+ list_get_allocated = _testcapi ._list_get_allocated
441+ empty_tuple = ()
442+
443+ for size in (0 , LIST_SMALL_ALLOCATED , 1024 ):
444+ with self .subTest (size = size ):
445+ writer = self .create_writer (size )
446+ writer .extend (empty_tuple )
447+ writer .extend_array (empty_tuple )
448+ writer .extend_array_steal (empty_tuple )
449+ result = writer .finish ()
450+ self .list_equal (result , [])
451+
452+ if size == 0 or size > LIST_SMALL_ALLOCATED :
453+ expected = 0
454+ else :
455+ expected = size
456+ self .assertEqual (list_get_allocated (result ), expected )
457+
458+ def test_finish_shrink (self ):
459+ # PyListWriter_Finish() shrinks the list if its allocated size
460+ # is way too big
461+ list_get_allocated = _testcapi ._list_get_allocated
462+
463+ def compute_allocated (size ):
464+ return (size + (size >> 3 ) + 6 ) & ~ 3
465+
466+ def check_allocated (items , alloc , expected ):
467+ writer = self .create_writer (alloc )
468+ # Don't use PyListWriter_Extend() since it can shrink the list
469+ for item in items :
470+ writer .append (item )
471+ result = writer .finish ()
472+ self .list_equal (result , list (items ))
473+ self .assertEqual (list_get_allocated (result ), expected )
474+
475+ # small list
476+ items = [1 , 2 , 3 ]
477+ check_allocated (items ,
478+ 1024 ,
479+ compute_allocated (len (items )))
480+
481+ alloc = LIST_SMALL_ALLOCATED
482+ check_allocated (items , alloc , alloc )
483+
484+ # larger list
485+ items = tuple (range (100 ))
486+ check_allocated (items ,
487+ (len (items ) + 1 ) * 2 ,
488+ compute_allocated (len (items )))
489+
490+ alloc = len (items ) * 2
491+ check_allocated (items , alloc , alloc )
492+
493+ # do not shrink on MemoryError
494+ alloc = 1024
495+ writer = self .create_writer (alloc )
496+ writer .append (1 )
497+ with support .inject_memory_error_cm ():
498+ # list_resize() never fails when shrinking
499+ result = writer .finish ()
500+ self .list_equal (result , [1 ])
501+ self .assertEqual (list_get_allocated (result ), alloc )
502+
417503 def test_discard (self ):
418504 # Test PyListWriter_Discard(): must not leak memory
419- writer = self .create_writer ()
505+ writer = self .create_writer (0 )
420506 writer .extend ([2 , 5 , 7 ])
421507 writer .discard ()
422508
423509 @threading_helper .requires_working_threading ()
424510 def test_thread (self ):
425511 # PyListWriter can be used by multiple threads: it's up to the caller
426512 # to implement a lock to prevent concurrent accesses.
427- writer = self .create_writer ()
513+ writer = self .create_writer (0 )
428514
429515 def thread_func (writer ):
430- writer .append ('Hello' )
431- writer .extend (['World!' ])
516+ writer .append ('a' )
517+ writer .append_steal ('b' )
518+ writer .extend (('c' ,))
519+ writer .extend_array (('d' ,))
520+ writer .extend_array_steal (('e' ,))
432521
433522 thread = threading .Thread (target = thread_func , args = (writer ,))
434523 thread .start ()
435524 threading_helper .join_thread (thread )
436525
437- self .list_equal (writer .finish (), [ 'Hello' , 'World!' ] )
526+ self .list_equal (writer .finish (), list ( 'abcde' ) )
438527
439528
440529if __name__ == "__main__" :
0 commit comments