@@ -1565,6 +1565,103 @@ def test_cleanup_with_symlink_to_a_directory(self):
15651565 "were deleted" )
15661566 d2 .cleanup ()
15671567
1568+ @os_helper .skip_unless_symlink
1569+ def test_cleanup_with_symlink_modes (self ):
1570+ # cleanup() should not follow symlinks when fixing mode bits (#91133)
1571+ with self .do_create (recurse = 0 ) as d2 :
1572+ file1 = os .path .join (d2 , 'file1' )
1573+ open (file1 , 'wb' ).close ()
1574+ dir1 = os .path .join (d2 , 'dir1' )
1575+ os .mkdir (dir1 )
1576+ for mode in range (8 ):
1577+ mode <<= 6
1578+ with self .subTest (mode = format (mode , '03o' )):
1579+ def test (target , target_is_directory ):
1580+ d1 = self .do_create (recurse = 0 )
1581+ symlink = os .path .join (d1 .name , 'symlink' )
1582+ os .symlink (target , symlink ,
1583+ target_is_directory = target_is_directory )
1584+ try :
1585+ os .chmod (symlink , mode , follow_symlinks = False )
1586+ except NotImplementedError :
1587+ pass
1588+ try :
1589+ os .chmod (symlink , mode )
1590+ except FileNotFoundError :
1591+ pass
1592+ os .chmod (d1 .name , mode )
1593+ d1 .cleanup ()
1594+ self .assertFalse (os .path .exists (d1 .name ))
1595+
1596+ with self .subTest ('nonexisting file' ):
1597+ test ('nonexisting' , target_is_directory = False )
1598+ with self .subTest ('nonexisting dir' ):
1599+ test ('nonexisting' , target_is_directory = True )
1600+
1601+ with self .subTest ('existing file' ):
1602+ os .chmod (file1 , mode )
1603+ old_mode = os .stat (file1 ).st_mode
1604+ test (file1 , target_is_directory = False )
1605+ new_mode = os .stat (file1 ).st_mode
1606+ self .assertEqual (new_mode , old_mode ,
1607+ '%03o != %03o' % (new_mode , old_mode ))
1608+
1609+ with self .subTest ('existing dir' ):
1610+ os .chmod (dir1 , mode )
1611+ old_mode = os .stat (dir1 ).st_mode
1612+ test (dir1 , target_is_directory = True )
1613+ new_mode = os .stat (dir1 ).st_mode
1614+ self .assertEqual (new_mode , old_mode ,
1615+ '%03o != %03o' % (new_mode , old_mode ))
1616+
1617+ @unittest .skipUnless (hasattr (os , 'chflags' ), 'requires os.chflags' )
1618+ @os_helper .skip_unless_symlink
1619+ def test_cleanup_with_symlink_flags (self ):
1620+ # cleanup() should not follow symlinks when fixing flags (#91133)
1621+ flags = stat .UF_IMMUTABLE | stat .UF_NOUNLINK
1622+ self .check_flags (flags )
1623+
1624+ with self .do_create (recurse = 0 ) as d2 :
1625+ file1 = os .path .join (d2 , 'file1' )
1626+ open (file1 , 'wb' ).close ()
1627+ dir1 = os .path .join (d2 , 'dir1' )
1628+ os .mkdir (dir1 )
1629+ def test (target , target_is_directory ):
1630+ d1 = self .do_create (recurse = 0 )
1631+ symlink = os .path .join (d1 .name , 'symlink' )
1632+ os .symlink (target , symlink ,
1633+ target_is_directory = target_is_directory )
1634+ try :
1635+ os .chflags (symlink , flags , follow_symlinks = False )
1636+ except NotImplementedError :
1637+ pass
1638+ try :
1639+ os .chflags (symlink , flags )
1640+ except FileNotFoundError :
1641+ pass
1642+ os .chflags (d1 .name , flags )
1643+ d1 .cleanup ()
1644+ self .assertFalse (os .path .exists (d1 .name ))
1645+
1646+ with self .subTest ('nonexisting file' ):
1647+ test ('nonexisting' , target_is_directory = False )
1648+ with self .subTest ('nonexisting dir' ):
1649+ test ('nonexisting' , target_is_directory = True )
1650+
1651+ with self .subTest ('existing file' ):
1652+ os .chflags (file1 , flags )
1653+ old_flags = os .stat (file1 ).st_flags
1654+ test (file1 , target_is_directory = False )
1655+ new_flags = os .stat (file1 ).st_flags
1656+ self .assertEqual (new_flags , old_flags )
1657+
1658+ with self .subTest ('existing dir' ):
1659+ os .chflags (dir1 , flags )
1660+ old_flags = os .stat (dir1 ).st_flags
1661+ test (dir1 , target_is_directory = True )
1662+ new_flags = os .stat (dir1 ).st_flags
1663+ self .assertEqual (new_flags , old_flags )
1664+
15681665 @support .cpython_only
15691666 def test_del_on_collection (self ):
15701667 # A TemporaryDirectory is deleted when garbage collected
@@ -1737,10 +1834,7 @@ def test_modes(self):
17371834 d .cleanup ()
17381835 self .assertFalse (os .path .exists (d .name ))
17391836
1740- @unittest .skipUnless (hasattr (os , 'chflags' ), 'requires os.chflags' )
1741- def test_flags (self ):
1742- flags = stat .UF_IMMUTABLE | stat .UF_NOUNLINK
1743-
1837+ def check_flags (self , flags ):
17441838 # skip the test if these flags are not supported (ex: FreeBSD 13)
17451839 filename = os_helper .TESTFN
17461840 try :
@@ -1749,13 +1843,18 @@ def test_flags(self):
17491843 os .chflags (filename , flags )
17501844 except OSError as exc :
17511845 # "OSError: [Errno 45] Operation not supported"
1752- self .skipTest (f"chflags() doesn't support "
1753- f"UF_IMMUTABLE|UF_NOUNLINK : { exc } " )
1846+ self .skipTest (f"chflags() doesn't support flags "
1847+ f"{ flags :#b } : { exc } " )
17541848 else :
17551849 os .chflags (filename , 0 )
17561850 finally :
17571851 os_helper .unlink (filename )
17581852
1853+ @unittest .skipUnless (hasattr (os , 'chflags' ), 'requires os.chflags' )
1854+ def test_flags (self ):
1855+ flags = stat .UF_IMMUTABLE | stat .UF_NOUNLINK
1856+ self .check_flags (flags )
1857+
17591858 d = self .do_create (recurse = 3 , dirs = 2 , files = 2 )
17601859 with d :
17611860 # Change files and directories flags recursively.
0 commit comments