@@ -1298,19 +1298,25 @@ def __exit__(self, *exc_info):
12981298class TestTemporaryDirectory (BaseTestCase ):
12991299 """Test TemporaryDirectory()."""
13001300
1301- def do_create (self , dir = None , pre = "" , suf = "" , recurse = 1 ):
1301+ def do_create (self , dir = None , pre = "" , suf = "" , recurse = 1 , dirs = 1 , files = 1 ):
13021302 if dir is None :
13031303 dir = tempfile .gettempdir ()
13041304 tmp = tempfile .TemporaryDirectory (dir = dir , prefix = pre , suffix = suf )
13051305 self .nameCheck (tmp .name , dir , pre , suf )
1306- # Create a subdirectory and some files
1307- if recurse :
1308- d1 = self .do_create (tmp .name , pre , suf , recurse - 1 )
1309- d1 .name = None
1310- with open (os .path .join (tmp .name , "test.txt" ), "wb" ) as f :
1311- f .write (b"Hello world!" )
1306+ self .do_create2 (tmp .name , recurse , dirs , files )
13121307 return tmp
13131308
1309+ def do_create2 (self , path , recurse = 1 , dirs = 1 , files = 1 ):
1310+ # Create subdirectories and some files
1311+ if recurse :
1312+ for i in range (dirs ):
1313+ name = os .path .join (path , "dir%d" % i )
1314+ os .mkdir (name )
1315+ self .do_create2 (name , recurse - 1 , dirs , files )
1316+ for i in range (files ):
1317+ with open (os .path .join (path , "test%d.txt" % i ), "wb" ) as f :
1318+ f .write (b"Hello world!" )
1319+
13141320 def test_mkdtemp_failure (self ):
13151321 # Check no additional exception if mkdtemp fails
13161322 # Previously would raise AttributeError instead
@@ -1350,11 +1356,108 @@ def test_cleanup_with_symlink_to_a_directory(self):
13501356 "TemporaryDirectory %s exists after cleanup" % d1 .name )
13511357 self .assertTrue (os .path .exists (d2 .name ),
13521358 "Directory pointed to by a symlink was deleted" )
1353- self .assertEqual (os .listdir (d2 .name ), ['test .txt' ],
1359+ self .assertEqual (os .listdir (d2 .name ), ['test0 .txt' ],
13541360 "Contents of the directory pointed to by a symlink "
13551361 "were deleted" )
13561362 d2 .cleanup ()
13571363
1364+ @support .skip_unless_symlink
1365+ def test_cleanup_with_symlink_modes (self ):
1366+ # cleanup() should not follow symlinks when fixing mode bits (#91133)
1367+ with self .do_create (recurse = 0 ) as d2 :
1368+ file1 = os .path .join (d2 , 'file1' )
1369+ open (file1 , 'wb' ).close ()
1370+ dir1 = os .path .join (d2 , 'dir1' )
1371+ os .mkdir (dir1 )
1372+ for mode in range (8 ):
1373+ mode <<= 6
1374+ with self .subTest (mode = format (mode , '03o' )):
1375+ def test (target , target_is_directory ):
1376+ d1 = self .do_create (recurse = 0 )
1377+ symlink = os .path .join (d1 .name , 'symlink' )
1378+ os .symlink (target , symlink ,
1379+ target_is_directory = target_is_directory )
1380+ try :
1381+ os .chmod (symlink , mode , follow_symlinks = False )
1382+ except NotImplementedError :
1383+ pass
1384+ try :
1385+ os .chmod (symlink , mode )
1386+ except FileNotFoundError :
1387+ pass
1388+ os .chmod (d1 .name , mode )
1389+ d1 .cleanup ()
1390+ self .assertFalse (os .path .exists (d1 .name ))
1391+
1392+ with self .subTest ('nonexisting file' ):
1393+ test ('nonexisting' , target_is_directory = False )
1394+ with self .subTest ('nonexisting dir' ):
1395+ test ('nonexisting' , target_is_directory = True )
1396+
1397+ with self .subTest ('existing file' ):
1398+ os .chmod (file1 , mode )
1399+ old_mode = os .stat (file1 ).st_mode
1400+ test (file1 , target_is_directory = False )
1401+ new_mode = os .stat (file1 ).st_mode
1402+ self .assertEqual (new_mode , old_mode ,
1403+ '%03o != %03o' % (new_mode , old_mode ))
1404+
1405+ with self .subTest ('existing dir' ):
1406+ os .chmod (dir1 , mode )
1407+ old_mode = os .stat (dir1 ).st_mode
1408+ test (dir1 , target_is_directory = True )
1409+ new_mode = os .stat (dir1 ).st_mode
1410+ self .assertEqual (new_mode , old_mode ,
1411+ '%03o != %03o' % (new_mode , old_mode ))
1412+
1413+ @unittest .skipUnless (hasattr (os , 'chflags' ), 'requires os.chflags' )
1414+ @support .skip_unless_symlink
1415+ def test_cleanup_with_symlink_flags (self ):
1416+ # cleanup() should not follow symlinks when fixing flags (#91133)
1417+ flags = stat .UF_IMMUTABLE | stat .UF_NOUNLINK
1418+ self .check_flags (flags )
1419+
1420+ with self .do_create (recurse = 0 ) as d2 :
1421+ file1 = os .path .join (d2 , 'file1' )
1422+ open (file1 , 'wb' ).close ()
1423+ dir1 = os .path .join (d2 , 'dir1' )
1424+ os .mkdir (dir1 )
1425+ def test (target , target_is_directory ):
1426+ d1 = self .do_create (recurse = 0 )
1427+ symlink = os .path .join (d1 .name , 'symlink' )
1428+ os .symlink (target , symlink ,
1429+ target_is_directory = target_is_directory )
1430+ try :
1431+ os .chflags (symlink , flags , follow_symlinks = False )
1432+ except NotImplementedError :
1433+ pass
1434+ try :
1435+ os .chflags (symlink , flags )
1436+ except FileNotFoundError :
1437+ pass
1438+ os .chflags (d1 .name , flags )
1439+ d1 .cleanup ()
1440+ self .assertFalse (os .path .exists (d1 .name ))
1441+
1442+ with self .subTest ('nonexisting file' ):
1443+ test ('nonexisting' , target_is_directory = False )
1444+ with self .subTest ('nonexisting dir' ):
1445+ test ('nonexisting' , target_is_directory = True )
1446+
1447+ with self .subTest ('existing file' ):
1448+ os .chflags (file1 , flags )
1449+ old_flags = os .stat (file1 ).st_flags
1450+ test (file1 , target_is_directory = False )
1451+ new_flags = os .stat (file1 ).st_flags
1452+ self .assertEqual (new_flags , old_flags )
1453+
1454+ with self .subTest ('existing dir' ):
1455+ os .chflags (dir1 , flags )
1456+ old_flags = os .stat (dir1 ).st_flags
1457+ test (dir1 , target_is_directory = True )
1458+ new_flags = os .stat (dir1 ).st_flags
1459+ self .assertEqual (new_flags , old_flags )
1460+
13581461 @support .cpython_only
13591462 def test_del_on_collection (self ):
13601463 # A TemporaryDirectory is deleted when garbage collected
@@ -1385,7 +1488,7 @@ def test_del_on_shutdown(self):
13851488
13861489 tmp2 = os.path.join(tmp.name, 'test_dir')
13871490 os.mkdir(tmp2)
1388- with open(os.path.join(tmp2, "test .txt"), "w") as f:
1491+ with open(os.path.join(tmp2, "test0 .txt"), "w") as f:
13891492 f.write("Hello world!")
13901493
13911494 {mod}.tmp = tmp
@@ -1453,6 +1556,51 @@ def test_context_manager(self):
14531556 self .assertEqual (name , d .name )
14541557 self .assertFalse (os .path .exists (name ))
14551558
1559+ def test_modes (self ):
1560+ for mode in range (8 ):
1561+ mode <<= 6
1562+ with self .subTest (mode = format (mode , '03o' )):
1563+ d = self .do_create (recurse = 3 , dirs = 2 , files = 2 )
1564+ with d :
1565+ # Change files and directories mode recursively.
1566+ for root , dirs , files in os .walk (d .name , topdown = False ):
1567+ for name in files :
1568+ os .chmod (os .path .join (root , name ), mode )
1569+ os .chmod (root , mode )
1570+ d .cleanup ()
1571+ self .assertFalse (os .path .exists (d .name ))
1572+
1573+ def check_flags (self , flags ):
1574+ # skip the test if these flags are not supported (ex: FreeBSD 13)
1575+ filename = support .TESTFN
1576+ try :
1577+ open (filename , "w" ).close ()
1578+ try :
1579+ os .chflags (filename , flags )
1580+ except OSError as exc :
1581+ # "OSError: [Errno 45] Operation not supported"
1582+ self .skipTest (f"chflags() doesn't support flags "
1583+ f"{ flags :#b} : { exc } " )
1584+ else :
1585+ os .chflags (filename , 0 )
1586+ finally :
1587+ support .unlink (filename )
1588+
1589+ @unittest .skipUnless (hasattr (os , 'chflags' ), 'requires os.lchflags' )
1590+ def test_flags (self ):
1591+ flags = stat .UF_IMMUTABLE | stat .UF_NOUNLINK
1592+ self .check_flags (flags )
1593+
1594+ d = self .do_create (recurse = 3 , dirs = 2 , files = 2 )
1595+ with d :
1596+ # Change files and directories flags recursively.
1597+ for root , dirs , files in os .walk (d .name , topdown = False ):
1598+ for name in files :
1599+ os .chflags (os .path .join (root , name ), flags )
1600+ os .chflags (root , flags )
1601+ d .cleanup ()
1602+ self .assertFalse (os .path .exists (d .name ))
1603+
14561604
14571605if __name__ == "__main__" :
14581606 unittest .main ()
0 commit comments