@@ -1652,6 +1652,45 @@ def test_cleanup_with_symlink_to_a_directory(self):
1652
1652
"were deleted" )
1653
1653
d2 .cleanup ()
1654
1654
1655
+ @os_helper .skip_unless_symlink
1656
+ @os_helper .skip_unless_working_chmod
1657
+ @unittest .skipUnless (os .chmod in os .supports_follow_symlinks , 'needs chmod follow_symlinks support' )
1658
+ def test_cleanup_with_error_deleting_symlink (self ):
1659
+ # cleanup() should not follow symlinks when fixing mode bits etc. (#91133)
1660
+ d1 = self .do_create ()
1661
+ d2 = self .do_create (recurse = 0 )
1662
+
1663
+ # Symlink d1/my_symlink -> d2, then give d2 a custom mode to see if changes.
1664
+ os .symlink (d2 .name , os .path .join (d1 .name , "my_symlink" ))
1665
+ os .chmod (d2 .name , 0o567 )
1666
+ expected_mode = os .stat (d2 .name ).st_mode # can be impacted by umask etc.
1667
+
1668
+ # There are a variety of reasons why the OS may raise a PermissionError,
1669
+ # but provoking those reliably and cross-platform is not straightforward,
1670
+ # so raise the error synthetically instead.
1671
+ real_unlink = os .unlink
1672
+ error_was_raised = False
1673
+ def patched_unlink (path , ** kwargs ):
1674
+ nonlocal error_was_raised
1675
+ # unlink may be called with full path or path relative to 'fd' kwarg.
1676
+ if path .endswith ("my_symlink" ) and not error_was_raised :
1677
+ error_was_raised = True
1678
+ raise PermissionError ()
1679
+ real_unlink (path , ** kwargs )
1680
+
1681
+ with mock .patch ("tempfile._os.unlink" , patched_unlink ):
1682
+ # This call to cleanup() should not follow my_symlink when fixing permissions
1683
+ d1 .cleanup ()
1684
+
1685
+ self .assertTrue (error_was_raised , "did not see expected 'unlink' call" )
1686
+ self .assertFalse (os .path .exists (d1 .name ),
1687
+ "TemporaryDirectory %s exists after cleanup" % d1 .name )
1688
+ self .assertTrue (os .path .exists (d2 .name ),
1689
+ "Directory pointed to by a symlink was deleted" )
1690
+ self .assertEqual (os .stat (d2 .name ).st_mode , expected_mode ,
1691
+ "Mode of the directory pointed to by a symlink changed" )
1692
+ d2 .cleanup ()
1693
+
1655
1694
@support .cpython_only
1656
1695
def test_del_on_collection (self ):
1657
1696
# A TemporaryDirectory is deleted when garbage collected
0 commit comments