@@ -1147,6 +1147,234 @@ class LzmaWriterTests(AbstractWriterTests, unittest.TestCase):
11471147 compression = zipfile .ZIP_LZMA
11481148
11491149
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+ # prepare the test zip
1243+ expected_offset = {}
1244+ with zipfile .ZipFile (TESTFN , 'w' ) as zh :
1245+ for file , data in test_files :
1246+ zh .writestr (file , data )
1247+ expected_offset [file ] = zh .getinfo (file ).header_offset
1248+
1249+ # do the removal and check the result
1250+ with zipfile .ZipFile (TESTFN , 'a' ) as zh :
1251+ members = {zh .getinfo ('file1.txt' )}
1252+ zh ._remove_members (members , remove_physical = False )
1253+ self .assertEqual (zh .namelist (), ['file0.txt' , 'file2.txt' ])
1254+ self .assertEqual (zh .getinfo (file ).header_offset , expected_offset [file ])
1255+ self .assertIsNone (zh .testzip ())
1256+
1257+ def test_verify (self ):
1258+ # Test if params are passed to underlying _remove_members() correctly,
1259+ # or never passed if conditions not met.
1260+ file0 = 'file0.txt'
1261+ data0 = b'Lorem ipsum dolor sit amet, consectetur adipiscing elit'
1262+ file = 'datafile.txt'
1263+ data = b'Sed ut perspiciatis unde omnis iste natus error sit voluptatem'
1264+
1265+ # closed: error and do nothing
1266+ with zipfile .ZipFile (TESTFN , 'w' ) as zh :
1267+ zh .writestr (file , data )
1268+ with zipfile .ZipFile (TESTFN , 'a' ) as zh :
1269+ zh .close ()
1270+ with mock .patch ('zipfile.ZipFile._remove_members' ) as mock_fn :
1271+ with self .assertRaises (ValueError ):
1272+ zh .remove (file )
1273+ mock_fn .assert_not_called ()
1274+
1275+ # writing: error and do nothing
1276+ with zipfile .ZipFile (TESTFN , 'w' ) as zh :
1277+ zh .writestr (file , data )
1278+ with zipfile .ZipFile (TESTFN , 'a' ) as zh :
1279+ with mock .patch ('zipfile.ZipFile._remove_members' ) as mock_fn :
1280+ with zh .open (file0 , 'w' ) as fh :
1281+ with self .assertRaises (ValueError ):
1282+ zh .remove (file )
1283+ mock_fn .assert_not_called ()
1284+
1285+ # mode 'r': error and do nothing
1286+ with zipfile .ZipFile (TESTFN , 'r' ) as zh :
1287+ with mock .patch ('zipfile.ZipFile._remove_members' ) as mock_fn :
1288+ with self .assertRaises (ValueError ):
1289+ zh .remove (file )
1290+ mock_fn .assert_not_called ()
1291+
1292+ # mode 'a': the most general use case
1293+ with zipfile .ZipFile (TESTFN , 'w' ) as zh :
1294+ zh .writestr (file , data )
1295+ # -- remove with arcname
1296+ with zipfile .ZipFile (TESTFN , 'a' ) as zh :
1297+ with mock .patch ('zipfile.ZipFile._remove_members' ) as mock_fn :
1298+ zh .remove (file )
1299+ mock_fn .assert_called_once_with ({zh .getinfo (file )})
1300+ # -- remove with zinfo
1301+ with zipfile .ZipFile (TESTFN , 'a' ) as zh :
1302+ with mock .patch ('zipfile.ZipFile._remove_members' ) as mock_fn :
1303+ zinfo = zh .getinfo (file )
1304+ zh .remove (zinfo )
1305+ mock_fn .assert_called_once_with ({zinfo })
1306+ # -- remove with nonexist arcname
1307+ with zipfile .ZipFile (TESTFN , 'a' ) as zh :
1308+ with mock .patch ('zipfile.ZipFile._remove_members' ) as mock_fn :
1309+ with self .assertRaises (KeyError ):
1310+ zh .remove ('nonexist.file' )
1311+ mock_fn .assert_not_called ()
1312+ # -- remove with nonexist zinfo (even if same name)
1313+ with zipfile .ZipFile (TESTFN , 'a' ) as zh :
1314+ with mock .patch ('zipfile.ZipFile._remove_members' ) as mock_fn :
1315+ zinfo = zipfile .ZipInfo (file )
1316+ with self .assertRaises (KeyError ):
1317+ zh .remove (zinfo )
1318+ mock_fn .assert_not_called ()
1319+
1320+ # mode 'w': like 'a'; allows removing a just written member
1321+ with zipfile .ZipFile (TESTFN , 'w' ) as zh :
1322+ zh .writestr (file , data )
1323+ with mock .patch ('zipfile.ZipFile._remove_members' ) as mock_fn :
1324+ zh .remove (file )
1325+ mock_fn .assert_called_once_with ({zh .getinfo (file )})
1326+
1327+ # mode 'x': like 'w'
1328+ os .remove (TESTFN )
1329+ with zipfile .ZipFile (TESTFN , 'x' ) as zh :
1330+ zh .writestr (file , data )
1331+ with mock .patch ('zipfile.ZipFile._remove_members' ) as mock_fn :
1332+ zh .remove (file )
1333+ mock_fn .assert_called_once_with ({zh .getinfo (file )})
1334+
1335+ def test_zip64 (self ):
1336+ # Test if members use zip64.
1337+ file = 'datafile.txt'
1338+ file1 = 'pre.txt'
1339+ file2 = 'post.txt'
1340+ data = b'Sed ut perspiciatis unde omnis iste natus error sit voluptatem'
1341+ data1 = b'Lorem ipsum dolor sit amet, consectetur adipiscing elit'
1342+ data2 = b'Duis aute irure dolor in reprehenderit in voluptate velit esse'
1343+ with zipfile .ZipFile (TESTFN , 'w' ) as zh :
1344+ with zh .open (file1 , 'w' , force_zip64 = True ) as fh :
1345+ fh .write (data1 )
1346+ with zh .open (file2 , 'w' , force_zip64 = True ) as fh :
1347+ fh .write (data2 )
1348+ expected_size = os .path .getsize (TESTFN )
1349+
1350+ with zipfile .ZipFile (TESTFN , 'w' ) as zh :
1351+ with zh .open (file1 , 'w' , force_zip64 = True ) as fh :
1352+ fh .write (data1 )
1353+ with zh .open (file , 'w' , force_zip64 = True ) as fh :
1354+ fh .write (data )
1355+ with zh .open (file2 , 'w' , force_zip64 = True ) as fh :
1356+ fh .write (data2 )
1357+ with zipfile .ZipFile (TESTFN , 'a' ) as zh :
1358+ zh .remove (file )
1359+ self .assertIsNone (zh .testzip ())
1360+ self .assertEqual (os .path .getsize (TESTFN ), expected_size )
1361+
1362+ class StoredRemoveTests (AbstractRemoveTests , unittest .TestCase ):
1363+ compression = zipfile .ZIP_STORED
1364+
1365+ @requires_zlib ()
1366+ class DeflateRemoveTests (AbstractRemoveTests , unittest .TestCase ):
1367+ compression = zipfile .ZIP_DEFLATED
1368+
1369+ @requires_bz2 ()
1370+ class Bzip2RemoveTests (AbstractRemoveTests , unittest .TestCase ):
1371+ compression = zipfile .ZIP_BZIP2
1372+
1373+ @requires_lzma ()
1374+ class LzmaRemoveTests (AbstractRemoveTests , unittest .TestCase ):
1375+ compression = zipfile .ZIP_LZMA
1376+
1377+
11501378class PyZipFileTests (unittest .TestCase ):
11511379 def assertCompiledIn (self , name , namelist ):
11521380 if name + 'o' not in namelist :
0 commit comments