@@ -1147,6 +1147,240 @@ class LzmaWriterTests(AbstractWriterTests, unittest.TestCase):
1147
1147
compression = zipfile .ZIP_LZMA
1148
1148
1149
1149
1150
+ class AbstractRemoveTests :
1151
+
1152
+ def _test_removing_indexes (self ,test_files ,indexes ):
1153
+ """Test underlying _remove_members() for removing members at given
1154
+ indexes."""
1155
+ # calculate the expected results
1156
+ expected_files = []
1157
+ with zipfile .ZipFile (TESTFN ,'w' )as zh :
1158
+ for i , (file ,data )in enumerate (test_files ):
1159
+ if i not in indexes :
1160
+ zh .writestr (file ,data )
1161
+ expected_files .append (file )
1162
+ expected_size = os .path .getsize (TESTFN )
1163
+
1164
+ # prepare the test zip
1165
+ with zipfile .ZipFile (TESTFN ,'w' )as zh :
1166
+ for file ,data in test_files :
1167
+ zh .writestr (file ,data )
1168
+
1169
+ # do the removal and check the result
1170
+ with zipfile .ZipFile (TESTFN ,'a' )as zh :
1171
+ members = {zh .infolist ()[i ]for i in indexes }
1172
+ zh ._remove_members (members )
1173
+
1174
+ # make sure internal caches have reflected the change
1175
+ # and are consistent
1176
+ self .assertEqual (zh .namelist (),expected_files )
1177
+ for file ,_ in test_files :
1178
+ if file in zh .namelist ():
1179
+ self .assertEqual (zh .getinfo (file ).filename ,file )
1180
+ else :
1181
+ with self .assertRaises (KeyError ):
1182
+ zh .getinfo (file )
1183
+
1184
+ self .assertIsNone (zh .testzip ())
1185
+ self .assertEqual (os .path .getsize (TESTFN ),expected_size )
1186
+
1187
+ def _test_removing_combinations (self ,test_files ,n = None ):
1188
+ """Test underlying _remove_members() for removing random combinations
1189
+ of members."""
1190
+ ln = len (test_files )
1191
+ if n is None :
1192
+ # iterate n from 1 to all
1193
+ for n in range (1 ,ln + 1 ):
1194
+ for indexes in itertools .combinations (range (ln ),n ):
1195
+ with self .subTest (remove = indexes ):
1196
+ self ._test_removing_indexes (test_files ,indexes )
1197
+ else :
1198
+ for indexes in itertools .combinations (range (ln ),n ):
1199
+ with self .subTest (remove = indexes ):
1200
+ self ._test_removing_indexes (test_files ,indexes )
1201
+
1202
+ def test_basic (self ):
1203
+ # Test underlying _remove_members() for removing random combinations of members.
1204
+ test_files = [
1205
+ ('file0.txt' ,b'Lorem ipsum dolor sit amet, consectetur adipiscing elit' ),
1206
+ ('file1.txt' ,b'Duis aute irure dolor in reprehenderit in voluptate velit esse' ),
1207
+ ('file2.txt' ,b'Sed ut perspiciatis unde omnis iste natus error sit voluptatem' ),
1208
+ ]
1209
+
1210
+ self ._test_removing_combinations (test_files )
1211
+
1212
+ def test_duplicated_arcname (self ):
1213
+ # Test underlying _remove_members() for removing any one of random duplicated members.
1214
+ dupl_file = 'file.txt'
1215
+ test_files = [
1216
+ ('file0.txt' ,b'Lorem ipsum dolor sit amet, consectetur adipiscing elit' ),
1217
+ ('file1.txt' ,b'Duis aute irure dolor in reprehenderit in voluptate velit esse' ),
1218
+ ('file2.txt' ,b'Sed ut perspiciatis unde omnis iste natus error sit voluptatem' ),
1219
+ ]
1220
+
1221
+ ln = len (test_files )
1222
+ for n in range (2 ,ln + 1 ):
1223
+ for dups in itertools .combinations (range (ln ),n ):
1224
+ files = []
1225
+ for i , (file ,data )in enumerate (test_files ):
1226
+ file_ = dupl_file if i in dups else file
1227
+ files .append ((file_ ,data ))
1228
+
1229
+ for index in dups :
1230
+ indexes = [index ]
1231
+ with self .subTest (dups = dups ,indexes = indexes ):
1232
+ self ._test_removing_indexes (files ,indexes )
1233
+
1234
+ def test_non_physical (self ):
1235
+ # Test underlying _remove_members() for non-physical removing.
1236
+ test_files = [
1237
+ ('file0.txt' ,b'Lorem ipsum dolor sit amet, consectetur adipiscing elit' ),
1238
+ ('file1.txt' ,b'Duis aute irure dolor in reprehenderit in voluptate velit esse' ),
1239
+ ('file2.txt' ,b'Sed ut perspiciatis unde omnis iste natus error sit voluptatem' ),
1240
+ ]
1241
+
1242
+ ln = len (test_files )
1243
+ for n in range (1 ,ln + 1 ):
1244
+ for indexes in itertools .combinations (range (ln ),n ):
1245
+ with self .subTest (remove = indexes ):
1246
+ # prepare the test zip
1247
+ expected = {}
1248
+ with zipfile .ZipFile (TESTFN ,'w' )as zh :
1249
+ for i , (file ,data )in enumerate (test_files ):
1250
+ zh .writestr (file ,data )
1251
+ if i not in indexes :
1252
+ expected [file ]= zh .getinfo (file ).header_offset
1253
+
1254
+ # do the removal and check the result
1255
+ with zipfile .ZipFile (TESTFN ,'a' )as zh :
1256
+ members = {zh .infolist ()[i ]for i in indexes }
1257
+ zh ._remove_members (members ,remove_physical = False )
1258
+ self .assertEqual (zh .namelist (),list (expected ))
1259
+ for file ,offset in expected .items ():
1260
+ self .assertEqual (zh .getinfo (file ).header_offset ,offset )
1261
+ self .assertIsNone (zh .testzip ())
1262
+
1263
+ def test_verify (self ):
1264
+ # Test if params are passed to underlying _remove_members() correctly,
1265
+ # or never passed if conditions not met.
1266
+ file0 = 'file0.txt'
1267
+ data0 = b'Lorem ipsum dolor sit amet, consectetur adipiscing elit'
1268
+ file = 'datafile.txt'
1269
+ data = b'Sed ut perspiciatis unde omnis iste natus error sit voluptatem'
1270
+
1271
+ # closed: error and do nothing
1272
+ with zipfile .ZipFile (TESTFN ,'w' )as zh :
1273
+ zh .writestr (file ,data )
1274
+ with zipfile .ZipFile (TESTFN ,'a' )as zh :
1275
+ zh .close ()
1276
+ with mock .patch ('zipfile.ZipFile._remove_members' )as mock_fn :
1277
+ with self .assertRaises (ValueError ):
1278
+ zh .remove (file )
1279
+ mock_fn .assert_not_called ()
1280
+
1281
+ # writing: error and do nothing
1282
+ with zipfile .ZipFile (TESTFN ,'w' )as zh :
1283
+ zh .writestr (file ,data )
1284
+ with zipfile .ZipFile (TESTFN ,'a' )as zh :
1285
+ with mock .patch ('zipfile.ZipFile._remove_members' )as mock_fn :
1286
+ with zh .open (file0 ,'w' )as fh :
1287
+ with self .assertRaises (ValueError ):
1288
+ zh .remove (file )
1289
+ mock_fn .assert_not_called ()
1290
+
1291
+ # mode 'r': error and do nothing
1292
+ with zipfile .ZipFile (TESTFN ,'r' )as zh :
1293
+ with mock .patch ('zipfile.ZipFile._remove_members' )as mock_fn :
1294
+ with self .assertRaises (ValueError ):
1295
+ zh .remove (file )
1296
+ mock_fn .assert_not_called ()
1297
+
1298
+ # mode 'a': the most general use case
1299
+ with zipfile .ZipFile (TESTFN ,'w' )as zh :
1300
+ zh .writestr (file ,data )
1301
+ # -- remove with arcname
1302
+ with zipfile .ZipFile (TESTFN ,'a' )as zh :
1303
+ with mock .patch ('zipfile.ZipFile._remove_members' )as mock_fn :
1304
+ zh .remove (file )
1305
+ mock_fn .assert_called_once_with ({zh .getinfo (file )})
1306
+ # -- remove with zinfo
1307
+ with zipfile .ZipFile (TESTFN ,'a' )as zh :
1308
+ with mock .patch ('zipfile.ZipFile._remove_members' )as mock_fn :
1309
+ zinfo = zh .getinfo (file )
1310
+ zh .remove (zinfo )
1311
+ mock_fn .assert_called_once_with ({zinfo })
1312
+ # -- remove with nonexist arcname
1313
+ with zipfile .ZipFile (TESTFN ,'a' )as zh :
1314
+ with mock .patch ('zipfile.ZipFile._remove_members' )as mock_fn :
1315
+ with self .assertRaises (KeyError ):
1316
+ zh .remove ('nonexist.file' )
1317
+ mock_fn .assert_not_called ()
1318
+ # -- remove with nonexist zinfo (even if same name)
1319
+ with zipfile .ZipFile (TESTFN ,'a' )as zh :
1320
+ with mock .patch ('zipfile.ZipFile._remove_members' )as mock_fn :
1321
+ zinfo = zipfile .ZipInfo (file )
1322
+ with self .assertRaises (KeyError ):
1323
+ zh .remove (zinfo )
1324
+ mock_fn .assert_not_called ()
1325
+
1326
+ # mode 'w': like 'a'; allows removing a just written member
1327
+ with zipfile .ZipFile (TESTFN ,'w' )as zh :
1328
+ zh .writestr (file ,data )
1329
+ with mock .patch ('zipfile.ZipFile._remove_members' )as mock_fn :
1330
+ zh .remove (file )
1331
+ mock_fn .assert_called_once_with ({zh .getinfo (file )})
1332
+
1333
+ # mode 'x': like 'w'
1334
+ os .remove (TESTFN )
1335
+ with zipfile .ZipFile (TESTFN ,'x' )as zh :
1336
+ zh .writestr (file ,data )
1337
+ with mock .patch ('zipfile.ZipFile._remove_members' )as mock_fn :
1338
+ zh .remove (file )
1339
+ mock_fn .assert_called_once_with ({zh .getinfo (file )})
1340
+
1341
+ def test_zip64 (self ):
1342
+ # Test if members use zip64.
1343
+ file = 'datafile.txt'
1344
+ file1 = 'pre.txt'
1345
+ file2 = 'post.txt'
1346
+ data = b'Sed ut perspiciatis unde omnis iste natus error sit voluptatem'
1347
+ data1 = b'Lorem ipsum dolor sit amet, consectetur adipiscing elit'
1348
+ data2 = b'Duis aute irure dolor in reprehenderit in voluptate velit esse'
1349
+ with zipfile .ZipFile (TESTFN ,'w' )as zh :
1350
+ with zh .open (file1 ,'w' ,force_zip64 = True )as fh :
1351
+ fh .write (data1 )
1352
+ with zh .open (file2 ,'w' ,force_zip64 = True )as fh :
1353
+ fh .write (data2 )
1354
+ expected_size = os .path .getsize (TESTFN )
1355
+
1356
+ with zipfile .ZipFile (TESTFN ,'w' )as zh :
1357
+ with zh .open (file1 ,'w' ,force_zip64 = True )as fh :
1358
+ fh .write (data1 )
1359
+ with zh .open (file ,'w' ,force_zip64 = True )as fh :
1360
+ fh .write (data )
1361
+ with zh .open (file2 ,'w' ,force_zip64 = True )as fh :
1362
+ fh .write (data2 )
1363
+ with zipfile .ZipFile (TESTFN ,'a' )as zh :
1364
+ zh .remove (file )
1365
+ self .assertIsNone (zh .testzip ())
1366
+ self .assertEqual (os .path .getsize (TESTFN ),expected_size )
1367
+
1368
+ class StoredRemoveTests (AbstractRemoveTests ,unittest .TestCase ):
1369
+ compression = zipfile .ZIP_STORED
1370
+
1371
+ @requires_zlib ()
1372
+ class DeflateRemoveTests (AbstractRemoveTests ,unittest .TestCase ):
1373
+ compression = zipfile .ZIP_DEFLATED
1374
+
1375
+ @requires_bz2 ()
1376
+ class Bzip2RemoveTests (AbstractRemoveTests ,unittest .TestCase ):
1377
+ compression = zipfile .ZIP_BZIP2
1378
+
1379
+ @requires_lzma ()
1380
+ class LzmaRemoveTests (AbstractRemoveTests ,unittest .TestCase ):
1381
+ compression = zipfile .ZIP_LZMA
1382
+
1383
+
1150
1384
class PyZipFileTests (unittest .TestCase ):
1151
1385
def assertCompiledIn (self ,name ,namelist ):
1152
1386
if name + 'o' not in namelist :