Repository navigation
Expand file tree
/
Copy pathtest_zipfile64.py
More file actions
278 lines (235 loc) · 11.8 KB
/
Copy pathtest_zipfile64.py
File metadata and controls
278 lines (235 loc) · 11.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
# Tests of the full ZIP64 functionality of zipfile
# The support.requires call is the only reason for keeping this separate
# from test_zipfile
from test import support
# XXX(nnorwitz): disable this test by looking for extralargefile resource,
# which doesn't exist. This test takes over 30 minutes to run in general
# and requires more disk space than most of the buildbots.
support.requires(
'extralargefile',
'test requires loads of disk-space bytes and a long time to run'
)
import zipfile, unittest
import time
import tracemalloc
import sys
import unittest.mock as mock
from contextlib import contextmanager
from tempfile import TemporaryFile
from test.support import os_helper
from test.support import requires_zlib
from test.test_zipfile.test_core import Unseekable
from test.test_zipfile.test_core import struct_pack_no_dd_sig
TESTFN = os_helper.TESTFN
TESTFN2 = TESTFN + "2"
# How much time in seconds can pass before we print a 'Still working' message.
_PRINT_WORKING_MSG_INTERVAL = 60
class TestsWithSourceFile(unittest.TestCase):
def setUp(self):
# Create test data.
line_gen = ("Test of zipfile line %d." % i for i in range(1000000))
self.data = '\n'.join(line_gen).encode('ascii')
def zipTest(self, f, compression):
# Create the ZIP archive.
with zipfile.ZipFile(f, "w", compression) as zipfp:
# It will contain enough copies of self.data to reach about 6 GiB of
# raw data to store.
filecount = 6*1024**3 // len(self.data)
next_time = time.monotonic() + _PRINT_WORKING_MSG_INTERVAL
for num in range(filecount):
zipfp.writestr("testfn%d" % num, self.data)
# Print still working message since this test can be really slow
if next_time <= time.monotonic():
next_time = time.monotonic() + _PRINT_WORKING_MSG_INTERVAL
print((
' zipTest still writing %d of %d, be patient...' %
(num, filecount)), file=sys.__stdout__)
sys.__stdout__.flush()
# Read the ZIP archive
with zipfile.ZipFile(f, "r", compression) as zipfp:
for num in range(filecount):
self.assertEqual(zipfp.read("testfn%d" % num), self.data)
# Print still working message since this test can be really slow
if next_time <= time.monotonic():
next_time = time.monotonic() + _PRINT_WORKING_MSG_INTERVAL
print((
' zipTest still reading %d of %d, be patient...' %
(num, filecount)), file=sys.__stdout__)
sys.__stdout__.flush()
# Check that testzip thinks the archive is valid
self.assertIsNone(zipfp.testzip())
def testStored(self):
# Try the temp file first. If we do TESTFN2 first, then it hogs
# gigabytes of disk space for the duration of the test.
with TemporaryFile() as f:
self.zipTest(f, zipfile.ZIP_STORED)
self.assertFalse(f.closed)
self.zipTest(TESTFN2, zipfile.ZIP_STORED)
@requires_zlib()
def testDeflated(self):
# Try the temp file first. If we do TESTFN2 first, then it hogs
# gigabytes of disk space for the duration of the test.
with TemporaryFile() as f:
self.zipTest(f, zipfile.ZIP_DEFLATED)
self.assertFalse(f.closed)
self.zipTest(TESTFN2, zipfile.ZIP_DEFLATED)
def tearDown(self):
os_helper.unlink(TESTFN2)
class TestRepacker(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.largefilename = 'largefile.txt'
line_gen = ("Test of zipfile line %d." % i for i in range(1000000))
cls.chunk = '\n'.join(line_gen).encode('ascii')
# It will contain enough copies of cls.chunk to reach about 4.1 GiB.
cls.chunkcount = int(4.1*1024**3 / len(cls.chunk))
cls.filename = 'file.txt'
cls.lorem = b'Sed ut perspiciatis unde omnis iste natus error sit voluptatem'
# Memory usage should not exceed 10 MiB during repacking.
# This empirical threshold ensures that the internal processing
# like signature scanning, compressed block end tracing, and
# data copying are properly buffered without loading the entire
# large file into memory.
cls.allowed_memory = 10*1024**2
@contextmanager
def assert_memory_usage(self, threshold):
tracemalloc.start()
try:
yield
finally:
current, peak = tracemalloc.get_traced_memory()
tracemalloc.stop()
self.assertLess(peak, threshold)
def _write_large_file(self, fh):
next_time = time.monotonic() + _PRINT_WORKING_MSG_INTERVAL
for num in range(self.chunkcount):
fh.write(self.chunk)
# Print still working message since this test can be really slow
if next_time <= time.monotonic():
next_time = time.monotonic() + _PRINT_WORKING_MSG_INTERVAL
print((
' writing %d of %d, be patient...' %
(num, self.chunkcount)), file=sys.__stdout__)
sys.__stdout__.flush()
def test_strip_removed_large_file(self):
"""Should move the physical data of a file positioned after a large
removed file without causing a memory issue."""
with TemporaryFile() as f:
with zipfile.ZipFile(f, 'w') as zh:
with zh.open(self.largefilename, 'w', force_zip64=True) as fh:
self._write_large_file(fh)
zh.writestr(self.filename, self.lorem)
with self.assert_memory_usage(self.allowed_memory), \
zipfile.ZipFile(f, 'a') as zh:
zh.remove(self.largefilename)
zh.repack()
self.assertIsNone(zh.testzip())
def test_strip_removed_file_before_large_file(self):
"""Should move the physical data of a large file positioned after a
removed file without causing a memory issue."""
with TemporaryFile() as f:
with zipfile.ZipFile(f, 'w') as zh:
zh.writestr(self.filename, self.lorem)
with zh.open(self.largefilename, 'w', force_zip64=True) as fh:
self._write_large_file(fh)
with self.assert_memory_usage(self.allowed_memory), \
zipfile.ZipFile(f, 'a') as zh:
zh.remove(self.filename)
zh.repack()
self.assertIsNone(zh.testzip())
def test_strip_removed_large_file_with_dd(self):
"""Should scan for the data descriptor of a removed large file without
causing a memory issue."""
with TemporaryFile() as f:
with zipfile.ZipFile(Unseekable(f), 'w') as zh:
with zh.open(self.largefilename, 'w', force_zip64=True) as fh:
self._write_large_file(fh)
zh.writestr(self.filename, self.lorem)
with self.assert_memory_usage(self.allowed_memory), \
zipfile.ZipFile(f, 'a') as zh:
zh.remove(self.largefilename)
zh.repack()
self.assertIsNone(zh.testzip())
def test_strip_removed_large_file_with_dd_no_sig(self):
"""Should scan for the unsigned data descriptor of a removed large file
without causing a memory issue."""
# Reduce data scale for this test, as it's especially slow...
self.chunkcount = int(30*1024**2 / len(self.chunk))
with TemporaryFile() as f:
with mock.patch('zipfile.struct.pack', side_effect=struct_pack_no_dd_sig), \
zipfile.ZipFile(Unseekable(f), 'w') as zh:
with zh.open(self.largefilename, 'w', force_zip64=True) as fh:
self._write_large_file(fh)
zh.writestr(self.filename, self.lorem)
with self.assert_memory_usage(self.allowed_memory), \
zipfile.ZipFile(f, 'a') as zh:
zh.remove(self.largefilename)
# strict_descriptor=False to scan the unsigned data descriptor
# (scanning is disabled under the strict_descriptor=True default)
zh.repack(strict_descriptor=False)
self.assertIsNone(zh.testzip())
@requires_zlib()
def test_strip_removed_large_file_with_dd_no_sig_by_decompression(self):
"""Should scan for the unsigned data descriptor (via tracing compressed
block end) of a removed large file without causing a memory issue."""
with TemporaryFile() as f:
with mock.patch('zipfile.struct.pack', side_effect=struct_pack_no_dd_sig), \
zipfile.ZipFile(Unseekable(f), 'w', compression=zipfile.ZIP_DEFLATED) as zh:
with zh.open(self.largefilename, 'w', force_zip64=True) as fh:
self._write_large_file(fh)
zh.writestr(self.filename, self.lorem)
with self.assert_memory_usage(self.allowed_memory), \
zipfile.ZipFile(f, 'a') as zh:
zh.remove(self.largefilename)
# strict_descriptor=False to detect the unsigned data descriptor
# (scanning is disabled under the strict_descriptor=True default)
zh.repack(strict_descriptor=False)
self.assertIsNone(zh.testzip())
class OtherTests(unittest.TestCase):
def testMoreThan64kFiles(self):
# This test checks that more than 64k files can be added to an archive,
# and that the resulting archive can be read properly by ZipFile
with zipfile.ZipFile(TESTFN, mode="w", allowZip64=True) as zipf:
zipf.debug = 100
numfiles = (1 << 16) * 3//2
for i in range(numfiles):
zipf.writestr("foo%08d" % i, "%d" % (i**3 % 57))
self.assertEqual(len(zipf.namelist()), numfiles)
with zipfile.ZipFile(TESTFN, mode="r") as zipf2:
self.assertEqual(len(zipf2.namelist()), numfiles)
for i in range(numfiles):
content = zipf2.read("foo%08d" % i).decode('ascii')
self.assertEqual(content, "%d" % (i**3 % 57))
def testMoreThan64kFilesAppend(self):
with zipfile.ZipFile(TESTFN, mode="w", allowZip64=False) as zipf:
zipf.debug = 100
numfiles = (1 << 16) - 1
for i in range(numfiles):
zipf.writestr("foo%08d" % i, "%d" % (i**3 % 57))
self.assertEqual(len(zipf.namelist()), numfiles)
with self.assertRaises(zipfile.LargeZipFile):
zipf.writestr("foo%08d" % numfiles, b'')
self.assertEqual(len(zipf.namelist()), numfiles)
with zipfile.ZipFile(TESTFN, mode="a", allowZip64=False) as zipf:
zipf.debug = 100
self.assertEqual(len(zipf.namelist()), numfiles)
with self.assertRaises(zipfile.LargeZipFile):
zipf.writestr("foo%08d" % numfiles, b'')
self.assertEqual(len(zipf.namelist()), numfiles)
with zipfile.ZipFile(TESTFN, mode="a", allowZip64=True) as zipf:
zipf.debug = 100
self.assertEqual(len(zipf.namelist()), numfiles)
numfiles2 = (1 << 16) * 3//2
for i in range(numfiles, numfiles2):
zipf.writestr("foo%08d" % i, "%d" % (i**3 % 57))
self.assertEqual(len(zipf.namelist()), numfiles2)
with zipfile.ZipFile(TESTFN, mode="r") as zipf2:
self.assertEqual(len(zipf2.namelist()), numfiles2)
for i in range(numfiles2):
content = zipf2.read("foo%08d" % i).decode('ascii')
self.assertEqual(content, "%d" % (i**3 % 57))
def tearDown(self):
os_helper.unlink(TESTFN)
os_helper.unlink(TESTFN2)
if __name__ == "__main__":
unittest.main()