zipremove 0.4.0__tar.gz → 0.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {zipremove-0.4.0/src/zipremove.egg-info → zipremove-0.5.0}/PKG-INFO +2 -2
- {zipremove-0.4.0 → zipremove-0.5.0}/README.md +1 -1
- {zipremove-0.4.0 → zipremove-0.5.0}/setup.cfg +1 -1
- {zipremove-0.4.0 → zipremove-0.5.0}/src/zipremove/__init__.py +23 -32
- {zipremove-0.4.0 → zipremove-0.5.0/src/zipremove.egg-info}/PKG-INFO +2 -2
- {zipremove-0.4.0 → zipremove-0.5.0}/tests/test_zipfile.py +152 -76
- {zipremove-0.4.0 → zipremove-0.5.0}/tests/test_zipfile64.py +9 -6
- {zipremove-0.4.0 → zipremove-0.5.0}/LICENSE.txt +0 -0
- {zipremove-0.4.0 → zipremove-0.5.0}/pyproject.toml +0 -0
- {zipremove-0.4.0 → zipremove-0.5.0}/setup.py +0 -0
- {zipremove-0.4.0 → zipremove-0.5.0}/src/zipremove.egg-info/SOURCES.txt +0 -0
- {zipremove-0.4.0 → zipremove-0.5.0}/src/zipremove.egg-info/dependency_links.txt +0 -0
- {zipremove-0.4.0 → zipremove-0.5.0}/src/zipremove.egg-info/requires.txt +0 -0
- {zipremove-0.4.0 → zipremove-0.5.0}/src/zipremove.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: zipremove
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: Extend `zipfile` with `remove`-related functionalities
|
|
5
5
|
Home-page: https://github.com/danny0838/zipremove
|
|
6
6
|
Author: Danny Lin
|
|
@@ -52,7 +52,7 @@ This package extends `zipfile` with `remove`-related functionalities.
|
|
|
52
52
|
a path is provided.
|
|
53
53
|
|
|
54
54
|
This does not physically remove the local file entry from the archive.
|
|
55
|
-
Call `
|
|
55
|
+
Call `repack` afterwards to reclaim space.
|
|
56
56
|
|
|
57
57
|
The archive must be opened with mode ``'w'``, ``'x'`` or ``'a'``.
|
|
58
58
|
|
|
@@ -18,7 +18,7 @@ This package extends `zipfile` with `remove`-related functionalities.
|
|
|
18
18
|
a path is provided.
|
|
19
19
|
|
|
20
20
|
This does not physically remove the local file entry from the archive.
|
|
21
|
-
Call `
|
|
21
|
+
Call `repack` afterwards to reclaim space.
|
|
22
22
|
|
|
23
23
|
The archive must be opened with mode ``'w'``, ``'x'`` or ``'a'``.
|
|
24
24
|
|
|
@@ -82,7 +82,7 @@ class _ZipRepacker:
|
|
|
82
82
|
|
|
83
83
|
def copy(self, zfile, zinfo, filename):
|
|
84
84
|
# make a copy of zinfo
|
|
85
|
-
zinfo2 = copy.
|
|
85
|
+
zinfo2 = copy.copy(zinfo)
|
|
86
86
|
|
|
87
87
|
# apply sanitized new filename as in `ZipInfo.__init__`
|
|
88
88
|
zinfo2.orig_filename = filename
|
|
@@ -90,7 +90,7 @@ class _ZipRepacker:
|
|
|
90
90
|
|
|
91
91
|
zinfo2.header_offset = zfile.start_dir
|
|
92
92
|
|
|
93
|
-
# polyfill:
|
|
93
|
+
# polyfill: clear zinfo2._end_offset if exists
|
|
94
94
|
# (Python >= 3.8 with fix #109858)
|
|
95
95
|
if hasattr(zinfo2, '_end_offset'):
|
|
96
96
|
zinfo2._end_offset = None
|
|
@@ -113,10 +113,9 @@ class _ZipRepacker:
|
|
|
113
113
|
"""
|
|
114
114
|
Repack the ZIP file, stripping unreferenced local file entries.
|
|
115
115
|
|
|
116
|
-
Assumes that local file entries
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
Behavior:
|
|
116
|
+
Assumes that local file entries (and the central directory, which is
|
|
117
|
+
mostly treated as the "last entry") are stored consecutively, with no
|
|
118
|
+
gaps or overlaps:
|
|
120
119
|
|
|
121
120
|
1. If any referenced entry overlaps with another, a `BadZipFile` error
|
|
122
121
|
is raised since safe repacking cannot be guaranteed.
|
|
@@ -129,8 +128,8 @@ class _ZipRepacker:
|
|
|
129
128
|
be a sequence of consecutive entries with no extra preceding bytes;
|
|
130
129
|
extra following bytes are preserved.
|
|
131
130
|
|
|
132
|
-
|
|
133
|
-
|
|
131
|
+
This is to prevent an unexpected data removal (false positive), though
|
|
132
|
+
a false negative may happen in certain rare cases.
|
|
134
133
|
|
|
135
134
|
Examples:
|
|
136
135
|
|
|
@@ -180,8 +179,8 @@ class _ZipRepacker:
|
|
|
180
179
|
- Modifies the ZIP file in place.
|
|
181
180
|
- Updates zfile.start_dir to account for removed data.
|
|
182
181
|
- Sets zfile._didModify to True.
|
|
183
|
-
- Updates header_offset and _end_offset of referenced
|
|
184
|
-
instances.
|
|
182
|
+
- Updates header_offset and clears _end_offset of referenced
|
|
183
|
+
ZipInfo instances.
|
|
185
184
|
|
|
186
185
|
Parameters:
|
|
187
186
|
zfile: A ZipFile object representing the archive to repack.
|
|
@@ -262,14 +261,11 @@ class _ZipRepacker:
|
|
|
262
261
|
used_entry_size,
|
|
263
262
|
)
|
|
264
263
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
)
|
|
271
|
-
else:
|
|
272
|
-
stale_entry_size = 0
|
|
264
|
+
stale_entry_size = self._validate_local_file_entry_sequence(
|
|
265
|
+
fp,
|
|
266
|
+
old_header_offset + used_entry_size,
|
|
267
|
+
old_header_offset + entry_size,
|
|
268
|
+
)
|
|
273
269
|
|
|
274
270
|
if stale_entry_size > 0:
|
|
275
271
|
self._copy_bytes(
|
|
@@ -286,17 +282,11 @@ class _ZipRepacker:
|
|
|
286
282
|
zfile.start_dir -= entry_offset
|
|
287
283
|
zfile._didModify = True
|
|
288
284
|
|
|
289
|
-
# polyfill:
|
|
285
|
+
# polyfill: clear ZipInfo._end_offset if exists
|
|
290
286
|
# (Python >= 3.8 with fix #109858)
|
|
291
287
|
if hasattr(ZipInfo, '_end_offset'):
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
if zinfo in removed_zinfos:
|
|
295
|
-
zinfo._end_offset = None
|
|
296
|
-
else:
|
|
297
|
-
if zinfo._end_offset is not None:
|
|
298
|
-
zinfo._end_offset = end_offset
|
|
299
|
-
end_offset = zinfo.header_offset
|
|
288
|
+
for zinfo in filelist:
|
|
289
|
+
zinfo._end_offset = None
|
|
300
290
|
|
|
301
291
|
def _calc_initial_entry_offset(self, fp, data_offset):
|
|
302
292
|
checked_offsets = {}
|
|
@@ -380,8 +370,8 @@ class _ZipRepacker:
|
|
|
380
370
|
if pos > end_offset:
|
|
381
371
|
return None
|
|
382
372
|
|
|
373
|
+
# parse zip64
|
|
383
374
|
try:
|
|
384
|
-
# parse zip64
|
|
385
375
|
try:
|
|
386
376
|
zinfo._decodeExtra(crc32(filename))
|
|
387
377
|
except TypeError:
|
|
@@ -617,15 +607,16 @@ class ZipFile(ZipFile):
|
|
|
617
607
|
|
|
618
608
|
with self._lock:
|
|
619
609
|
# get the zinfo
|
|
620
|
-
# raise KeyError if arcname does not exist
|
|
621
610
|
if isinstance(zinfo_or_arcname, ZipInfo):
|
|
622
611
|
zinfo = zinfo_or_arcname
|
|
623
|
-
if zinfo not in self.filelist:
|
|
624
|
-
raise KeyError('There is no item %r in the archive' % zinfo)
|
|
625
612
|
else:
|
|
613
|
+
# raise KeyError if arcname does not exist
|
|
626
614
|
zinfo = self.getinfo(zinfo_or_arcname)
|
|
627
615
|
|
|
628
|
-
|
|
616
|
+
try:
|
|
617
|
+
self.filelist.remove(zinfo)
|
|
618
|
+
except ValueError:
|
|
619
|
+
raise KeyError('There is no item %r in the archive' % zinfo) from None
|
|
629
620
|
|
|
630
621
|
try:
|
|
631
622
|
del self.NameToInfo[zinfo.filename]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: zipremove
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: Extend `zipfile` with `remove`-related functionalities
|
|
5
5
|
Home-page: https://github.com/danny0838/zipremove
|
|
6
6
|
Author: Danny Lin
|
|
@@ -52,7 +52,7 @@ This package extends `zipfile` with `remove`-related functionalities.
|
|
|
52
52
|
a path is provided.
|
|
53
53
|
|
|
54
54
|
This does not physically remove the local file entry from the archive.
|
|
55
|
-
Call `
|
|
55
|
+
Call `repack` afterwards to reclaim space.
|
|
56
56
|
|
|
57
57
|
The archive must be opened with mode ``'w'``, ``'x'`` or ``'a'``.
|
|
58
58
|
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
+
import contextlib
|
|
1
2
|
import io
|
|
2
3
|
import itertools
|
|
3
4
|
import os
|
|
4
5
|
import struct
|
|
5
6
|
import sys
|
|
7
|
+
import time
|
|
6
8
|
import unittest
|
|
7
9
|
import unittest.mock as mock
|
|
8
10
|
import warnings
|
|
9
|
-
from contextlib import nullcontext
|
|
10
11
|
|
|
11
12
|
import zipremove as zipfile
|
|
12
13
|
|
|
@@ -42,8 +43,11 @@ def requires_zip64fix(reason='requires Python >= 3.11.4 for zip64 fix (#103861)'
|
|
|
42
43
|
return unittest.skipUnless(sys.version_info >= (3, 11, 4), reason)
|
|
43
44
|
|
|
44
45
|
|
|
45
|
-
|
|
46
|
-
|
|
46
|
+
class ComparableZipInfo:
|
|
47
|
+
keys = [i for i in zipfile.ZipInfo.__slots__ if not i.startswith('_')]
|
|
48
|
+
|
|
49
|
+
def __new__(cls, zinfo):
|
|
50
|
+
return {i: getattr(zinfo, i) for i in cls.keys}
|
|
47
51
|
|
|
48
52
|
_struct_pack = struct.pack
|
|
49
53
|
|
|
@@ -59,6 +63,8 @@ def struct_pack_no_dd_sig(fmt, *values):
|
|
|
59
63
|
|
|
60
64
|
class RepackHelperMixin:
|
|
61
65
|
"""Common helpers for remove and repack."""
|
|
66
|
+
maxDiff = 8192
|
|
67
|
+
|
|
62
68
|
@classmethod
|
|
63
69
|
def _prepare_test_files(cls):
|
|
64
70
|
return [
|
|
@@ -69,37 +75,48 @@ class RepackHelperMixin:
|
|
|
69
75
|
|
|
70
76
|
@classmethod
|
|
71
77
|
def _prepare_zip_from_test_files(cls, zfname, test_files, force_zip64=False):
|
|
72
|
-
zinfos = []
|
|
73
78
|
with zipfile.ZipFile(zfname, 'w', cls.compression) as zh:
|
|
74
79
|
for file, data in test_files:
|
|
75
80
|
with zh.open(file, 'w', force_zip64=force_zip64) as fh:
|
|
76
81
|
fh.write(data)
|
|
77
|
-
|
|
78
|
-
zinfos.append(ComparableZipInfo(zinfo))
|
|
79
|
-
return zinfos
|
|
82
|
+
return list(zh.infolist())
|
|
80
83
|
|
|
81
84
|
class AbstractCopyTests(RepackHelperMixin):
|
|
82
85
|
@classmethod
|
|
83
86
|
def setUpClass(cls):
|
|
84
87
|
cls.test_files = cls._prepare_test_files()
|
|
85
88
|
|
|
89
|
+
def tearDown(self):
|
|
90
|
+
unlink(TESTFN)
|
|
91
|
+
|
|
86
92
|
def test_copy_by_name(self):
|
|
87
93
|
for i in range(3):
|
|
88
94
|
with self.subTest(i=i, filename=self.test_files[i][0]):
|
|
89
95
|
zinfos = self._prepare_zip_from_test_files(TESTFN, self.test_files)
|
|
90
96
|
with zipfile.ZipFile(TESTFN, 'a', self.compression) as zh:
|
|
91
|
-
zi_new =
|
|
97
|
+
zi_new = {
|
|
98
|
+
**ComparableZipInfo(zinfos[i]),
|
|
99
|
+
'filename': 'file.txt',
|
|
100
|
+
'orig_filename': 'file.txt',
|
|
101
|
+
'header_offset': zh.start_dir,
|
|
102
|
+
}
|
|
92
103
|
zh.copy(self.test_files[i][0], 'file.txt')
|
|
93
104
|
|
|
94
105
|
# check infolist
|
|
95
106
|
self.assertEqual(
|
|
96
107
|
[ComparableZipInfo(zi) for zi in zh.infolist()],
|
|
97
|
-
[*(zi for
|
|
108
|
+
[*(ComparableZipInfo(zi) for zi in zinfos), zi_new],
|
|
98
109
|
)
|
|
99
110
|
|
|
100
111
|
# check NameToInfo cache
|
|
101
112
|
self.assertEqual(ComparableZipInfo(zh.getinfo('file.txt')), zi_new)
|
|
102
113
|
|
|
114
|
+
# check content
|
|
115
|
+
self.assertEqual(
|
|
116
|
+
zh.read(zi_new['filename']),
|
|
117
|
+
zh.read(zinfos[i].filename),
|
|
118
|
+
)
|
|
119
|
+
|
|
103
120
|
# make sure the zip file is still valid
|
|
104
121
|
with zipfile.ZipFile(TESTFN) as zh:
|
|
105
122
|
self.assertIsNone(zh.testzip())
|
|
@@ -109,18 +126,29 @@ class AbstractCopyTests(RepackHelperMixin):
|
|
|
109
126
|
with self.subTest(i=i, filename=self.test_files[i][0]):
|
|
110
127
|
zinfos = self._prepare_zip_from_test_files(TESTFN, self.test_files)
|
|
111
128
|
with zipfile.ZipFile(TESTFN, 'a', self.compression) as zh:
|
|
112
|
-
zi_new =
|
|
129
|
+
zi_new = {
|
|
130
|
+
**ComparableZipInfo(zinfos[i]),
|
|
131
|
+
'filename': 'file.txt',
|
|
132
|
+
'orig_filename': 'file.txt',
|
|
133
|
+
'header_offset': zh.start_dir,
|
|
134
|
+
}
|
|
113
135
|
zh.copy(zh.infolist()[i], 'file.txt')
|
|
114
136
|
|
|
115
137
|
# check infolist
|
|
116
138
|
self.assertEqual(
|
|
117
139
|
[ComparableZipInfo(zi) for zi in zh.infolist()],
|
|
118
|
-
[*(zi for
|
|
140
|
+
[*(ComparableZipInfo(zi) for zi in zinfos), zi_new],
|
|
119
141
|
)
|
|
120
142
|
|
|
121
143
|
# check NameToInfo cache
|
|
122
144
|
self.assertEqual(ComparableZipInfo(zh.getinfo('file.txt')), zi_new)
|
|
123
145
|
|
|
146
|
+
# check content
|
|
147
|
+
self.assertEqual(
|
|
148
|
+
zh.read(zi_new['filename']),
|
|
149
|
+
zh.read(zinfos[i].filename),
|
|
150
|
+
)
|
|
151
|
+
|
|
124
152
|
# make sure the zip file is still valid
|
|
125
153
|
with zipfile.ZipFile(TESTFN) as zh:
|
|
126
154
|
self.assertIsNone(zh.testzip())
|
|
@@ -130,18 +158,29 @@ class AbstractCopyTests(RepackHelperMixin):
|
|
|
130
158
|
with self.subTest(i=i, filename=self.test_files[i][0]):
|
|
131
159
|
zinfos = self._prepare_zip_from_test_files(TESTFN, self.test_files, force_zip64=True)
|
|
132
160
|
with zipfile.ZipFile(TESTFN, 'a', self.compression) as zh:
|
|
133
|
-
zi_new =
|
|
161
|
+
zi_new = {
|
|
162
|
+
**ComparableZipInfo(zinfos[i]),
|
|
163
|
+
'filename': 'file.txt',
|
|
164
|
+
'orig_filename': 'file.txt',
|
|
165
|
+
'header_offset': zh.start_dir,
|
|
166
|
+
}
|
|
134
167
|
zh.copy(self.test_files[i][0], 'file.txt')
|
|
135
168
|
|
|
136
169
|
# check infolist
|
|
137
170
|
self.assertEqual(
|
|
138
171
|
[ComparableZipInfo(zi) for zi in zh.infolist()],
|
|
139
|
-
[*(zi for
|
|
172
|
+
[*(ComparableZipInfo(zi) for zi in zinfos), zi_new],
|
|
140
173
|
)
|
|
141
174
|
|
|
142
175
|
# check NameToInfo cache
|
|
143
176
|
self.assertEqual(ComparableZipInfo(zh.getinfo('file.txt')), zi_new)
|
|
144
177
|
|
|
178
|
+
# check content
|
|
179
|
+
self.assertEqual(
|
|
180
|
+
zh.read(zi_new['filename']),
|
|
181
|
+
zh.read(zinfos[i].filename),
|
|
182
|
+
)
|
|
183
|
+
|
|
145
184
|
# make sure the zip file is still valid
|
|
146
185
|
with zipfile.ZipFile(TESTFN) as zh:
|
|
147
186
|
self.assertIsNone(zh.testzip())
|
|
@@ -152,18 +191,29 @@ class AbstractCopyTests(RepackHelperMixin):
|
|
|
152
191
|
with open(TESTFN, 'wb') as fh:
|
|
153
192
|
zinfos = self._prepare_zip_from_test_files(Unseekable(fh), self.test_files)
|
|
154
193
|
with zipfile.ZipFile(TESTFN, 'a', self.compression) as zh:
|
|
155
|
-
zi_new =
|
|
194
|
+
zi_new = {
|
|
195
|
+
**ComparableZipInfo(zinfos[i]),
|
|
196
|
+
'filename': 'file.txt',
|
|
197
|
+
'orig_filename': 'file.txt',
|
|
198
|
+
'header_offset': zh.start_dir,
|
|
199
|
+
}
|
|
156
200
|
zh.copy(self.test_files[i][0], 'file.txt')
|
|
157
201
|
|
|
158
202
|
# check infolist
|
|
159
203
|
self.assertEqual(
|
|
160
204
|
[ComparableZipInfo(zi) for zi in zh.infolist()],
|
|
161
|
-
[*(zi for
|
|
205
|
+
[*(ComparableZipInfo(zi) for zi in zinfos), zi_new],
|
|
162
206
|
)
|
|
163
207
|
|
|
164
208
|
# check NameToInfo cache
|
|
165
209
|
self.assertEqual(ComparableZipInfo(zh.getinfo('file.txt')), zi_new)
|
|
166
210
|
|
|
211
|
+
# check content
|
|
212
|
+
self.assertEqual(
|
|
213
|
+
zh.read(zi_new['filename']),
|
|
214
|
+
zh.read(zinfos[i].filename),
|
|
215
|
+
)
|
|
216
|
+
|
|
167
217
|
# make sure the zip file is still valid
|
|
168
218
|
with zipfile.ZipFile(TESTFN) as zh:
|
|
169
219
|
self.assertIsNone(zh.testzip())
|
|
@@ -173,18 +223,29 @@ class AbstractCopyTests(RepackHelperMixin):
|
|
|
173
223
|
with self.subTest(i=i, filename=self.test_files[i][0]):
|
|
174
224
|
zinfos = self._prepare_zip_from_test_files(TESTFN, self.test_files)
|
|
175
225
|
with zipfile.ZipFile(TESTFN, 'a', self.compression) as zh:
|
|
176
|
-
zi_new =
|
|
226
|
+
zi_new = {
|
|
227
|
+
**ComparableZipInfo(zinfos[i]),
|
|
228
|
+
'filename': 'file2.txt',
|
|
229
|
+
'orig_filename': 'file2.txt',
|
|
230
|
+
'header_offset': zh.start_dir,
|
|
231
|
+
}
|
|
177
232
|
zh.copy(self.test_files[i][0], 'file2.txt')
|
|
178
233
|
|
|
179
234
|
# check infolist
|
|
180
235
|
self.assertEqual(
|
|
181
236
|
[ComparableZipInfo(zi) for zi in zh.infolist()],
|
|
182
|
-
[*(zi for
|
|
237
|
+
[*(ComparableZipInfo(zi) for zi in zinfos), zi_new],
|
|
183
238
|
)
|
|
184
239
|
|
|
185
240
|
# check NameToInfo cache
|
|
186
241
|
self.assertEqual(ComparableZipInfo(zh.getinfo('file2.txt')), zi_new)
|
|
187
242
|
|
|
243
|
+
# check content
|
|
244
|
+
self.assertEqual(
|
|
245
|
+
zh.read(zi_new['filename']),
|
|
246
|
+
zh.read(zinfos[i].filename),
|
|
247
|
+
)
|
|
248
|
+
|
|
188
249
|
# make sure the zip file is still valid
|
|
189
250
|
with zipfile.ZipFile(TESTFN) as zh:
|
|
190
251
|
self.assertIsNone(zh.testzip())
|
|
@@ -222,44 +283,63 @@ class AbstractCopyTests(RepackHelperMixin):
|
|
|
222
283
|
with zipfile.ZipFile(TESTFN, 'w') as zh:
|
|
223
284
|
for file, data in self.test_files:
|
|
224
285
|
zh.writestr(file, data)
|
|
225
|
-
zinfos =
|
|
226
|
-
|
|
227
|
-
zi_new =
|
|
286
|
+
zinfos = list(zh.infolist())
|
|
287
|
+
|
|
288
|
+
zi_new = {
|
|
289
|
+
**ComparableZipInfo(zinfos[0]),
|
|
290
|
+
'filename': 'file.txt',
|
|
291
|
+
'orig_filename': 'file.txt',
|
|
292
|
+
'header_offset': zh.start_dir,
|
|
293
|
+
}
|
|
228
294
|
zh.copy(zh.infolist()[0], 'file.txt')
|
|
229
295
|
|
|
230
296
|
# check infolist
|
|
231
297
|
self.assertEqual(
|
|
232
298
|
[ComparableZipInfo(zi) for zi in zh.infolist()],
|
|
233
|
-
[*(zi for
|
|
299
|
+
[*(ComparableZipInfo(zi) for zi in zinfos), zi_new],
|
|
234
300
|
)
|
|
235
301
|
|
|
236
302
|
# check NameToInfo cache
|
|
237
303
|
self.assertEqual(ComparableZipInfo(zh.getinfo('file.txt')), zi_new)
|
|
238
|
-
|
|
304
|
+
|
|
305
|
+
# check content
|
|
306
|
+
self.assertEqual(
|
|
307
|
+
zh.read(zi_new['filename']),
|
|
308
|
+
zh.read(zinfos[0].filename),
|
|
309
|
+
)
|
|
239
310
|
|
|
240
311
|
# make sure the zip file is still valid
|
|
241
312
|
with zipfile.ZipFile(TESTFN) as zh:
|
|
242
313
|
self.assertIsNone(zh.testzip())
|
|
243
314
|
|
|
244
315
|
def test_copy_mode_x(self):
|
|
245
|
-
unlink(TESTFN)
|
|
246
316
|
with zipfile.ZipFile(TESTFN, 'x') as zh:
|
|
247
317
|
for file, data in self.test_files:
|
|
248
318
|
zh.writestr(file, data)
|
|
249
|
-
zinfos =
|
|
250
|
-
|
|
251
|
-
zi_new =
|
|
319
|
+
zinfos = list(zh.infolist())
|
|
320
|
+
|
|
321
|
+
zi_new = {
|
|
322
|
+
**ComparableZipInfo(zinfos[0]),
|
|
323
|
+
'filename': 'file.txt',
|
|
324
|
+
'orig_filename': 'file.txt',
|
|
325
|
+
'header_offset': zh.start_dir,
|
|
326
|
+
}
|
|
252
327
|
zh.copy(zh.infolist()[0], 'file.txt')
|
|
253
328
|
|
|
254
329
|
# check infolist
|
|
255
330
|
self.assertEqual(
|
|
256
331
|
[ComparableZipInfo(zi) for zi in zh.infolist()],
|
|
257
|
-
[*(zi for
|
|
332
|
+
[*(ComparableZipInfo(zi) for zi in zinfos), zi_new],
|
|
258
333
|
)
|
|
259
334
|
|
|
260
335
|
# check NameToInfo cache
|
|
261
336
|
self.assertEqual(ComparableZipInfo(zh.getinfo('file.txt')), zi_new)
|
|
262
|
-
|
|
337
|
+
|
|
338
|
+
# check content
|
|
339
|
+
self.assertEqual(
|
|
340
|
+
zh.read(zi_new['filename']),
|
|
341
|
+
zh.read(zinfos[0].filename),
|
|
342
|
+
)
|
|
263
343
|
|
|
264
344
|
# make sure the zip file is still valid
|
|
265
345
|
with zipfile.ZipFile(TESTFN) as zh:
|
|
@@ -302,7 +382,7 @@ class AbstractRemoveTests(RepackHelperMixin):
|
|
|
302
382
|
# check infolist
|
|
303
383
|
self.assertEqual(
|
|
304
384
|
[ComparableZipInfo(zi) for zi in zh.infolist()],
|
|
305
|
-
[zi for j, zi in enumerate(zinfos) if j != i],
|
|
385
|
+
[ComparableZipInfo(zi) for j, zi in enumerate(zinfos) if j != i],
|
|
306
386
|
)
|
|
307
387
|
|
|
308
388
|
# check NameToInfo cache
|
|
@@ -323,7 +403,7 @@ class AbstractRemoveTests(RepackHelperMixin):
|
|
|
323
403
|
# check infolist
|
|
324
404
|
self.assertEqual(
|
|
325
405
|
[ComparableZipInfo(zi) for zi in zh.infolist()],
|
|
326
|
-
[zi for j, zi in enumerate(zinfos) if j != i],
|
|
406
|
+
[ComparableZipInfo(zi) for j, zi in enumerate(zinfos) if j != i],
|
|
327
407
|
)
|
|
328
408
|
|
|
329
409
|
# check NameToInfo cache
|
|
@@ -364,13 +444,13 @@ class AbstractRemoveTests(RepackHelperMixin):
|
|
|
364
444
|
# check infolist
|
|
365
445
|
self.assertEqual(
|
|
366
446
|
[ComparableZipInfo(zi) for zi in zh.infolist()],
|
|
367
|
-
[zinfos[0], zinfos[2]],
|
|
447
|
+
[ComparableZipInfo(zi) for zi in [zinfos[0], zinfos[2]]],
|
|
368
448
|
)
|
|
369
449
|
|
|
370
450
|
# check NameToInfo cache
|
|
371
451
|
self.assertEqual(
|
|
372
452
|
ComparableZipInfo(zh.getinfo('file.txt')),
|
|
373
|
-
zinfos[0],
|
|
453
|
+
ComparableZipInfo(zinfos[0]),
|
|
374
454
|
)
|
|
375
455
|
|
|
376
456
|
# make sure the zip file is still valid
|
|
@@ -385,7 +465,7 @@ class AbstractRemoveTests(RepackHelperMixin):
|
|
|
385
465
|
# check infolist
|
|
386
466
|
self.assertEqual(
|
|
387
467
|
[ComparableZipInfo(zi) for zi in zh.infolist()],
|
|
388
|
-
[zinfos[2]],
|
|
468
|
+
[ComparableZipInfo(zi) for zi in [zinfos[2]]],
|
|
389
469
|
)
|
|
390
470
|
|
|
391
471
|
# check NameToInfo cache
|
|
@@ -414,13 +494,13 @@ class AbstractRemoveTests(RepackHelperMixin):
|
|
|
414
494
|
# check infolist
|
|
415
495
|
self.assertEqual(
|
|
416
496
|
[ComparableZipInfo(zi) for zi in zh.infolist()],
|
|
417
|
-
[zinfos[1], zinfos[2]],
|
|
497
|
+
[ComparableZipInfo(zi) for zi in [zinfos[1], zinfos[2]]],
|
|
418
498
|
)
|
|
419
499
|
|
|
420
500
|
# check NameToInfo cache
|
|
421
501
|
self.assertEqual(
|
|
422
502
|
ComparableZipInfo(zh.getinfo('file.txt')),
|
|
423
|
-
zinfos[1],
|
|
503
|
+
ComparableZipInfo(zinfos[1]),
|
|
424
504
|
)
|
|
425
505
|
|
|
426
506
|
# make sure the zip file is still valid
|
|
@@ -434,13 +514,13 @@ class AbstractRemoveTests(RepackHelperMixin):
|
|
|
434
514
|
# check infolist
|
|
435
515
|
self.assertEqual(
|
|
436
516
|
[ComparableZipInfo(zi) for zi in zh.infolist()],
|
|
437
|
-
[zinfos[0], zinfos[2]],
|
|
517
|
+
[ComparableZipInfo(zi) for zi in [zinfos[0], zinfos[2]]],
|
|
438
518
|
)
|
|
439
519
|
|
|
440
520
|
# check NameToInfo cache
|
|
441
521
|
self.assertEqual(
|
|
442
522
|
ComparableZipInfo(zh.getinfo('file.txt')),
|
|
443
|
-
zinfos[0],
|
|
523
|
+
ComparableZipInfo(zinfos[0]),
|
|
444
524
|
)
|
|
445
525
|
|
|
446
526
|
# make sure the zip file is still valid
|
|
@@ -456,7 +536,7 @@ class AbstractRemoveTests(RepackHelperMixin):
|
|
|
456
536
|
# check infolist
|
|
457
537
|
self.assertEqual(
|
|
458
538
|
[ComparableZipInfo(zi) for zi in zh.infolist()],
|
|
459
|
-
[zinfos[2]],
|
|
539
|
+
[ComparableZipInfo(zi) for zi in [zinfos[2]]],
|
|
460
540
|
)
|
|
461
541
|
|
|
462
542
|
# check NameToInfo cache
|
|
@@ -478,7 +558,7 @@ class AbstractRemoveTests(RepackHelperMixin):
|
|
|
478
558
|
# check infolist
|
|
479
559
|
self.assertEqual(
|
|
480
560
|
[ComparableZipInfo(zi) for zi in zh.infolist()],
|
|
481
|
-
[zi for j, zi in enumerate(zinfos) if j != i],
|
|
561
|
+
[ComparableZipInfo(zi) for j, zi in enumerate(zinfos) if j != i],
|
|
482
562
|
)
|
|
483
563
|
|
|
484
564
|
# check NameToInfo cache
|
|
@@ -513,14 +593,14 @@ class AbstractRemoveTests(RepackHelperMixin):
|
|
|
513
593
|
with zipfile.ZipFile(TESTFN, 'w') as zh:
|
|
514
594
|
for file, data in self.test_files:
|
|
515
595
|
zh.writestr(file, data)
|
|
516
|
-
zinfos =
|
|
596
|
+
zinfos = list(zh.infolist())
|
|
517
597
|
|
|
518
598
|
zh.remove(self.test_files[0][0])
|
|
519
599
|
|
|
520
600
|
# check infolist
|
|
521
601
|
self.assertEqual(
|
|
522
602
|
[ComparableZipInfo(zi) for zi in zh.infolist()],
|
|
523
|
-
[zinfos[1], zinfos[2]],
|
|
603
|
+
[ComparableZipInfo(zi) for zi in [zinfos[1], zinfos[2]]],
|
|
524
604
|
)
|
|
525
605
|
|
|
526
606
|
# check NameToInfo cache
|
|
@@ -535,14 +615,14 @@ class AbstractRemoveTests(RepackHelperMixin):
|
|
|
535
615
|
with zipfile.ZipFile(TESTFN, 'x') as zh:
|
|
536
616
|
for file, data in self.test_files:
|
|
537
617
|
zh.writestr(file, data)
|
|
538
|
-
zinfos =
|
|
618
|
+
zinfos = list(zh.infolist())
|
|
539
619
|
|
|
540
620
|
zh.remove(self.test_files[0][0])
|
|
541
621
|
|
|
542
622
|
# check infolist
|
|
543
623
|
self.assertEqual(
|
|
544
624
|
[ComparableZipInfo(zi) for zi in zh.infolist()],
|
|
545
|
-
[zinfos[1], zinfos[2]],
|
|
625
|
+
[ComparableZipInfo(zi) for zi in [zinfos[1], zinfos[2]]],
|
|
546
626
|
)
|
|
547
627
|
|
|
548
628
|
# check NameToInfo cache
|
|
@@ -601,7 +681,7 @@ class AbstractRepackTests(RepackHelperMixin):
|
|
|
601
681
|
# check infolist
|
|
602
682
|
self.assertEqual(
|
|
603
683
|
[ComparableZipInfo(zi) for zi in zh.infolist()],
|
|
604
|
-
expected_zinfos,
|
|
684
|
+
[ComparableZipInfo(zi) for zi in expected_zinfos],
|
|
605
685
|
)
|
|
606
686
|
|
|
607
687
|
# check file size
|
|
@@ -616,27 +696,20 @@ class AbstractRepackTests(RepackHelperMixin):
|
|
|
616
696
|
self._prepare_zip_from_test_files(TESTFN, self.test_files)
|
|
617
697
|
|
|
618
698
|
with zipfile.ZipFile(TESTFN, 'a', self.compression) as zh:
|
|
619
|
-
|
|
620
|
-
|
|
699
|
+
with mock.patch.object(zipfile._ZipRepacker, 'repack') as m_rp, \
|
|
700
|
+
mock.patch.object(zipfile, '_ZipRepacker', wraps=zipfile._ZipRepacker) as m_zr:
|
|
621
701
|
zh.repack()
|
|
702
|
+
m_zr.assert_called_once_with()
|
|
622
703
|
m_rp.assert_called_once_with(zh, None)
|
|
623
704
|
|
|
624
705
|
with zipfile.ZipFile(TESTFN, 'a', self.compression) as zh:
|
|
625
706
|
zi = zh.remove(zh.infolist()[0])
|
|
626
|
-
with mock.patch.object(zipfile._ZipRepacker, 'repack') as m_rp
|
|
627
|
-
|
|
707
|
+
with mock.patch.object(zipfile._ZipRepacker, 'repack') as m_rp, \
|
|
708
|
+
mock.patch.object(zipfile, '_ZipRepacker', wraps=zipfile._ZipRepacker) as m_zr:
|
|
709
|
+
zh.repack([zi], strict_descriptor=True, chunk_size=1024)
|
|
710
|
+
m_zr.assert_called_once_with(strict_descriptor=True, chunk_size=1024)
|
|
628
711
|
m_rp.assert_called_once_with(zh, [zi])
|
|
629
712
|
|
|
630
|
-
with zipfile.ZipFile(TESTFN, 'a', self.compression) as zh:
|
|
631
|
-
with mock.patch.object(zipfile, '_ZipRepacker') as m_rp:
|
|
632
|
-
zh.repack()
|
|
633
|
-
m_rp.assert_called_once_with()
|
|
634
|
-
|
|
635
|
-
with zipfile.ZipFile(TESTFN, 'a', self.compression) as zh:
|
|
636
|
-
with mock.patch.object(zipfile, '_ZipRepacker') as m_rp:
|
|
637
|
-
zh.repack(strict_descriptor=True, chunk_size=1024)
|
|
638
|
-
m_rp.assert_called_once_with(strict_descriptor=True, chunk_size=1024)
|
|
639
|
-
|
|
640
713
|
def test_repack_bytes_before_first_file(self):
|
|
641
714
|
"""Should preserve random bytes before the first recorded local file entry."""
|
|
642
715
|
for ii in ([], [0], [0, 1], [0, 1, 2]):
|
|
@@ -660,7 +733,7 @@ class AbstractRepackTests(RepackHelperMixin):
|
|
|
660
733
|
# check infolist
|
|
661
734
|
self.assertEqual(
|
|
662
735
|
[ComparableZipInfo(zi) for zi in zh.infolist()],
|
|
663
|
-
expected_zinfos,
|
|
736
|
+
[ComparableZipInfo(zi) for zi in expected_zinfos],
|
|
664
737
|
)
|
|
665
738
|
|
|
666
739
|
# check file size
|
|
@@ -694,7 +767,7 @@ class AbstractRepackTests(RepackHelperMixin):
|
|
|
694
767
|
# check infolist
|
|
695
768
|
self.assertEqual(
|
|
696
769
|
[ComparableZipInfo(zi) for zi in zh.infolist()],
|
|
697
|
-
expected_zinfos,
|
|
770
|
+
[ComparableZipInfo(zi) for zi in expected_zinfos],
|
|
698
771
|
)
|
|
699
772
|
|
|
700
773
|
# check file size
|
|
@@ -740,7 +813,7 @@ class AbstractRepackTests(RepackHelperMixin):
|
|
|
740
813
|
# check infolist
|
|
741
814
|
self.assertEqual(
|
|
742
815
|
[ComparableZipInfo(zi) for zi in zh.infolist()],
|
|
743
|
-
expected_zinfos,
|
|
816
|
+
[ComparableZipInfo(zi) for zi in expected_zinfos],
|
|
744
817
|
)
|
|
745
818
|
|
|
746
819
|
# check file size
|
|
@@ -750,6 +823,7 @@ class AbstractRepackTests(RepackHelperMixin):
|
|
|
750
823
|
with zipfile.ZipFile(TESTFN) as zh:
|
|
751
824
|
self.assertIsNone(zh.testzip())
|
|
752
825
|
|
|
826
|
+
@mock.patch.object(time, 'time', new=lambda: 315504000) # fix time for ZipFile.writestr()
|
|
753
827
|
def test_repack_bytes_before_removed_files(self):
|
|
754
828
|
"""Should preserve if there are bytes before stale local file entries."""
|
|
755
829
|
for ii in ([1], [1, 2], [2]):
|
|
@@ -764,7 +838,7 @@ class AbstractRepackTests(RepackHelperMixin):
|
|
|
764
838
|
zh.writestr(file, data)
|
|
765
839
|
for i in ii:
|
|
766
840
|
zh.remove(self.test_files[i][0])
|
|
767
|
-
expected_zinfos =
|
|
841
|
+
expected_zinfos = list(zh.infolist())
|
|
768
842
|
expected_size = os.path.getsize(TESTFN)
|
|
769
843
|
|
|
770
844
|
# do the removal and check the result
|
|
@@ -783,7 +857,7 @@ class AbstractRepackTests(RepackHelperMixin):
|
|
|
783
857
|
# check infolist
|
|
784
858
|
self.assertEqual(
|
|
785
859
|
[ComparableZipInfo(zi) for zi in zh.infolist()],
|
|
786
|
-
expected_zinfos,
|
|
860
|
+
[ComparableZipInfo(zi) for zi in expected_zinfos],
|
|
787
861
|
)
|
|
788
862
|
|
|
789
863
|
# check file size
|
|
@@ -793,6 +867,7 @@ class AbstractRepackTests(RepackHelperMixin):
|
|
|
793
867
|
with zipfile.ZipFile(TESTFN) as zh:
|
|
794
868
|
self.assertIsNone(zh.testzip())
|
|
795
869
|
|
|
870
|
+
@mock.patch.object(time, 'time', new=lambda: 315504000) # fix time for ZipFile.writestr()
|
|
796
871
|
def test_repack_bytes_after_removed_files(self):
|
|
797
872
|
"""Should keep extra bytes if there are bytes after stale local file entries."""
|
|
798
873
|
for ii in ([1], [1, 2], [2]):
|
|
@@ -806,7 +881,7 @@ class AbstractRepackTests(RepackHelperMixin):
|
|
|
806
881
|
if i == ii[-1]:
|
|
807
882
|
fh.write(b' dummy bytes ')
|
|
808
883
|
zh.start_dir = fh.tell()
|
|
809
|
-
expected_zinfos =
|
|
884
|
+
expected_zinfos = list(zh.infolist())
|
|
810
885
|
expected_size = os.path.getsize(TESTFN)
|
|
811
886
|
|
|
812
887
|
# do the removal and check the result
|
|
@@ -825,7 +900,7 @@ class AbstractRepackTests(RepackHelperMixin):
|
|
|
825
900
|
# check infolist
|
|
826
901
|
self.assertEqual(
|
|
827
902
|
[ComparableZipInfo(zi) for zi in zh.infolist()],
|
|
828
|
-
expected_zinfos,
|
|
903
|
+
[ComparableZipInfo(zi) for zi in expected_zinfos],
|
|
829
904
|
)
|
|
830
905
|
|
|
831
906
|
# check file size
|
|
@@ -835,6 +910,7 @@ class AbstractRepackTests(RepackHelperMixin):
|
|
|
835
910
|
with zipfile.ZipFile(TESTFN) as zh:
|
|
836
911
|
self.assertIsNone(zh.testzip())
|
|
837
912
|
|
|
913
|
+
@mock.patch.object(time, 'time', new=lambda: 315504000) # fix time for ZipFile.writestr()
|
|
838
914
|
def test_repack_bytes_between_removed_files(self):
|
|
839
915
|
"""Should strip only local file entries before random bytes."""
|
|
840
916
|
# calculate the expected results
|
|
@@ -845,7 +921,7 @@ class AbstractRepackTests(RepackHelperMixin):
|
|
|
845
921
|
zh.start_dir = fh.tell()
|
|
846
922
|
zh.writestr(*self.test_files[2])
|
|
847
923
|
zh.remove(self.test_files[2][0])
|
|
848
|
-
expected_zinfos =
|
|
924
|
+
expected_zinfos = list(zh.infolist())
|
|
849
925
|
expected_size = os.path.getsize(TESTFN)
|
|
850
926
|
|
|
851
927
|
# do the removal and check the result
|
|
@@ -864,7 +940,7 @@ class AbstractRepackTests(RepackHelperMixin):
|
|
|
864
940
|
# check infolist
|
|
865
941
|
self.assertEqual(
|
|
866
942
|
[ComparableZipInfo(zi) for zi in zh.infolist()],
|
|
867
|
-
expected_zinfos,
|
|
943
|
+
[ComparableZipInfo(zi) for zi in expected_zinfos],
|
|
868
944
|
)
|
|
869
945
|
|
|
870
946
|
# check file size
|
|
@@ -886,7 +962,7 @@ class AbstractRepackTests(RepackHelperMixin):
|
|
|
886
962
|
fh.write(b'dummy ')
|
|
887
963
|
fh.write(fz.read())
|
|
888
964
|
with zipfile.ZipFile(TESTFN) as zh:
|
|
889
|
-
expected_zinfos =
|
|
965
|
+
expected_zinfos = list(zh.infolist())
|
|
890
966
|
expected_size = os.path.getsize(TESTFN)
|
|
891
967
|
|
|
892
968
|
# do the removal and check the result
|
|
@@ -904,7 +980,7 @@ class AbstractRepackTests(RepackHelperMixin):
|
|
|
904
980
|
# check infolist
|
|
905
981
|
self.assertEqual(
|
|
906
982
|
[ComparableZipInfo(zi) for zi in zh.infolist()],
|
|
907
|
-
expected_zinfos,
|
|
983
|
+
[ComparableZipInfo(zi) for zi in expected_zinfos],
|
|
908
984
|
)
|
|
909
985
|
|
|
910
986
|
# check file size
|
|
@@ -949,7 +1025,7 @@ class AbstractRepackTests(RepackHelperMixin):
|
|
|
949
1025
|
# check infolist
|
|
950
1026
|
self.assertEqual(
|
|
951
1027
|
[ComparableZipInfo(zi) for zi in zh.infolist()],
|
|
952
|
-
expected_zinfos,
|
|
1028
|
+
[ComparableZipInfo(zi) for zi in expected_zinfos],
|
|
953
1029
|
)
|
|
954
1030
|
|
|
955
1031
|
# check file size
|
|
@@ -992,20 +1068,20 @@ class AbstractRepackTests(RepackHelperMixin):
|
|
|
992
1068
|
with zipfile.ZipFile(TESTFN) as zh:
|
|
993
1069
|
self.assertIsNone(zh.testzip())
|
|
994
1070
|
|
|
1071
|
+
@mock.patch.object(time, 'time', new=lambda: 315504000) # fix time for ZipFile.writestr()
|
|
995
1072
|
def test_repack_removed_bytes_between_files(self):
|
|
996
1073
|
"""Should not remove bytes between local file entries."""
|
|
997
1074
|
for ii in ([0], [1], [2]):
|
|
998
1075
|
with self.subTest(removed=ii):
|
|
999
1076
|
# calculate the expected results
|
|
1000
|
-
expected_zinfos = []
|
|
1001
1077
|
with open(TESTFN, 'wb') as fh:
|
|
1002
1078
|
with zipfile.ZipFile(fh, 'w', self.compression) as zh:
|
|
1003
1079
|
for j, (file, data) in enumerate(self.test_files):
|
|
1004
1080
|
if j not in ii:
|
|
1005
1081
|
zh.writestr(file, data)
|
|
1006
|
-
expected_zinfos.append(ComparableZipInfo(zh.getinfo(file)))
|
|
1007
1082
|
fh.write(b' dummy bytes ')
|
|
1008
1083
|
zh.start_dir = fh.tell()
|
|
1084
|
+
expected_zinfos = list(zh.infolist())
|
|
1009
1085
|
expected_size = os.path.getsize(TESTFN)
|
|
1010
1086
|
|
|
1011
1087
|
# do the removal and check the result
|
|
@@ -1022,7 +1098,7 @@ class AbstractRepackTests(RepackHelperMixin):
|
|
|
1022
1098
|
# check infolist
|
|
1023
1099
|
self.assertEqual(
|
|
1024
1100
|
[ComparableZipInfo(zi) for zi in zh.infolist()],
|
|
1025
|
-
expected_zinfos,
|
|
1101
|
+
[ComparableZipInfo(zi) for zi in expected_zinfos],
|
|
1026
1102
|
)
|
|
1027
1103
|
|
|
1028
1104
|
# check file size
|
|
@@ -1078,7 +1154,7 @@ class AbstractRepackTests(RepackHelperMixin):
|
|
|
1078
1154
|
fh.write(b'dummy ')
|
|
1079
1155
|
fh.write(fz.read())
|
|
1080
1156
|
with zipfile.ZipFile(TESTFN) as zh:
|
|
1081
|
-
expected_zinfos =
|
|
1157
|
+
expected_zinfos = list(zh.infolist())
|
|
1082
1158
|
expected_size = os.path.getsize(TESTFN)
|
|
1083
1159
|
|
|
1084
1160
|
# do the removal and check the result
|
|
@@ -1095,7 +1171,7 @@ class AbstractRepackTests(RepackHelperMixin):
|
|
|
1095
1171
|
# check infolist
|
|
1096
1172
|
self.assertEqual(
|
|
1097
1173
|
[ComparableZipInfo(zi) for zi in zh.infolist()],
|
|
1098
|
-
expected_zinfos,
|
|
1174
|
+
[ComparableZipInfo(zi) for zi in expected_zinfos],
|
|
1099
1175
|
)
|
|
1100
1176
|
|
|
1101
1177
|
# check file size
|
|
@@ -1392,7 +1468,7 @@ class ZipRepackerTests(unittest.TestCase):
|
|
|
1392
1468
|
fz = io.BytesIO()
|
|
1393
1469
|
f = Unseekable(fz) if dd else fz
|
|
1394
1470
|
cm = (mock.patch.object(struct, 'pack', side_effect=struct_pack_no_dd_sig)
|
|
1395
|
-
if not dd_sig else nullcontext())
|
|
1471
|
+
if not dd_sig else contextlib.nullcontext())
|
|
1396
1472
|
with zipfile.ZipFile(f, 'w', compression=compression) as zh:
|
|
1397
1473
|
with cm:
|
|
1398
1474
|
with zh.open(arcname, 'w', force_zip64=force_zip64) as fh:
|
|
@@ -18,16 +18,19 @@ except ImportError:
|
|
|
18
18
|
# polyfill for Python < 3.12
|
|
19
19
|
from test.test_zipfile import Unseekable, requires_zlib
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
def requires_resource(res):
|
|
22
|
+
if not hasattr(requires_resource, '_resources'):
|
|
23
|
+
requires_resource._resources = set(os.environ.get("TEST_RESOURCES", "").split(","))
|
|
24
24
|
return unittest.skipUnless(
|
|
25
|
-
|
|
26
|
-
f"requires resource
|
|
25
|
+
res in requires_resource._resources,
|
|
26
|
+
f"requires resource {res!r} in envvar TEST_RESOURCES"
|
|
27
27
|
)
|
|
28
28
|
|
|
29
|
+
@requires_resource('extralargefile')
|
|
30
|
+
def setUpModule():
|
|
31
|
+
pass
|
|
32
|
+
|
|
29
33
|
|
|
30
|
-
@requires('extralargefile')
|
|
31
34
|
class TestRepack(unittest.TestCase):
|
|
32
35
|
def setUp(self):
|
|
33
36
|
# Create test data.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|