|
| 1 | +import io |
| 2 | +import sys |
| 3 | +import unittest |
| 4 | +import zipfile |
| 5 | + |
| 6 | +# TODO: Find out which archiver can create ZSTD blobs |
| 7 | +null_zip_contents = ( |
| 8 | + ("null_bzip2.bin", 12, 4096, 46, (2026, 9, 4, 10, 1, 28), 0xC71C0011), |
| 9 | + ("null_deflate.bin", zipfile.ZIP_DEFLATED, 4096, 20, (2026, 9, 4, 10, 1, 32), 0xC71C0011), |
| 10 | + ("null_lzma.bin", 14, 4096, 41, (2026, 9, 4, 10, 1, 32), 0xC71C0011), |
| 11 | + ("null_stored.bin", zipfile.ZIP_STORED, 4096, 4096, (2026, 9, 4, 10, 1, 20), 0xC71C0011), |
| 12 | +) |
| 13 | + |
| 14 | +deflate_zip_contents = ( |
| 15 | + ("directory/", True, 0, 0, 0, None), |
| 16 | + ("directory/hello_again.txt", False, 6656, 66, 0x946F8D8C, b"Hello again, MicroPython!\n"), |
| 17 | + ("empty", False, 0, 0, 0, None), |
| 18 | + ("hello.txt", False, 5120, 57, 0xA89FD9B3, b"Hello, MicroPython!\n"), |
| 19 | +) |
| 20 | + |
| 21 | +stored_zip_contents = ( |
| 22 | + ("directory/", True, 0, 0, 0, None), |
| 23 | + ("directory/hello_again.txt", False, 6656, 6656, 0x946F8D8C, b"Hello again, MicroPython!\n"), |
| 24 | + ("empty", False, 0, 0, 0, None), |
| 25 | + ("hello.txt", False, 5120, 5120, 0xA89FD9B3, b"Hello, MicroPython!\n"), |
| 26 | +) |
| 27 | + |
| 28 | +TEST_NULL = "tests/null.zip" |
| 29 | +TEST_STORED = "tests/stored.zip" |
| 30 | +TEST_DEFLATED = "tests/deflated.zip" |
| 31 | + |
| 32 | +class TestZipFile(unittest.TestCase): |
| 33 | + def check_header(self, file, expected): |
| 34 | + name, method, uncompressed_size, compressed_size, timestamp, crc32 = expected |
| 35 | + self.assertEqual(name, file.filename) |
| 36 | + self.assertEqual(method, file.compress_type) |
| 37 | + self.assertEqual(uncompressed_size, file.file_size) |
| 38 | + self.assertEqual(compressed_size, file.compress_size) |
| 39 | + self.assertEqual(timestamp, file.date_time) |
| 40 | + self.assertEqual(crc32, file.CRC) |
| 41 | + |
| 42 | + def test_null_iter(self): |
| 43 | + zf = zipfile.ZipFile(TEST_NULL) |
| 44 | + for index, info in enumerate(zf.infolist()): |
| 45 | + self.check_header(info, null_zip_contents[index]) |
| 46 | + zf.close() |
| 47 | + |
| 48 | + def test_null_missing(self): |
| 49 | + with zipfile.ZipFile(TEST_NULL) as zf: |
| 50 | + with self.assertRaises(KeyError): |
| 51 | + zf.getinfo("missing.txt") |
| 52 | + |
| 53 | + def test_null_stored_read(self): |
| 54 | + zf = zipfile.ZipFile(TEST_NULL) |
| 55 | + expected = bytes(4096) |
| 56 | + for index, name in enumerate(zf.namelist()): |
| 57 | + self.assertEqual(name, null_zip_contents[index][0]) |
| 58 | + if null_zip_contents[index][1] != zipfile.ZIP_STORED: |
| 59 | + continue |
| 60 | + self.assertEqual(zf.read(name), expected) |
| 61 | + zf.close() |
| 62 | + |
| 63 | + def test_null_deflated_read(self): |
| 64 | + if sys.implementation.name == "micropython": |
| 65 | + try: |
| 66 | + import deflate |
| 67 | + except ImportError: |
| 68 | + self.skipTest("no deflate module") |
| 69 | + zf = zipfile.ZipFile(TEST_NULL) |
| 70 | + expected = bytes(4096) |
| 71 | + for index, name in enumerate(zf.namelist()): |
| 72 | + self.assertEqual(name, null_zip_contents[index][0]) |
| 73 | + if null_zip_contents[index][1] != zipfile.ZIP_DEFLATED: |
| 74 | + continue |
| 75 | + self.assertEqual(zf.read(name), expected) |
| 76 | + zf.close() |
| 77 | + |
| 78 | + def test_null_unsupported_read(self): |
| 79 | + if sys.implementation.name != "micropython": |
| 80 | + self.skipTest("cannot guarantee lack of methods support") |
| 81 | + zf = zipfile.ZipFile(TEST_NULL) |
| 82 | + for index, name in enumerate(zf.namelist()): |
| 83 | + self.assertEqual(name, null_zip_contents[index][0]) |
| 84 | + if null_zip_contents[index][1] not in (zipfile.ZIP_STORED, zipfile.ZIP_DEFLATED): |
| 85 | + with self.assertRaises(NotImplementedError): |
| 86 | + zf.read(name) |
| 87 | + zf.close() |
| 88 | + |
| 89 | + def test_null_stored_open(self): |
| 90 | + zf = zipfile.ZipFile(TEST_NULL) |
| 91 | + expected = bytes(4096) |
| 92 | + for index, name in enumerate(zf.namelist()): |
| 93 | + self.assertEqual(name, null_zip_contents[index][0]) |
| 94 | + if null_zip_contents[index][1] != zipfile.ZIP_STORED: |
| 95 | + continue |
| 96 | + with zf.open(name) as f: |
| 97 | + self.assertEqual(expected, f.read()) |
| 98 | + zf.close() |
| 99 | + |
| 100 | + def test_null_deflated_open(self): |
| 101 | + if sys.implementation.name == "micropython": |
| 102 | + try: |
| 103 | + import deflate |
| 104 | + except ImportError: |
| 105 | + self.skipTest("no deflate module") |
| 106 | + zf = zipfile.ZipFile(TEST_NULL) |
| 107 | + expected = bytes(4096) |
| 108 | + for index, name in enumerate(zf.namelist()): |
| 109 | + self.assertEqual(name, null_zip_contents[index][0]) |
| 110 | + if null_zip_contents[index][1] != zipfile.ZIP_DEFLATED: |
| 111 | + continue |
| 112 | + with zf.open(name) as f: |
| 113 | + self.assertEqual(expected, f.read()) |
| 114 | + zf.close() |
| 115 | + |
| 116 | + def test_null_unsupported_open(self): |
| 117 | + if sys.implementation.name != "micropython": |
| 118 | + self.skipTest("cannot guarantee lack of methods support") |
| 119 | + zf = zipfile.ZipFile(TEST_NULL) |
| 120 | + for index, name in enumerate(zf.namelist()): |
| 121 | + self.assertEqual(name, null_zip_contents[index][0]) |
| 122 | + if null_zip_contents[index][1] not in (zipfile.ZIP_STORED, zipfile.ZIP_DEFLATED): |
| 123 | + with self.assertRaises(NotImplementedError): |
| 124 | + with zf.open(name): |
| 125 | + self.fail() |
| 126 | + zf.close() |
| 127 | + |
| 128 | + def test_null_getinfo(self): |
| 129 | + zf = zipfile.ZipFile(TEST_NULL) |
| 130 | + for zc in null_zip_contents: |
| 131 | + info = zf.getinfo(zc[0]) |
| 132 | + self.check_header(info, zc) |
| 133 | + zf.close() |
| 134 | + |
| 135 | + def test_context(self): |
| 136 | + with zipfile.ZipFile(TEST_NULL) as zf: |
| 137 | + for index, info in enumerate(zf.infolist()): |
| 138 | + self.check_header(info, null_zip_contents[index]) |
| 139 | + |
| 140 | + def test_close(self): |
| 141 | + zf = zipfile.ZipFile(TEST_NULL) |
| 142 | + zf.close() |
| 143 | + zf.close() # check this doesn't raise |
| 144 | + |
| 145 | + # CPython doesn't raise as the archive directory is cached and not |
| 146 | + # flushed once `close` is called. `namelist` and `infolist` do not |
| 147 | + # raise on CPython. |
| 148 | + if sys.implementation.name == "micropython": |
| 149 | + with self.assertRaises(ValueError): |
| 150 | + zf.namelist() |
| 151 | + with self.assertRaises(ValueError): |
| 152 | + zf.infolist() |
| 153 | + with self.assertRaises(ValueError): |
| 154 | + zf.open(null_zip_contents[0][0]) |
| 155 | + with self.assertRaises(ValueError): |
| 156 | + with zf.open(null_zip_contents[0][0]): |
| 157 | + self.fail() |
| 158 | + with self.assertRaises(ValueError): |
| 159 | + zf.read(null_zip_contents[0][0]) |
| 160 | + |
| 161 | + def check_contents(self, zf, found, expected, method): |
| 162 | + self.assertEqual(found.is_dir(), expected[1]) |
| 163 | + self.assertEqual(found.file_size, expected[2]) |
| 164 | + self.assertEqual(found.compress_size, expected[3]) |
| 165 | + self.assertEqual(found.CRC, expected[4]) |
| 166 | + self.assertEqual(found.compress_type, zipfile.ZIP_STORED if expected[2] == 0 else method) |
| 167 | + if payload := expected[5]: |
| 168 | + data = payload * 256 |
| 169 | + with zf.open(expected[0]) as f: |
| 170 | + self.assertEqual(f.read(), data) |
| 171 | + self.assertEqual(zf.read(expected[0]), data) |
| 172 | + |
| 173 | + def test_read_deflate(self): |
| 174 | + if sys.implementation.name == "micropython": |
| 175 | + try: |
| 176 | + import deflate |
| 177 | + except ImportError: |
| 178 | + self.skipTest("no deflate module") |
| 179 | + with zipfile.ZipFile(TEST_DEFLATED) as zf: |
| 180 | + for file in deflate_zip_contents: |
| 181 | + info = zf.getinfo(file[0]) |
| 182 | + self.check_contents(zf, info, file, zipfile.ZIP_DEFLATED) |
| 183 | + |
| 184 | + def test_read_stored(self): |
| 185 | + with zipfile.ZipFile(TEST_STORED) as zf: |
| 186 | + for file in stored_zip_contents: |
| 187 | + info = zf.getinfo(file[0]) |
| 188 | + self.check_contents(zf, info, file, zipfile.ZIP_STORED) |
| 189 | + |
| 190 | + def test_is_zipfile(self): |
| 191 | + for n in (TEST_STORED, TEST_NULL, TEST_DEFLATED): |
| 192 | + self.assertEqual(zipfile.is_zipfile(n), True) |
| 193 | + with open(n, "rb") as f: |
| 194 | + old_offset = f.tell() |
| 195 | + self.assertEqual(zipfile.is_zipfile(f), True) |
| 196 | + self.assertEqual(f.tell(), old_offset) |
| 197 | + |
| 198 | + self.assertEqual(zipfile.is_zipfile("test_zipfile.py"), False) |
| 199 | + with open("test_zipfile.py", "rb") as f: |
| 200 | + old_offset = f.tell() |
| 201 | + self.assertEqual(zipfile.is_zipfile(f), False) |
| 202 | + self.assertEqual(f.tell(), old_offset) |
| 203 | + |
| 204 | + self.assertEqual(zipfile.is_zipfile("missing.zip"), False) |
| 205 | + self.assertEqual(zipfile.is_zipfile("zipfile"), False) |
| 206 | + |
| 207 | + with io.BytesIO() as empty: |
| 208 | + self.assertEqual(zipfile.is_zipfile(empty), False) |
0 commit comments