@@ -1360,6 +1360,239 @@ class LzmaWriterTests(AbstractWriterTests, unittest.TestCase):
13601360class ZstdWriterTests (AbstractWriterTests , unittest .TestCase ):
13611361 compression = zipfile .ZIP_ZSTANDARD
13621362
1363+ class AbstractRemoveTests :
1364+
1365+ def _test_removing_indexes (self , test_files , indexes ):
1366+ """Test underlying _remove_members() for removing members at given
1367+ indexes."""
1368+ # calculate the expected results
1369+ expected_files = []
1370+ with zipfile .ZipFile (TESTFN , 'w' ) as zh :
1371+ for i , (file , data ) in enumerate (test_files ):
1372+ if i not in indexes :
1373+ zh .writestr (file , data )
1374+ expected_files .append (file )
1375+ expected_size = os .path .getsize (TESTFN )
1376+
1377+ # prepare the test zip
1378+ with zipfile .ZipFile (TESTFN , 'w' ) as zh :
1379+ for file , data in test_files :
1380+ zh .writestr (file , data )
1381+
1382+ # do the removal and check the result
1383+ with zipfile .ZipFile (TESTFN , 'a' ) as zh :
1384+ members = {zh .infolist ()[i ] for i in indexes }
1385+ zh ._remove_members (members )
1386+
1387+ # make sure internal caches have reflected the change
1388+ # and are consistent
1389+ self .assertEqual (zh .namelist (), expected_files )
1390+ for file , _ in test_files :
1391+ if file in zh .namelist ():
1392+ self .assertEqual (zh .getinfo (file ).filename , file )
1393+ else :
1394+ with self .assertRaises (KeyError ):
1395+ zh .getinfo (file )
1396+
1397+ self .assertIsNone (zh .testzip ())
1398+ self .assertEqual (os .path .getsize (TESTFN ), expected_size )
1399+
1400+ def _test_removing_combinations (self , test_files , n = None ):
1401+ """Test underlying _remove_members() for removing random combinations
1402+ of members."""
1403+ ln = len (test_files )
1404+ if n is None :
1405+ # iterate n from 1 to all
1406+ for n in range (1 , ln + 1 ):
1407+ for indexes in itertools .combinations (range (ln ), n ):
1408+ with self .subTest (remove = indexes ):
1409+ self ._test_removing_indexes (test_files , indexes )
1410+ else :
1411+ for indexes in itertools .combinations (range (ln ), n ):
1412+ with self .subTest (remove = indexes ):
1413+ self ._test_removing_indexes (test_files , indexes )
1414+
1415+ def test_basic (self ):
1416+ # Test underlying _remove_members() for removing random combinations of members.
1417+ test_files = [
1418+ ('file0.txt' , b'Lorem ipsum dolor sit amet, consectetur adipiscing elit' ),
1419+ ('file1.txt' , b'Duis aute irure dolor in reprehenderit in voluptate velit esse' ),
1420+ ('file2.txt' , b'Sed ut perspiciatis unde omnis iste natus error sit voluptatem' ),
1421+ ]
1422+
1423+ self ._test_removing_combinations (test_files )
1424+
1425+ def test_duplicated_arcname (self ):
1426+ # Test underlying _remove_members() for removing any one of random duplicated members.
1427+ dupl_file = 'file.txt'
1428+ test_files = [
1429+ ('file0.txt' , b'Lorem ipsum dolor sit amet, consectetur adipiscing elit' ),
1430+ ('file1.txt' , b'Duis aute irure dolor in reprehenderit in voluptate velit esse' ),
1431+ ('file2.txt' , b'Sed ut perspiciatis unde omnis iste natus error sit voluptatem' ),
1432+ ]
1433+
1434+ ln = len (test_files )
1435+ for n in range (2 , ln + 1 ):
1436+ for dups in itertools .combinations (range (ln ), n ):
1437+ files = []
1438+ for i , (file , data ) in enumerate (test_files ):
1439+ file_ = dupl_file if i in dups else file
1440+ files .append ((file_ , data ))
1441+
1442+ for index in dups :
1443+ indexes = [index ]
1444+ with self .subTest (dups = dups , indexes = indexes ):
1445+ self ._test_removing_indexes (files , indexes )
1446+
1447+ def test_non_physical (self ):
1448+ # Test underlying _remove_members() for non-physical removing.
1449+ test_files = [
1450+ ('file0.txt' , b'Lorem ipsum dolor sit amet, consectetur adipiscing elit' ),
1451+ ('file1.txt' , b'Duis aute irure dolor in reprehenderit in voluptate velit esse' ),
1452+ ('file2.txt' , b'Sed ut perspiciatis unde omnis iste natus error sit voluptatem' ),
1453+ ]
1454+
1455+ ln = len (test_files )
1456+ for n in range (1 , ln + 1 ):
1457+ for indexes in itertools .combinations (range (ln ), n ):
1458+ with self .subTest (remove = indexes ):
1459+ # prepare the test zip
1460+ expected = {}
1461+ with zipfile .ZipFile (TESTFN , 'w' ) as zh :
1462+ for i , (file , data ) in enumerate (test_files ):
1463+ zh .writestr (file , data )
1464+ if i not in indexes :
1465+ expected [file ] = zh .getinfo (file ).header_offset
1466+
1467+ # do the removal and check the result
1468+ with zipfile .ZipFile (TESTFN , 'a' ) as zh :
1469+ members = {zh .infolist ()[i ] for i in indexes }
1470+ zh ._remove_members (members , remove_physical = False )
1471+ self .assertEqual (zh .namelist (), list (expected ))
1472+ for file , offset in expected .items ():
1473+ self .assertEqual (zh .getinfo (file ).header_offset , offset )
1474+ self .assertIsNone (zh .testzip ())
1475+
1476+ def test_verify (self ):
1477+ # Test if params are passed to underlying _remove_members() correctly,
1478+ # or never passed if conditions not met.
1479+ file0 = 'file0.txt'
1480+ file = 'datafile.txt'
1481+ data = b'Sed ut perspiciatis unde omnis iste natus error sit voluptatem'
1482+
1483+ # closed: error and do nothing
1484+ with zipfile .ZipFile (TESTFN , 'w' ) as zh :
1485+ zh .writestr (file , data )
1486+ with zipfile .ZipFile (TESTFN , 'a' ) as zh :
1487+ zh .close ()
1488+ with mock .patch ('zipfile.ZipFile._remove_members' ) as mock_fn :
1489+ with self .assertRaises (ValueError ):
1490+ zh .remove (file )
1491+ mock_fn .assert_not_called ()
1492+
1493+ # writing: error and do nothing
1494+ with zipfile .ZipFile (TESTFN , 'w' ) as zh :
1495+ zh .writestr (file , data )
1496+ with zipfile .ZipFile (TESTFN , 'a' ) as zh :
1497+ with mock .patch ('zipfile.ZipFile._remove_members' ) as mock_fn :
1498+ with zh .open (file0 , 'w' ) as fh :
1499+ with self .assertRaises (ValueError ):
1500+ zh .remove (file )
1501+ mock_fn .assert_not_called ()
1502+
1503+ # mode 'r': error and do nothing
1504+ with zipfile .ZipFile (TESTFN , 'r' ) as zh :
1505+ with mock .patch ('zipfile.ZipFile._remove_members' ) as mock_fn :
1506+ with self .assertRaises (ValueError ):
1507+ zh .remove (file )
1508+ mock_fn .assert_not_called ()
1509+
1510+ # mode 'a': the most general use case
1511+ with zipfile .ZipFile (TESTFN , 'w' ) as zh :
1512+ zh .writestr (file , data )
1513+ # -- remove with arcname
1514+ with zipfile .ZipFile (TESTFN , 'a' ) as zh :
1515+ with mock .patch ('zipfile.ZipFile._remove_members' ) as mock_fn :
1516+ zh .remove (file )
1517+ mock_fn .assert_called_once_with ({zh .getinfo (file )})
1518+ # -- remove with zinfo
1519+ with zipfile .ZipFile (TESTFN , 'a' ) as zh :
1520+ with mock .patch ('zipfile.ZipFile._remove_members' ) as mock_fn :
1521+ zinfo = zh .getinfo (file )
1522+ zh .remove (zinfo )
1523+ mock_fn .assert_called_once_with ({zinfo })
1524+ # -- remove with nonexist arcname
1525+ with zipfile .ZipFile (TESTFN , 'a' ) as zh :
1526+ with mock .patch ('zipfile.ZipFile._remove_members' ) as mock_fn :
1527+ with self .assertRaises (KeyError ):
1528+ zh .remove ('nonexist.file' )
1529+ mock_fn .assert_not_called ()
1530+ # -- remove with nonexist zinfo (even if same name)
1531+ with zipfile .ZipFile (TESTFN , 'a' ) as zh :
1532+ with mock .patch ('zipfile.ZipFile._remove_members' ) as mock_fn :
1533+ zinfo = zipfile .ZipInfo (file )
1534+ with self .assertRaises (KeyError ):
1535+ zh .remove (zinfo )
1536+ mock_fn .assert_not_called ()
1537+
1538+ # mode 'w': like 'a'; allows removing a just written member
1539+ with zipfile .ZipFile (TESTFN , 'w' ) as zh :
1540+ zh .writestr (file , data )
1541+ with mock .patch ('zipfile.ZipFile._remove_members' ) as mock_fn :
1542+ zh .remove (file )
1543+ mock_fn .assert_called_once_with ({zh .getinfo (file )})
1544+
1545+ # mode 'x': like 'w'
1546+ os .remove (TESTFN )
1547+ with zipfile .ZipFile (TESTFN , 'x' ) as zh :
1548+ zh .writestr (file , data )
1549+ with mock .patch ('zipfile.ZipFile._remove_members' ) as mock_fn :
1550+ zh .remove (file )
1551+ mock_fn .assert_called_once_with ({zh .getinfo (file )})
1552+
1553+ def test_zip64 (self ):
1554+ # Test if members use zip64.
1555+ file = 'datafile.txt'
1556+ file1 = 'pre.txt'
1557+ file2 = 'post.txt'
1558+ data = b'Sed ut perspiciatis unde omnis iste natus error sit voluptatem'
1559+ data1 = b'Lorem ipsum dolor sit amet, consectetur adipiscing elit'
1560+ data2 = b'Duis aute irure dolor in reprehenderit in voluptate velit esse'
1561+ with zipfile .ZipFile (TESTFN , 'w' ) as zh :
1562+ with zh .open (file1 , 'w' , force_zip64 = True ) as fh :
1563+ fh .write (data1 )
1564+ with zh .open (file2 , 'w' , force_zip64 = True ) as fh :
1565+ fh .write (data2 )
1566+ expected_size = os .path .getsize (TESTFN )
1567+
1568+ with zipfile .ZipFile (TESTFN , 'w' ) as zh :
1569+ with zh .open (file1 , 'w' , force_zip64 = True ) as fh :
1570+ fh .write (data1 )
1571+ with zh .open (file , 'w' , force_zip64 = True ) as fh :
1572+ fh .write (data )
1573+ with zh .open (file2 , 'w' , force_zip64 = True ) as fh :
1574+ fh .write (data2 )
1575+ with zipfile .ZipFile (TESTFN , 'a' ) as zh :
1576+ zh .remove (file )
1577+ self .assertIsNone (zh .testzip ())
1578+ self .assertEqual (os .path .getsize (TESTFN ), expected_size )
1579+
1580+ class StoredRemoveTests (AbstractRemoveTests , unittest .TestCase ):
1581+ compression = zipfile .ZIP_STORED
1582+
1583+ @requires_zlib ()
1584+ class DeflateRemoveTests (AbstractRemoveTests , unittest .TestCase ):
1585+ compression = zipfile .ZIP_DEFLATED
1586+
1587+ @requires_bz2 ()
1588+ class Bzip2RemoveTests (AbstractRemoveTests , unittest .TestCase ):
1589+ compression = zipfile .ZIP_BZIP2
1590+
1591+ @requires_lzma ()
1592+ class LzmaRemoveTests (AbstractRemoveTests , unittest .TestCase ):
1593+ compression = zipfile .ZIP_LZMA
1594+
1595+
13631596class PyZipFileTests (unittest .TestCase ):
13641597 def assertCompiledIn (self , name , namelist ):
13651598 if name + 'o' not in namelist :
0 commit comments