@@ -1147,6 +1147,239 @@ 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+ 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+ file = 'datafile.txt'
1268+ data = b'Sed ut perspiciatis unde omnis iste natus error sit voluptatem'
1269+
1270+ # closed: error and do nothing
1271+ with zipfile .ZipFile (TESTFN , 'w' ) as zh :
1272+ zh .writestr (file , data )
1273+ with zipfile .ZipFile (TESTFN , 'a' ) as zh :
1274+ zh .close ()
1275+ with mock .patch ('zipfile.ZipFile._remove_members' ) as mock_fn :
1276+ with self .assertRaises (ValueError ):
1277+ zh .remove (file )
1278+ mock_fn .assert_not_called ()
1279+
1280+ # writing: error and do nothing
1281+ with zipfile .ZipFile (TESTFN , 'w' ) as zh :
1282+ zh .writestr (file , data )
1283+ with zipfile .ZipFile (TESTFN , 'a' ) as zh :
1284+ with mock .patch ('zipfile.ZipFile._remove_members' ) as mock_fn :
1285+ with zh .open (file0 , 'w' ) as fh :
1286+ with self .assertRaises (ValueError ):
1287+ zh .remove (file )
1288+ mock_fn .assert_not_called ()
1289+
1290+ # mode 'r': error and do nothing
1291+ with zipfile .ZipFile (TESTFN , 'r' ) as zh :
1292+ with mock .patch ('zipfile.ZipFile._remove_members' ) as mock_fn :
1293+ with self .assertRaises (ValueError ):
1294+ zh .remove (file )
1295+ mock_fn .assert_not_called ()
1296+
1297+ # mode 'a': the most general use case
1298+ with zipfile .ZipFile (TESTFN , 'w' ) as zh :
1299+ zh .writestr (file , data )
1300+ # -- remove with arcname
1301+ with zipfile .ZipFile (TESTFN , 'a' ) as zh :
1302+ with mock .patch ('zipfile.ZipFile._remove_members' ) as mock_fn :
1303+ zh .remove (file )
1304+ mock_fn .assert_called_once_with ({zh .getinfo (file )})
1305+ # -- remove with zinfo
1306+ with zipfile .ZipFile (TESTFN , 'a' ) as zh :
1307+ with mock .patch ('zipfile.ZipFile._remove_members' ) as mock_fn :
1308+ zinfo = zh .getinfo (file )
1309+ zh .remove (zinfo )
1310+ mock_fn .assert_called_once_with ({zinfo })
1311+ # -- remove with nonexist arcname
1312+ with zipfile .ZipFile (TESTFN , 'a' ) as zh :
1313+ with mock .patch ('zipfile.ZipFile._remove_members' ) as mock_fn :
1314+ with self .assertRaises (KeyError ):
1315+ zh .remove ('nonexist.file' )
1316+ mock_fn .assert_not_called ()
1317+ # -- remove with nonexist zinfo (even if same name)
1318+ with zipfile .ZipFile (TESTFN , 'a' ) as zh :
1319+ with mock .patch ('zipfile.ZipFile._remove_members' ) as mock_fn :
1320+ zinfo = zipfile .ZipInfo (file )
1321+ with self .assertRaises (KeyError ):
1322+ zh .remove (zinfo )
1323+ mock_fn .assert_not_called ()
1324+
1325+ # mode 'w': like 'a'; allows removing a just written member
1326+ with zipfile .ZipFile (TESTFN , 'w' ) as zh :
1327+ zh .writestr (file , data )
1328+ with mock .patch ('zipfile.ZipFile._remove_members' ) as mock_fn :
1329+ zh .remove (file )
1330+ mock_fn .assert_called_once_with ({zh .getinfo (file )})
1331+
1332+ # mode 'x': like 'w'
1333+ os .remove (TESTFN )
1334+ with zipfile .ZipFile (TESTFN , 'x' ) as zh :
1335+ zh .writestr (file , data )
1336+ with mock .patch ('zipfile.ZipFile._remove_members' ) as mock_fn :
1337+ zh .remove (file )
1338+ mock_fn .assert_called_once_with ({zh .getinfo (file )})
1339+
1340+ def test_zip64 (self ):
1341+ # Test if members use zip64.
1342+ file = 'datafile.txt'
1343+ file1 = 'pre.txt'
1344+ file2 = 'post.txt'
1345+ data = b'Sed ut perspiciatis unde omnis iste natus error sit voluptatem'
1346+ data1 = b'Lorem ipsum dolor sit amet, consectetur adipiscing elit'
1347+ data2 = b'Duis aute irure dolor in reprehenderit in voluptate velit esse'
1348+ with zipfile .ZipFile (TESTFN , 'w' ) as zh :
1349+ with zh .open (file1 , 'w' , force_zip64 = True ) as fh :
1350+ fh .write (data1 )
1351+ with zh .open (file2 , 'w' , force_zip64 = True ) as fh :
1352+ fh .write (data2 )
1353+ expected_size = os .path .getsize (TESTFN )
1354+
1355+ with zipfile .ZipFile (TESTFN , 'w' ) as zh :
1356+ with zh .open (file1 , 'w' , force_zip64 = True ) as fh :
1357+ fh .write (data1 )
1358+ with zh .open (file , 'w' , force_zip64 = True ) as fh :
1359+ fh .write (data )
1360+ with zh .open (file2 , 'w' , force_zip64 = True ) as fh :
1361+ fh .write (data2 )
1362+ with zipfile .ZipFile (TESTFN , 'a' ) as zh :
1363+ zh .remove (file )
1364+ self .assertIsNone (zh .testzip ())
1365+ self .assertEqual (os .path .getsize (TESTFN ), expected_size )
1366+
1367+ class StoredRemoveTests (AbstractRemoveTests , unittest .TestCase ):
1368+ compression = zipfile .ZIP_STORED
1369+
1370+ @requires_zlib ()
1371+ class DeflateRemoveTests (AbstractRemoveTests , unittest .TestCase ):
1372+ compression = zipfile .ZIP_DEFLATED
1373+
1374+ @requires_bz2 ()
1375+ class Bzip2RemoveTests (AbstractRemoveTests , unittest .TestCase ):
1376+ compression = zipfile .ZIP_BZIP2
1377+
1378+ @requires_lzma ()
1379+ class LzmaRemoveTests (AbstractRemoveTests , unittest .TestCase ):
1380+ compression = zipfile .ZIP_LZMA
1381+
1382+
11501383class PyZipFileTests (unittest .TestCase ):
11511384 def assertCompiledIn (self , name , namelist ):
11521385 if name + 'o' not in namelist :
0 commit comments