@@ -1673,6 +1673,103 @@ def test_cleanup_with_symlink_to_a_directory(self):
1673
1673
"were deleted")
1674
1674
d2.cleanup()
1675
1675
1676
+ @os_helper.skip_unless_symlink
1677
+ def test_cleanup_with_symlink_modes(self):
1678
+ # cleanup() should not follow symlinks when fixing mode bits (#91133)
1679
+ with self.do_create(recurse=0) as d2:
1680
+ file1 = os.path.join(d2, 'file1')
1681
+ open(file1, 'wb').close()
1682
+ dir1 = os.path.join(d2, 'dir1')
1683
+ os.mkdir(dir1)
1684
+ for mode in range(8):
1685
+ mode <<= 6
1686
+ with self.subTest(mode=format(mode, '03o')):
1687
+ def test(target, target_is_directory):
1688
+ d1 = self.do_create(recurse=0)
1689
+ symlink = os.path.join(d1.name, 'symlink')
1690
+ os.symlink(target, symlink,
1691
+ target_is_directory=target_is_directory)
1692
+ try:
1693
+ os.chmod(symlink, mode, follow_symlinks=False)
1694
+ except NotImplementedError:
1695
+ pass
1696
+ try:
1697
+ os.chmod(symlink, mode)
1698
+ except FileNotFoundError:
1699
+ pass
1700
+ os.chmod(d1.name, mode)
1701
+ d1.cleanup()
1702
+ self.assertFalse(os.path.exists(d1.name))
1703
+
1704
+ with self.subTest('nonexisting file'):
1705
+ test('nonexisting', target_is_directory=False)
1706
+ with self.subTest('nonexisting dir'):
1707
+ test('nonexisting', target_is_directory=True)
1708
+
1709
+ with self.subTest('existing file'):
1710
+ os.chmod(file1, mode)
1711
+ old_mode = os.stat(file1).st_mode
1712
+ test(file1, target_is_directory=False)
1713
+ new_mode = os.stat(file1).st_mode
1714
+ self.assertEqual(new_mode, old_mode,
1715
+ '%03o != %03o' % (new_mode, old_mode))
1716
+
1717
+ with self.subTest('existing dir'):
1718
+ os.chmod(dir1, mode)
1719
+ old_mode = os.stat(dir1).st_mode
1720
+ test(dir1, target_is_directory=True)
1721
+ new_mode = os.stat(dir1).st_mode
1722
+ self.assertEqual(new_mode, old_mode,
1723
+ '%03o != %03o' % (new_mode, old_mode))
1724
+
1725
+ @unittest.skipUnless(hasattr(os, 'chflags'), 'requires os.chflags')
1726
+ @os_helper.skip_unless_symlink
1727
+ def test_cleanup_with_symlink_flags(self):
1728
+ # cleanup() should not follow symlinks when fixing flags (#91133)
1729
+ flags = stat.UF_IMMUTABLE | stat.UF_NOUNLINK
1730
+ self.check_flags(flags)
1731
+
1732
+ with self.do_create(recurse=0) as d2:
1733
+ file1 = os.path.join(d2, 'file1')
1734
+ open(file1, 'wb').close()
1735
+ dir1 = os.path.join(d2, 'dir1')
1736
+ os.mkdir(dir1)
1737
+ def test(target, target_is_directory):
1738
+ d1 = self.do_create(recurse=0)
1739
+ symlink = os.path.join(d1.name, 'symlink')
1740
+ os.symlink(target, symlink,
1741
+ target_is_directory=target_is_directory)
1742
+ try:
1743
+ os.chflags(symlink, flags, follow_symlinks=False)
1744
+ except NotImplementedError:
1745
+ pass
1746
+ try:
1747
+ os.chflags(symlink, flags)
1748
+ except FileNotFoundError:
1749
+ pass
1750
+ os.chflags(d1.name, flags)
1751
+ d1.cleanup()
1752
+ self.assertFalse(os.path.exists(d1.name))
1753
+
1754
+ with self.subTest('nonexisting file'):
1755
+ test('nonexisting', target_is_directory=False)
1756
+ with self.subTest('nonexisting dir'):
1757
+ test('nonexisting', target_is_directory=True)
1758
+
1759
+ with self.subTest('existing file'):
1760
+ os.chflags(file1, flags)
1761
+ old_flags = os.stat(file1).st_flags
1762
+ test(file1, target_is_directory=False)
1763
+ new_flags = os.stat(file1).st_flags
1764
+ self.assertEqual(new_flags, old_flags)
1765
+
1766
+ with self.subTest('existing dir'):
1767
+ os.chflags(dir1, flags)
1768
+ old_flags = os.stat(dir1).st_flags
1769
+ test(dir1, target_is_directory=True)
1770
+ new_flags = os.stat(dir1).st_flags
1771
+ self.assertEqual(new_flags, old_flags)
1772
+
1676
1773
@support.cpython_only
1677
1774
def test_del_on_collection(self):
1678
1775
# A TemporaryDirectory is deleted when garbage collected
@@ -1845,10 +1942,7 @@ def test_modes(self):
1845
1942
d.cleanup()
1846
1943
self.assertFalse(os.path.exists(d.name))
1847
1944
1848
- @unittest.skipUnless(hasattr(os, 'chflags'), 'requires os.chflags')
1849
- def test_flags(self):
1850
- flags = stat.UF_IMMUTABLE | stat.UF_NOUNLINK
1851
-
1945
+ def check_flags(self, flags):
1852
1946
# skip the test if these flags are not supported (ex: FreeBSD 13)
1853
1947
filename = os_helper.TESTFN
1854
1948
try:
@@ -1857,13 +1951,18 @@ def test_flags(self):
1857
1951
os.chflags(filename, flags)
1858
1952
except OSError as exc:
1859
1953
# "OSError: [Errno 45] Operation not supported"
1860
- self.skipTest(f"chflags() doesn't support "
1861
- f"UF_IMMUTABLE|UF_NOUNLINK : {exc}")
1954
+ self.skipTest(f"chflags() doesn't support flags "
1955
+ f"{flags:#b} : {exc}")
1862
1956
else:
1863
1957
os.chflags(filename, 0)
1864
1958
finally:
1865
1959
os_helper.unlink(filename)
1866
1960
1961
+ @unittest.skipUnless(hasattr(os, 'chflags'), 'requires os.chflags')
1962
+ def test_flags(self):
1963
+ flags = stat.UF_IMMUTABLE | stat.UF_NOUNLINK
1964
+ self.check_flags(flags)
1965
+
1867
1966
d = self.do_create(recurse=3, dirs=2, files=2)
1868
1967
with d:
1869
1968
# Change files and directories flags recursively.
0 commit comments