@@ -2724,16 +2724,71 @@ def test_extract_command(self):
27242724 self .assertEqual (f .read (), zf .read (zi ))
27252725
27262726
2727+ class TestExecutablePrependedZip (unittest .TestCase ):
2728+ """Test our ability to open zip files with an executable prepended."""
2729+
2730+ def setUp (self ):
2731+ self .exe_zip = findfile ('exe_with_zip' , subdir = 'ziptestdata' )
2732+ self .exe_zip64 = findfile ('exe_with_z64' , subdir = 'ziptestdata' )
2733+
2734+ def _test_zip_works (self , name ):
2735+ # bpo28494 sanity check: ensure is_zipfile works on these.
2736+ self .assertTrue (zipfile .is_zipfile (name ),
2737+ f'is_zipfile failed on { name } ' )
2738+ # Ensure we can operate on these via ZipFile.
2739+ with zipfile .ZipFile (name ) as zipfp :
2740+ for n in zipfp .namelist ():
2741+ data = zipfp .read (n )
2742+ self .assertIn (b'FAVORITE_NUMBER' , data )
2743+
2744+ def test_read_zip_with_exe_prepended (self ):
2745+ self ._test_zip_works (self .exe_zip )
2746+
2747+ def test_read_zip64_with_exe_prepended (self ):
2748+ self ._test_zip_works (self .exe_zip64 )
2749+
2750+ @unittest .skipUnless (sys .executable , 'sys.executable required.' )
2751+ @unittest .skipUnless (os .access ('/bin/bash' , os .X_OK ),
2752+ 'Test relies on #!/bin/bash working.' )
2753+ def test_execute_zip2 (self ):
2754+ output = subprocess .check_output ([self .exe_zip , sys .executable ])
2755+ self .assertIn (b'number in executable: 5' , output )
2756+
2757+ @unittest .skipUnless (sys .executable , 'sys.executable required.' )
2758+ @unittest .skipUnless (os .access ('/bin/bash' , os .X_OK ),
2759+ 'Test relies on #!/bin/bash working.' )
2760+ def test_execute_zip64 (self ):
2761+ output = subprocess .check_output ([self .exe_zip64 , sys .executable ])
2762+ self .assertIn (b'number in executable: 5' , output )
2763+
2764+
27272765# Poor man's technique to consume a (smallish) iterable.
27282766consume = tuple
27292767
27302768
2769+ # from jaraco.itertools 5.0
2770+ class jaraco :
2771+ class itertools :
2772+ class Counter :
2773+ def __init__ (self , i ):
2774+ self .count = 0
2775+ self ._orig_iter = iter (i )
2776+
2777+ def __iter__ (self ):
2778+ return self
2779+
2780+ def __next__ (self ):
2781+ result = next (self ._orig_iter )
2782+ self .count += 1
2783+ return result
2784+
2785+
27312786def add_dirs (zf ):
27322787 """
27332788 Given a writable zip file zf, inject directory entries for
27342789 any directories implied by the presence of children.
27352790 """
2736- for name in zipfile .Path ._implied_dirs (zf .namelist ()):
2791+ for name in zipfile .CompleteDirs ._implied_dirs (zf .namelist ()):
27372792 zf .writestr (name , b"" )
27382793 return zf
27392794
@@ -2774,44 +2829,6 @@ def build_alpharep_fixture():
27742829 return zf
27752830
27762831
2777- class TestExecutablePrependedZip (unittest .TestCase ):
2778- """Test our ability to open zip files with an executable prepended."""
2779-
2780- def setUp (self ):
2781- self .exe_zip = findfile ('exe_with_zip' , subdir = 'ziptestdata' )
2782- self .exe_zip64 = findfile ('exe_with_z64' , subdir = 'ziptestdata' )
2783-
2784- def _test_zip_works (self , name ):
2785- # bpo-28494 sanity check: ensure is_zipfile works on these.
2786- self .assertTrue (zipfile .is_zipfile (name ),
2787- f'is_zipfile failed on { name } ' )
2788- # Ensure we can operate on these via ZipFile.
2789- with zipfile .ZipFile (name ) as zipfp :
2790- for n in zipfp .namelist ():
2791- data = zipfp .read (n )
2792- self .assertIn (b'FAVORITE_NUMBER' , data )
2793-
2794- def test_read_zip_with_exe_prepended (self ):
2795- self ._test_zip_works (self .exe_zip )
2796-
2797- def test_read_zip64_with_exe_prepended (self ):
2798- self ._test_zip_works (self .exe_zip64 )
2799-
2800- @unittest .skipUnless (sys .executable , 'sys.executable required.' )
2801- @unittest .skipUnless (os .access ('/bin/bash' , os .X_OK ),
2802- 'Test relies on #!/bin/bash working.' )
2803- def test_execute_zip2 (self ):
2804- output = subprocess .check_output ([self .exe_zip , sys .executable ])
2805- self .assertIn (b'number in executable: 5' , output )
2806-
2807- @unittest .skipUnless (sys .executable , 'sys.executable required.' )
2808- @unittest .skipUnless (os .access ('/bin/bash' , os .X_OK ),
2809- 'Test relies on #!/bin/bash working.' )
2810- def test_execute_zip64 (self ):
2811- output = subprocess .check_output ([self .exe_zip64 , sys .executable ])
2812- self .assertIn (b'number in executable: 5' , output )
2813-
2814-
28152832class TestPath (unittest .TestCase ):
28162833 def setUp (self ):
28172834 self .fixtures = contextlib .ExitStack ()
@@ -2849,6 +2866,14 @@ def test_iterdir_and_types(self):
28492866 i , = h .iterdir ()
28502867 assert i .is_file ()
28512868
2869+ def test_subdir_is_dir (self ):
2870+ for alpharep in self .zipfile_alpharep ():
2871+ root = zipfile .Path (alpharep )
2872+ assert (root / 'b' ).is_dir ()
2873+ assert (root / 'b/' ).is_dir ()
2874+ assert (root / 'g' ).is_dir ()
2875+ assert (root / 'g/' ).is_dir ()
2876+
28522877 def test_open (self ):
28532878 for alpharep in self .zipfile_alpharep ():
28542879 root = zipfile .Path (alpharep )
@@ -2910,6 +2935,45 @@ def test_missing_dir_parent(self):
29102935 root = zipfile .Path (alpharep )
29112936 assert (root / 'missing dir/' ).parent .at == ''
29122937
2938+ def test_mutability (self ):
2939+ """
2940+ If the underlying zipfile is changed, the Path object should
2941+ reflect that change.
2942+ """
2943+ for alpharep in self .zipfile_alpharep ():
2944+ root = zipfile .Path (alpharep )
2945+ a , b , g = root .iterdir ()
2946+ alpharep .writestr ('foo.txt' , 'foo' )
2947+ alpharep .writestr ('bar/baz.txt' , 'baz' )
2948+ assert any (
2949+ child .name == 'foo.txt'
2950+ for child in root .iterdir ())
2951+ assert (root / 'foo.txt' ).read_text () == 'foo'
2952+ baz , = (root / 'bar' ).iterdir ()
2953+ assert baz .read_text () == 'baz'
2954+
2955+ HUGE_ZIPFILE_NUM_ENTRIES = 2 ** 13
2956+
2957+ def huge_zipfile (self ):
2958+ """Create a read-only zipfile with a huge number of entries entries."""
2959+ strm = io .BytesIO ()
2960+ zf = zipfile .ZipFile (strm , "w" )
2961+ for entry in map (str , range (self .HUGE_ZIPFILE_NUM_ENTRIES )):
2962+ zf .writestr (entry , entry )
2963+ zf .mode = 'r'
2964+ return zf
2965+
2966+ def test_joinpath_constant_time (self ):
2967+ """
2968+ Ensure joinpath on items in zipfile is linear time.
2969+ """
2970+ root = zipfile .Path (self .huge_zipfile ())
2971+ entries = jaraco .itertools .Counter (root .iterdir ())
2972+ for entry in entries :
2973+ entry .joinpath ('suffix' )
2974+ # Check the file iterated all items
2975+ assert entries .count == self .HUGE_ZIPFILE_NUM_ENTRIES
2976+
29132977
29142978if __name__ == "__main__" :
29152979 unittest .main ()
0 commit comments