@@ -1499,6 +1499,103 @@ def test_cleanup_with_symlink_to_a_directory(self):
1499
1499
"were deleted" )
1500
1500
d2 .cleanup ()
1501
1501
1502
+ @os_helper .skip_unless_symlink
1503
+ def test_cleanup_with_symlink_modes (self ):
1504
+ # cleanup() should not follow symlinks when fixing mode bits (#91133)
1505
+ with self .do_create (recurse = 0 ) as d2 :
1506
+ file1 = os .path .join (d2 , 'file1' )
1507
+ open (file1 , 'wb' ).close ()
1508
+ dir1 = os .path .join (d2 , 'dir1' )
1509
+ os .mkdir (dir1 )
1510
+ for mode in range (8 ):
1511
+ mode <<= 6
1512
+ with self .subTest (mode = format (mode , '03o' )):
1513
+ def test (target , target_is_directory ):
1514
+ d1 = self .do_create (recurse = 0 )
1515
+ symlink = os .path .join (d1 .name , 'symlink' )
1516
+ os .symlink (target , symlink ,
1517
+ target_is_directory = target_is_directory )
1518
+ try :
1519
+ os .chmod (symlink , mode , follow_symlinks = False )
1520
+ except NotImplementedError :
1521
+ pass
1522
+ try :
1523
+ os .chmod (symlink , mode )
1524
+ except FileNotFoundError :
1525
+ pass
1526
+ os .chmod (d1 .name , mode )
1527
+ d1 .cleanup ()
1528
+ self .assertFalse (os .path .exists (d1 .name ))
1529
+
1530
+ with self .subTest ('nonexisting file' ):
1531
+ test ('nonexisting' , target_is_directory = False )
1532
+ with self .subTest ('nonexisting dir' ):
1533
+ test ('nonexisting' , target_is_directory = True )
1534
+
1535
+ with self .subTest ('existing file' ):
1536
+ os .chmod (file1 , mode )
1537
+ old_mode = os .stat (file1 ).st_mode
1538
+ test (file1 , target_is_directory = False )
1539
+ new_mode = os .stat (file1 ).st_mode
1540
+ self .assertEqual (new_mode , old_mode ,
1541
+ '%03o != %03o' % (new_mode , old_mode ))
1542
+
1543
+ with self .subTest ('existing dir' ):
1544
+ os .chmod (dir1 , mode )
1545
+ old_mode = os .stat (dir1 ).st_mode
1546
+ test (dir1 , target_is_directory = True )
1547
+ new_mode = os .stat (dir1 ).st_mode
1548
+ self .assertEqual (new_mode , old_mode ,
1549
+ '%03o != %03o' % (new_mode , old_mode ))
1550
+
1551
+ @unittest .skipUnless (hasattr (os , 'chflags' ), 'requires os.chflags' )
1552
+ @os_helper .skip_unless_symlink
1553
+ def test_cleanup_with_symlink_flags (self ):
1554
+ # cleanup() should not follow symlinks when fixing flags (#91133)
1555
+ flags = stat .UF_IMMUTABLE | stat .UF_NOUNLINK
1556
+ self .check_flags (flags )
1557
+
1558
+ with self .do_create (recurse = 0 ) as d2 :
1559
+ file1 = os .path .join (d2 , 'file1' )
1560
+ open (file1 , 'wb' ).close ()
1561
+ dir1 = os .path .join (d2 , 'dir1' )
1562
+ os .mkdir (dir1 )
1563
+ def test (target , target_is_directory ):
1564
+ d1 = self .do_create (recurse = 0 )
1565
+ symlink = os .path .join (d1 .name , 'symlink' )
1566
+ os .symlink (target , symlink ,
1567
+ target_is_directory = target_is_directory )
1568
+ try :
1569
+ os .chflags (symlink , flags , follow_symlinks = False )
1570
+ except NotImplementedError :
1571
+ pass
1572
+ try :
1573
+ os .chflags (symlink , flags )
1574
+ except FileNotFoundError :
1575
+ pass
1576
+ os .chflags (d1 .name , flags )
1577
+ d1 .cleanup ()
1578
+ self .assertFalse (os .path .exists (d1 .name ))
1579
+
1580
+ with self .subTest ('nonexisting file' ):
1581
+ test ('nonexisting' , target_is_directory = False )
1582
+ with self .subTest ('nonexisting dir' ):
1583
+ test ('nonexisting' , target_is_directory = True )
1584
+
1585
+ with self .subTest ('existing file' ):
1586
+ os .chflags (file1 , flags )
1587
+ old_flags = os .stat (file1 ).st_flags
1588
+ test (file1 , target_is_directory = False )
1589
+ new_flags = os .stat (file1 ).st_flags
1590
+ self .assertEqual (new_flags , old_flags )
1591
+
1592
+ with self .subTest ('existing dir' ):
1593
+ os .chflags (dir1 , flags )
1594
+ old_flags = os .stat (dir1 ).st_flags
1595
+ test (dir1 , target_is_directory = True )
1596
+ new_flags = os .stat (dir1 ).st_flags
1597
+ self .assertEqual (new_flags , old_flags )
1598
+
1502
1599
@support .cpython_only
1503
1600
def test_del_on_collection (self ):
1504
1601
# A TemporaryDirectory is deleted when garbage collected
@@ -1671,9 +1768,27 @@ def test_modes(self):
1671
1768
d .cleanup ()
1672
1769
self .assertFalse (os .path .exists (d .name ))
1673
1770
1674
- @unittest .skipUnless (hasattr (os , 'chflags' ), 'requires os.lchflags' )
1771
+ def check_flags (self , flags ):
1772
+ # skip the test if these flags are not supported (ex: FreeBSD 13)
1773
+ filename = os_helper .TESTFN
1774
+ try :
1775
+ open (filename , "w" ).close ()
1776
+ try :
1777
+ os .chflags (filename , flags )
1778
+ except OSError as exc :
1779
+ # "OSError: [Errno 45] Operation not supported"
1780
+ self .skipTest (f"chflags() doesn't support flags "
1781
+ f"{ flags :#b} : { exc } " )
1782
+ else :
1783
+ os .chflags (filename , 0 )
1784
+ finally :
1785
+ os_helper .unlink (filename )
1786
+
1787
+ @unittest .skipUnless (hasattr (os , 'chflags' ), 'requires os.chflags' )
1675
1788
def test_flags (self ):
1676
1789
flags = stat .UF_IMMUTABLE | stat .UF_NOUNLINK
1790
+ self .check_flags (flags )
1791
+
1677
1792
d = self .do_create (recurse = 3 , dirs = 2 , files = 2 )
1678
1793
with d :
1679
1794
# Change files and directories flags recursively.
0 commit comments