@@ -2683,16 +2683,71 @@ def test_extract_command(self):
26832683 self .assertEqual (f .read (), zf .read (zi ))
26842684
26852685
2686+ class TestExecutablePrependedZip (unittest .TestCase ):
2687+ """Test our ability to open zip files with an executable prepended."""
2688+
2689+ def setUp (self ):
2690+ self .exe_zip = findfile ('exe_with_zip' , subdir = 'ziptestdata' )
2691+ self .exe_zip64 = findfile ('exe_with_z64' , subdir = 'ziptestdata' )
2692+
2693+ def _test_zip_works (self , name ):
2694+ # bpo28494 sanity check: ensure is_zipfile works on these.
2695+ self .assertTrue (zipfile .is_zipfile (name ),
2696+ f'is_zipfile failed on { name } ' )
2697+ # Ensure we can operate on these via ZipFile.
2698+ with zipfile .ZipFile (name ) as zipfp :
2699+ for n in zipfp .namelist ():
2700+ data = zipfp .read (n )
2701+ self .assertIn (b'FAVORITE_NUMBER' , data )
2702+
2703+ def test_read_zip_with_exe_prepended (self ):
2704+ self ._test_zip_works (self .exe_zip )
2705+
2706+ def test_read_zip64_with_exe_prepended (self ):
2707+ self ._test_zip_works (self .exe_zip64 )
2708+
2709+ @unittest .skipUnless (sys .executable , 'sys.executable required.' )
2710+ @unittest .skipUnless (os .access ('/bin/bash' , os .X_OK ),
2711+ 'Test relies on #!/bin/bash working.' )
2712+ def test_execute_zip2 (self ):
2713+ output = subprocess .check_output ([self .exe_zip , sys .executable ])
2714+ self .assertIn (b'number in executable: 5' , output )
2715+
2716+ @unittest .skipUnless (sys .executable , 'sys.executable required.' )
2717+ @unittest .skipUnless (os .access ('/bin/bash' , os .X_OK ),
2718+ 'Test relies on #!/bin/bash working.' )
2719+ def test_execute_zip64 (self ):
2720+ output = subprocess .check_output ([self .exe_zip64 , sys .executable ])
2721+ self .assertIn (b'number in executable: 5' , output )
2722+
2723+
26862724# Poor man's technique to consume a (smallish) iterable.
26872725consume = tuple
26882726
26892727
2728+ # from jaraco.itertools 5.0
2729+ class jaraco :
2730+ class itertools :
2731+ class Counter :
2732+ def __init__ (self , i ):
2733+ self .count = 0
2734+ self ._orig_iter = iter (i )
2735+
2736+ def __iter__ (self ):
2737+ return self
2738+
2739+ def __next__ (self ):
2740+ result = next (self ._orig_iter )
2741+ self .count += 1
2742+ return result
2743+
2744+
26902745def add_dirs (zf ):
26912746 """
26922747 Given a writable zip file zf, inject directory entries for
26932748 any directories implied by the presence of children.
26942749 """
2695- for name in zipfile .Path ._implied_dirs (zf .namelist ()):
2750+ for name in zipfile .CompleteDirs ._implied_dirs (zf .namelist ()):
26962751 zf .writestr (name , b"" )
26972752 return zf
26982753
@@ -2733,44 +2788,6 @@ def build_alpharep_fixture():
27332788 return zf
27342789
27352790
2736- class TestExecutablePrependedZip (unittest .TestCase ):
2737- """Test our ability to open zip files with an executable prepended."""
2738-
2739- def setUp (self ):
2740- self .exe_zip = findfile ('exe_with_zip' , subdir = 'ziptestdata' )
2741- self .exe_zip64 = findfile ('exe_with_z64' , subdir = 'ziptestdata' )
2742-
2743- def _test_zip_works (self , name ):
2744- # bpo-28494 sanity check: ensure is_zipfile works on these.
2745- self .assertTrue (zipfile .is_zipfile (name ),
2746- f'is_zipfile failed on { name } ' )
2747- # Ensure we can operate on these via ZipFile.
2748- with zipfile .ZipFile (name ) as zipfp :
2749- for n in zipfp .namelist ():
2750- data = zipfp .read (n )
2751- self .assertIn (b'FAVORITE_NUMBER' , data )
2752-
2753- def test_read_zip_with_exe_prepended (self ):
2754- self ._test_zip_works (self .exe_zip )
2755-
2756- def test_read_zip64_with_exe_prepended (self ):
2757- self ._test_zip_works (self .exe_zip64 )
2758-
2759- @unittest .skipUnless (sys .executable , 'sys.executable required.' )
2760- @unittest .skipUnless (os .access ('/bin/bash' , os .X_OK ),
2761- 'Test relies on #!/bin/bash working.' )
2762- def test_execute_zip2 (self ):
2763- output = subprocess .check_output ([self .exe_zip , sys .executable ])
2764- self .assertIn (b'number in executable: 5' , output )
2765-
2766- @unittest .skipUnless (sys .executable , 'sys.executable required.' )
2767- @unittest .skipUnless (os .access ('/bin/bash' , os .X_OK ),
2768- 'Test relies on #!/bin/bash working.' )
2769- def test_execute_zip64 (self ):
2770- output = subprocess .check_output ([self .exe_zip64 , sys .executable ])
2771- self .assertIn (b'number in executable: 5' , output )
2772-
2773-
27742791class TestPath (unittest .TestCase ):
27752792 def setUp (self ):
27762793 self .fixtures = contextlib .ExitStack ()
@@ -2808,6 +2825,14 @@ def test_iterdir_and_types(self):
28082825 i , = h .iterdir ()
28092826 assert i .is_file ()
28102827
2828+ def test_subdir_is_dir (self ):
2829+ for alpharep in self .zipfile_alpharep ():
2830+ root = zipfile .Path (alpharep )
2831+ assert (root / 'b' ).is_dir ()
2832+ assert (root / 'b/' ).is_dir ()
2833+ assert (root / 'g' ).is_dir ()
2834+ assert (root / 'g/' ).is_dir ()
2835+
28112836 def test_open (self ):
28122837 for alpharep in self .zipfile_alpharep ():
28132838 root = zipfile .Path (alpharep )
@@ -2869,6 +2894,45 @@ def test_missing_dir_parent(self):
28692894 root = zipfile .Path (alpharep )
28702895 assert (root / 'missing dir/' ).parent .at == ''
28712896
2897+ def test_mutability (self ):
2898+ """
2899+ If the underlying zipfile is changed, the Path object should
2900+ reflect that change.
2901+ """
2902+ for alpharep in self .zipfile_alpharep ():
2903+ root = zipfile .Path (alpharep )
2904+ a , b , g = root .iterdir ()
2905+ alpharep .writestr ('foo.txt' , 'foo' )
2906+ alpharep .writestr ('bar/baz.txt' , 'baz' )
2907+ assert any (
2908+ child .name == 'foo.txt'
2909+ for child in root .iterdir ())
2910+ assert (root / 'foo.txt' ).read_text () == 'foo'
2911+ baz , = (root / 'bar' ).iterdir ()
2912+ assert baz .read_text () == 'baz'
2913+
2914+ HUGE_ZIPFILE_NUM_ENTRIES = 2 ** 13
2915+
2916+ def huge_zipfile (self ):
2917+ """Create a read-only zipfile with a huge number of entries entries."""
2918+ strm = io .BytesIO ()
2919+ zf = zipfile .ZipFile (strm , "w" )
2920+ for entry in map (str , range (self .HUGE_ZIPFILE_NUM_ENTRIES )):
2921+ zf .writestr (entry , entry )
2922+ zf .mode = 'r'
2923+ return zf
2924+
2925+ def test_joinpath_constant_time (self ):
2926+ """
2927+ Ensure joinpath on items in zipfile is linear time.
2928+ """
2929+ root = zipfile .Path (self .huge_zipfile ())
2930+ entries = jaraco .itertools .Counter (root .iterdir ())
2931+ for entry in entries :
2932+ entry .joinpath ('suffix' )
2933+ # Check the file iterated all items
2934+ assert entries .count == self .HUGE_ZIPFILE_NUM_ENTRIES
2935+
28722936
28732937if __name__ == "__main__" :
28742938 unittest .main ()
0 commit comments