@@ -1565,6 +1565,103 @@ def test_cleanup_with_symlink_to_a_directory(self):
1565
1565
"were deleted" )
1566
1566
d2 .cleanup ()
1567
1567
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
+
1568
1665
@support .cpython_only
1569
1666
def test_del_on_collection (self ):
1570
1667
# A TemporaryDirectory is deleted when garbage collected
@@ -1737,10 +1834,7 @@ def test_modes(self):
1737
1834
d .cleanup ()
1738
1835
self .assertFalse (os .path .exists (d .name ))
1739
1836
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 ):
1744
1838
# skip the test if these flags are not supported (ex: FreeBSD 13)
1745
1839
filename = os_helper .TESTFN
1746
1840
try :
@@ -1749,13 +1843,18 @@ def test_flags(self):
1749
1843
os .chflags (filename , flags )
1750
1844
except OSError as exc :
1751
1845
# "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 } " )
1754
1848
else :
1755
1849
os .chflags (filename , 0 )
1756
1850
finally :
1757
1851
os_helper .unlink (filename )
1758
1852
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
+
1759
1858
d = self .do_create (recurse = 3 , dirs = 2 , files = 2 )
1760
1859
with d :
1761
1860
# Change files and directories flags recursively.
0 commit comments