|
5 | 5 | import itertools
|
6 | 6 | import os
|
7 | 7 | import posixpath
|
| 8 | +import stat |
8 | 9 | import struct
|
9 | 10 | import subprocess
|
10 | 11 | import sys
|
|
15 | 16 |
|
16 | 17 |
|
17 | 18 | from tempfile import TemporaryFile
|
| 19 | +from test.support import os_helper |
18 | 20 | from random import randint, random, randbytes
|
19 | 21 |
|
20 | 22 | from test import archiver_tests
|
@@ -1781,19 +1783,20 @@ def test_writestr_extended_local_header_issue1202(self):
|
1781 | 1783 | orig_zip.writestr(zinfo, data)
|
1782 | 1784 |
|
1783 | 1785 | def test_write_with_source_date_epoch(self):
|
1784 |
| - # Set the SOURCE_DATE_EPOCH environment variable to a specific timestamp |
1785 |
| - os.environ['SOURCE_DATE_EPOCH'] = "1727440508" |
| 1786 | + with os_helper.EnvironmentVarGuard() as env: |
| 1787 | + # Set the SOURCE_DATE_EPOCH environment variable to a specific timestamp |
| 1788 | + env['SOURCE_DATE_EPOCH'] = "1727440508" |
1786 | 1789 |
|
1787 |
| - with zipfile.ZipFile(TESTFN, "w") as zf: |
1788 |
| - zf.writestr("test_source_date_epoch.txt", "Testing SOURCE_DATE_EPOCH") |
| 1790 | + with zipfile.ZipFile(TESTFN, "w") as zf: |
| 1791 | + zf.writestr("test_source_date_epoch.txt", "Testing SOURCE_DATE_EPOCH") |
1789 | 1792 |
|
1790 |
| - with zipfile.ZipFile(TESTFN, "r") as zf: |
1791 |
| - zip_info = zf.getinfo("test_source_date_epoch.txt") |
1792 |
| - get_time = time.gmtime(int(os.environ['SOURCE_DATE_EPOCH']))[:6] |
1793 |
| - # Compare each element of the date_time tuple |
1794 |
| - # Allow for a 1-second difference |
1795 |
| - for z_time, g_time in zip(zip_info.date_time, get_time): |
1796 |
| - self.assertAlmostEqual(z_time, g_time, delta=1) |
| 1793 | + with zipfile.ZipFile(TESTFN, "r") as zf: |
| 1794 | + zip_info = zf.getinfo("test_source_date_epoch.txt") |
| 1795 | + get_time = time.gmtime(int(os.environ['SOURCE_DATE_EPOCH']))[:6] |
| 1796 | + # Compare each element of the date_time tuple |
| 1797 | + # Allow for a 1-second difference |
| 1798 | + for z_time, g_time in zip(zip_info.date_time, get_time): |
| 1799 | + self.assertAlmostEqual(z_time, g_time, delta=1) |
1797 | 1800 |
|
1798 | 1801 | def test_write_without_source_date_epoch(self):
|
1799 | 1802 | if 'SOURCE_DATE_EPOCH' in os.environ:
|
@@ -1997,10 +2000,16 @@ def test_is_zip_valid_file(self):
|
1997 | 2000 | zip_contents = fp.read()
|
1998 | 2001 | # - passing a file-like object
|
1999 | 2002 | fp = io.BytesIO()
|
2000 |
| - fp.write(zip_contents) |
| 2003 | + end = fp.write(zip_contents) |
| 2004 | + self.assertEqual(fp.tell(), end) |
| 2005 | + mid = end // 2 |
| 2006 | + fp.seek(mid, 0) |
2001 | 2007 | self.assertTrue(zipfile.is_zipfile(fp))
|
2002 |
| - fp.seek(0, 0) |
| 2008 | + # check that the position is left unchanged after the call |
| 2009 | + # see: https://github.com/python/cpython/issues/122356 |
| 2010 | + self.assertEqual(fp.tell(), mid) |
2003 | 2011 | self.assertTrue(zipfile.is_zipfile(fp))
|
| 2012 | + self.assertEqual(fp.tell(), mid) |
2004 | 2013 |
|
2005 | 2014 | def test_non_existent_file_raises_OSError(self):
|
2006 | 2015 | # make sure we don't raise an AttributeError when a partially-constructed
|
@@ -2233,6 +2242,34 @@ def test_create_empty_zipinfo_repr(self):
|
2233 | 2242 | zi = zipfile.ZipInfo(filename="empty")
|
2234 | 2243 | self.assertEqual(repr(zi), "<ZipInfo filename='empty' file_size=0>")
|
2235 | 2244 |
|
| 2245 | + def test_for_archive(self): |
| 2246 | + base_filename = TESTFN2.rstrip('/') |
| 2247 | + |
| 2248 | + with zipfile.ZipFile(TESTFN, mode="w", compresslevel=1, |
| 2249 | + compression=zipfile.ZIP_STORED) as zf: |
| 2250 | + # no trailing forward slash |
| 2251 | + zi = zipfile.ZipInfo(base_filename)._for_archive(zf) |
| 2252 | + self.assertEqual(zi.compress_level, 1) |
| 2253 | + self.assertEqual(zi.compress_type, zipfile.ZIP_STORED) |
| 2254 | + # ?rw- --- --- |
| 2255 | + filemode = stat.S_IRUSR | stat.S_IWUSR |
| 2256 | + # filemode is stored as the highest 16 bits of external_attr |
| 2257 | + self.assertEqual(zi.external_attr >> 16, filemode) |
| 2258 | + self.assertEqual(zi.external_attr & 0xFF, 0) # no MS-DOS flag |
| 2259 | + |
| 2260 | + with zipfile.ZipFile(TESTFN, mode="w", compresslevel=1, |
| 2261 | + compression=zipfile.ZIP_STORED) as zf: |
| 2262 | + # with a trailing slash |
| 2263 | + zi = zipfile.ZipInfo(f'{base_filename}/')._for_archive(zf) |
| 2264 | + self.assertEqual(zi.compress_level, 1) |
| 2265 | + self.assertEqual(zi.compress_type, zipfile.ZIP_STORED) |
| 2266 | + # d rwx rwx r-x |
| 2267 | + filemode = stat.S_IFDIR |
| 2268 | + filemode |= stat.S_IRWXU | stat.S_IRWXG |
| 2269 | + filemode |= stat.S_IROTH | stat.S_IXOTH |
| 2270 | + self.assertEqual(zi.external_attr >> 16, filemode) |
| 2271 | + self.assertEqual(zi.external_attr & 0xFF, 0x10) # MS-DOS flag |
| 2272 | + |
2236 | 2273 | def test_create_empty_zipinfo_default_attributes(self):
|
2237 | 2274 | """Ensure all required attributes are set."""
|
2238 | 2275 | zi = zipfile.ZipInfo()
|
@@ -2355,6 +2392,18 @@ def test_read_after_seek(self):
|
2355 | 2392 | fp.seek(1, os.SEEK_CUR)
|
2356 | 2393 | self.assertEqual(fp.read(-1), b'men!')
|
2357 | 2394 |
|
| 2395 | + def test_uncompressed_interleaved_seek_read(self): |
| 2396 | + # gh-127847: Make sure the position in the archive is correct |
| 2397 | + # in the special case of seeking in a ZIP_STORED entry. |
| 2398 | + with zipfile.ZipFile(TESTFN, "w") as zipf: |
| 2399 | + zipf.writestr("a.txt", "123") |
| 2400 | + zipf.writestr("b.txt", "456") |
| 2401 | + with zipfile.ZipFile(TESTFN, "r") as zipf: |
| 2402 | + with zipf.open("a.txt", "r") as a, zipf.open("b.txt", "r") as b: |
| 2403 | + self.assertEqual(a.read(1), b"1") |
| 2404 | + self.assertEqual(b.seek(1), 1) |
| 2405 | + self.assertEqual(b.read(1), b"5") |
| 2406 | + |
2358 | 2407 | @requires_bz2()
|
2359 | 2408 | def test_decompress_without_3rd_party_library(self):
|
2360 | 2409 | data = b'PK\x05\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
|
0 commit comments