Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(12)

Delta Between Two Patch Sets: Lib/test/test_shutil.py

Issue 21697: shutil.copytree() handles symbolic directory incorrectly
Left Patch Set: Created 5 years, 8 months ago
Right Patch Set: Created 4 years, 7 months ago
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
Left: Side by side diff | Download
Right: Side by side diff | Download
« no previous file with change/comment | « Lib/shutil.py ('k') | no next file » | no next file with change/comment »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
LEFTRIGHT
1 # Copyright (C) 2003 Python Software Foundation 1 # Copyright (C) 2003 Python Software Foundation
2 2
3 import unittest 3 import unittest
4 import unittest.mock
4 import shutil 5 import shutil
5 import tempfile 6 import tempfile
6 import sys 7 import sys
7 import stat 8 import stat
8 import os 9 import os
9 import os.path 10 import os.path
10 import errno 11 import errno
11 import functools 12 import functools
12 import subprocess 13 import subprocess
14 from contextlib import ExitStack
13 from test import support 15 from test import support
14 from test.support import TESTFN 16 from test.support import TESTFN
15 from os.path import splitdrive 17 from os.path import splitdrive
16 from distutils.spawn import find_executable, spawn 18 from distutils.spawn import find_executable, spawn
17 from shutil import (_make_tarball, _make_zipfile, make_archive, 19 from shutil import (_make_tarball, _make_zipfile, make_archive,
18 register_archive_format, unregister_archive_format, 20 register_archive_format, unregister_archive_format,
19 get_archive_formats, Error, unpack_archive, 21 get_archive_formats, Error, unpack_archive,
20 register_unpack_format, RegistryError, 22 register_unpack_format, RegistryError,
21 unregister_unpack_format, get_unpack_formats, 23 unregister_unpack_format, get_unpack_formats,
22 SameFileError) 24 SameFileError)
23 import tarfile 25 import tarfile
24 import warnings 26 import warnings
25 27
26 from test import support 28 from test import support
27 from test.support import TESTFN, check_warnings, captured_stdout, requires_zlib 29 from test.support import TESTFN, check_warnings, captured_stdout, requires_zlib
28 30
29 try: 31 try:
30 import bz2 32 import bz2
31 BZ2_SUPPORTED = True 33 BZ2_SUPPORTED = True
32 except ImportError: 34 except ImportError:
33 BZ2_SUPPORTED = False 35 BZ2_SUPPORTED = False
34 36
37 try:
38 import lzma
39 LZMA_SUPPORTED = True
40 except ImportError:
41 LZMA_SUPPORTED = False
42
35 TESTFN2 = TESTFN + "2" 43 TESTFN2 = TESTFN + "2"
36 44
37 try: 45 try:
38 import grp 46 import grp
39 import pwd 47 import pwd
40 UID_GID_SUPPORT = True 48 UID_GID_SUPPORT = True
41 except ImportError: 49 except ImportError:
42 UID_GID_SUPPORT = False 50 UID_GID_SUPPORT = False
43 51
44 try: 52 try:
(...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after
109 self.tempdirs.append(d) 117 self.tempdirs.append(d)
110 return d 118 return d
111 119
112 def test_rmtree_works_on_bytes(self): 120 def test_rmtree_works_on_bytes(self):
113 tmp = self.mkdtemp() 121 tmp = self.mkdtemp()
114 victim = os.path.join(tmp, 'killme') 122 victim = os.path.join(tmp, 'killme')
115 os.mkdir(victim) 123 os.mkdir(victim)
116 write_file(os.path.join(victim, 'somefile'), 'foo') 124 write_file(os.path.join(victim, 'somefile'), 'foo')
117 victim = os.fsencode(victim) 125 victim = os.fsencode(victim)
118 self.assertIsInstance(victim, bytes) 126 self.assertIsInstance(victim, bytes)
119 shutil.rmtree(victim) 127 win = (os.name == 'nt')
128 with self.assertWarns(DeprecationWarning) if win else ExitStack():
129 shutil.rmtree(victim)
120 130
121 @support.skip_unless_symlink 131 @support.skip_unless_symlink
122 def test_rmtree_fails_on_symlink(self): 132 def test_rmtree_fails_on_symlink(self):
123 tmp = self.mkdtemp() 133 tmp = self.mkdtemp()
124 dir_ = os.path.join(tmp, 'dir') 134 dir_ = os.path.join(tmp, 'dir')
125 os.mkdir(dir_) 135 os.mkdir(dir_)
126 link = os.path.join(tmp, 'link') 136 link = os.path.join(tmp, 'link')
127 os.symlink(dir_, link) 137 os.symlink(dir_, link)
128 self.assertRaises(OSError, shutil.rmtree, link) 138 self.assertRaises(OSError, shutil.rmtree, link)
129 self.assertTrue(os.path.exists(dir_)) 139 self.assertTrue(os.path.exists(dir_))
(...skipping 500 matching lines...) Expand 10 before | Expand all | Expand 10 after
630 self.assertTrue(os.path.isfile(os.path.join(dst_dir, 'test.txt'))) 640 self.assertTrue(os.path.isfile(os.path.join(dst_dir, 'test.txt')))
631 self.assertTrue(os.path.isdir(os.path.join(dst_dir, 'test_dir'))) 641 self.assertTrue(os.path.isdir(os.path.join(dst_dir, 'test_dir')))
632 self.assertTrue(os.path.isfile(os.path.join(dst_dir, 'test_dir', 642 self.assertTrue(os.path.isfile(os.path.join(dst_dir, 'test_dir',
633 'test.txt'))) 643 'test.txt')))
634 actual = read_file((dst_dir, 'test.txt')) 644 actual = read_file((dst_dir, 'test.txt'))
635 self.assertEqual(actual, '123') 645 self.assertEqual(actual, '123')
636 actual = read_file((dst_dir, 'test_dir', 'test.txt')) 646 actual = read_file((dst_dir, 'test_dir', 'test.txt'))
637 self.assertEqual(actual, '456') 647 self.assertEqual(actual, '456')
638 648
639 @support.skip_unless_symlink 649 @support.skip_unless_symlink
640 def test_copytree_symbolic_directory(self):
641 # see issue 21697
642 tmp_dir = self.mkdtemp()
643 src_dir = os.path.join(tmp_dir, 'src')
644 dst_dir = os.path.join(tmp_dir, 'dst')
645 sub_dir = os.path.join(src_dir, 'sub')
646 os.mkdir(src_dir)
647 os.mkdir(sub_dir)
648 src_link = os.path.join(src_dir, 'link')
649 os.symlink(sub_dir, src_link)
650 shutil.copytree(src_dir, dst_dir)
651 self.assertTrue(os.path.isdir(os.path.join(dst_dir, 'sub')))
652 self.assertTrue(os.path.isdir(os.path.join(dst_dir, 'link')))
653
654 @support.skip_unless_symlink
655 def test_copytree_symlinks(self): 650 def test_copytree_symlinks(self):
656 tmp_dir = self.mkdtemp() 651 tmp_dir = self.mkdtemp()
657 src_dir = os.path.join(tmp_dir, 'src') 652 src_dir = os.path.join(tmp_dir, 'src')
658 dst_dir = os.path.join(tmp_dir, 'dst') 653 dst_dir = os.path.join(tmp_dir, 'dst')
659 sub_dir = os.path.join(src_dir, 'sub') 654 sub_dir = os.path.join(src_dir, 'sub')
660 os.mkdir(src_dir) 655 os.mkdir(src_dir)
661 os.mkdir(sub_dir) 656 os.mkdir(sub_dir)
662 write_file((src_dir, 'file.txt'), 'foo') 657 write_file((src_dir, 'file.txt'), 'foo')
663 src_link = os.path.join(sub_dir, 'link') 658 src_link = os.path.join(sub_dir, 'link')
664 dst_link = os.path.join(dst_dir, 'sub/link') 659 dst_link = os.path.join(dst_dir, 'sub/link')
(...skipping 98 matching lines...) Expand 10 before | Expand all | Expand 10 after
763 self.assertEqual(os.stat(src_dir).st_mode, os.stat(dst_dir).st_mode) 758 self.assertEqual(os.stat(src_dir).st_mode, os.stat(dst_dir).st_mode)
764 self.assertEqual(os.stat(os.path.join(src_dir, 'permissive.txt')).st_mod e, 759 self.assertEqual(os.stat(os.path.join(src_dir, 'permissive.txt')).st_mod e,
765 os.stat(os.path.join(dst_dir, 'permissive.txt')).st_mo de) 760 os.stat(os.path.join(dst_dir, 'permissive.txt')).st_mo de)
766 self.assertEqual(os.stat(os.path.join(src_dir, 'restrictive.txt')).st_mo de, 761 self.assertEqual(os.stat(os.path.join(src_dir, 'restrictive.txt')).st_mo de,
767 os.stat(os.path.join(dst_dir, 'restrictive.txt')).st_m ode) 762 os.stat(os.path.join(dst_dir, 'restrictive.txt')).st_m ode)
768 restrictive_subdir_dst = os.path.join(dst_dir, 763 restrictive_subdir_dst = os.path.join(dst_dir,
769 os.path.split(restrictive_subdir)[ 1]) 764 os.path.split(restrictive_subdir)[ 1])
770 self.assertEqual(os.stat(restrictive_subdir).st_mode, 765 self.assertEqual(os.stat(restrictive_subdir).st_mode,
771 os.stat(restrictive_subdir_dst).st_mode) 766 os.stat(restrictive_subdir_dst).st_mode)
772 767
768 @unittest.mock.patch('os.chmod')
769 def test_copytree_winerror(self, mock_patch):
770 # When copying to VFAT, copystat() raises OSError. On Windows, the
771 # exception object has a meaningful 'winerror' attribute, but not
772 # on other operating systems. Do not assume 'winerror' is set.
773 src_dir = tempfile.mkdtemp()
774 dst_dir = os.path.join(tempfile.mkdtemp(), 'destination')
775 self.addCleanup(shutil.rmtree, src_dir)
776 self.addCleanup(shutil.rmtree, os.path.dirname(dst_dir))
777
778 mock_patch.side_effect = PermissionError('ka-boom')
779 with self.assertRaises(shutil.Error):
780 shutil.copytree(src_dir, dst_dir)
781
773 @unittest.skipIf(os.name == 'nt', 'temporarily disabled on Windows') 782 @unittest.skipIf(os.name == 'nt', 'temporarily disabled on Windows')
774 @unittest.skipUnless(hasattr(os, 'link'), 'requires os.link') 783 @unittest.skipUnless(hasattr(os, 'link'), 'requires os.link')
775 def test_dont_copy_file_onto_link_to_itself(self): 784 def test_dont_copy_file_onto_link_to_itself(self):
776 # bug 851123. 785 # bug 851123.
777 os.mkdir(TESTFN) 786 os.mkdir(TESTFN)
778 src = os.path.join(TESTFN, 'cheese') 787 src = os.path.join(TESTFN, 'cheese')
779 dst = os.path.join(TESTFN, 'shop') 788 dst = os.path.join(TESTFN, 'shop')
780 try: 789 try:
781 with open(src, 'w') as f: 790 with open(src, 'w') as f:
782 f.write('cheddar') 791 f.write('cheddar')
(...skipping 101 matching lines...) Expand 10 before | Expand all | Expand 10 after
884 893
885 # a dangling symlink is ignored with the proper flag 894 # a dangling symlink is ignored with the proper flag
886 dst_dir = os.path.join(self.mkdtemp(), 'destination2') 895 dst_dir = os.path.join(self.mkdtemp(), 'destination2')
887 shutil.copytree(src_dir, dst_dir, ignore_dangling_symlinks=True) 896 shutil.copytree(src_dir, dst_dir, ignore_dangling_symlinks=True)
888 self.assertNotIn('test.txt', os.listdir(dst_dir)) 897 self.assertNotIn('test.txt', os.listdir(dst_dir))
889 898
890 # a dangling symlink is copied if symlinks=True 899 # a dangling symlink is copied if symlinks=True
891 dst_dir = os.path.join(self.mkdtemp(), 'destination3') 900 dst_dir = os.path.join(self.mkdtemp(), 'destination3')
892 shutil.copytree(src_dir, dst_dir, symlinks=True) 901 shutil.copytree(src_dir, dst_dir, symlinks=True)
893 self.assertIn('test.txt', os.listdir(dst_dir)) 902 self.assertIn('test.txt', os.listdir(dst_dir))
903
904 @support.skip_unless_symlink
905 def test_copytree_symlink_dir(self):
906 src_dir = self.mkdtemp()
907 dst_dir = os.path.join(self.mkdtemp(), 'destination')
908 os.mkdir(os.path.join(src_dir, 'real_dir'))
909 with open(os.path.join(src_dir, 'real_dir', 'test.txt'), 'w'):
910 pass
911 os.symlink(os.path.join(src_dir, 'real_dir'),
912 os.path.join(src_dir, 'link_to_dir'),
913 target_is_directory=True)
914 shutil.copytree(src_dir, dst_dir, symlinks=False)
915
916 assert not os.path.islink(os.path.join(dst_dir, 'link_to_dir'))
917 self.assertIn("test.txt", os.listdir(os.path.join(dst_dir, 'link_to_dir' )))
918
919 dst_dir = os.path.join(self.mkdtemp(), 'destination2')
920 shutil.copytree(src_dir, dst_dir, symlinks=True)
921 assert os.path.islink(os.path.join(dst_dir, 'link_to_dir'))
922 self.assertIn("test.txt", os.listdir(os.path.join(dst_dir, 'link_to_dir' )))
894 923
895 def _copy_file(self, method): 924 def _copy_file(self, method):
896 fname = 'test.txt' 925 fname = 'test.txt'
897 tmpdir = self.mkdtemp() 926 tmpdir = self.mkdtemp()
898 write_file((tmpdir, fname), 'xxx') 927 write_file((tmpdir, fname), 'xxx')
899 file1 = os.path.join(tmpdir, fname) 928 file1 = os.path.join(tmpdir, fname)
900 tmpdir2 = self.mkdtemp() 929 tmpdir2 = self.mkdtemp()
901 method(file1, tmpdir2) 930 method(file1, tmpdir2)
902 file2 = os.path.join(tmpdir2, fname) 931 file2 = os.path.join(tmpdir2, fname)
903 return (file1, file2) 932 return (file1, file2)
(...skipping 227 matching lines...) Expand 10 before | Expand all | Expand 10 after
1131 register_archive_format('xxx', _breaks, [], 'xxx file') 1160 register_archive_format('xxx', _breaks, [], 'xxx file')
1132 try: 1161 try:
1133 try: 1162 try:
1134 make_archive('xxx', 'xxx', root_dir=self.mkdtemp()) 1163 make_archive('xxx', 'xxx', root_dir=self.mkdtemp())
1135 except Exception: 1164 except Exception:
1136 pass 1165 pass
1137 self.assertEqual(os.getcwd(), current_dir) 1166 self.assertEqual(os.getcwd(), current_dir)
1138 finally: 1167 finally:
1139 unregister_archive_format('xxx') 1168 unregister_archive_format('xxx')
1140 1169
1170 def test_make_tarfile_in_curdir(self):
1171 # Issue #21280
1172 root_dir = self.mkdtemp()
1173 with support.change_cwd(root_dir):
1174 self.assertEqual(make_archive('test', 'tar'), 'test.tar')
1175 self.assertTrue(os.path.isfile('test.tar'))
1176
1177 @requires_zlib
1178 def test_make_zipfile_in_curdir(self):
1179 # Issue #21280
1180 root_dir = self.mkdtemp()
1181 with support.change_cwd(root_dir):
1182 self.assertEqual(make_archive('test', 'zip'), 'test.zip')
1183 self.assertTrue(os.path.isfile('test.zip'))
1184
1141 def test_register_archive_format(self): 1185 def test_register_archive_format(self):
1142 1186
1143 self.assertRaises(TypeError, register_archive_format, 'xxx', 1) 1187 self.assertRaises(TypeError, register_archive_format, 'xxx', 1)
1144 self.assertRaises(TypeError, register_archive_format, 'xxx', lambda: x, 1188 self.assertRaises(TypeError, register_archive_format, 'xxx', lambda: x,
1145 1) 1189 1)
1146 self.assertRaises(TypeError, register_archive_format, 'xxx', lambda: x, 1190 self.assertRaises(TypeError, register_archive_format, 'xxx', lambda: x,
1147 [(1, 2), (1, 2, 3)]) 1191 [(1, 2), (1, 2, 3)])
1148 1192
1149 register_archive_format('xxx', lambda: x, [(1, 2)], 'xxx file') 1193 register_archive_format('xxx', lambda: x, [(1, 2)], 'xxx file')
1150 formats = [name for name, params in get_archive_formats()] 1194 formats = [name for name, params in get_archive_formats()]
(...skipping 13 matching lines...) Expand all
1164 target_path = os.path.join(dir2, os.path.split(path)[-1]) 1208 target_path = os.path.join(dir2, os.path.split(path)[-1])
1165 if not os.path.exists(target_path): 1209 if not os.path.exists(target_path):
1166 diff.append(file_) 1210 diff.append(file_)
1167 return diff 1211 return diff
1168 1212
1169 @requires_zlib 1213 @requires_zlib
1170 def test_unpack_archive(self): 1214 def test_unpack_archive(self):
1171 formats = ['tar', 'gztar', 'zip'] 1215 formats = ['tar', 'gztar', 'zip']
1172 if BZ2_SUPPORTED: 1216 if BZ2_SUPPORTED:
1173 formats.append('bztar') 1217 formats.append('bztar')
1218 if LZMA_SUPPORTED:
1219 formats.append('xztar')
1174 1220
1175 for format in formats: 1221 for format in formats:
1176 tmpdir = self.mkdtemp() 1222 tmpdir = self.mkdtemp()
1177 base_dir, root_dir, base_name = self._create_files() 1223 base_dir, root_dir, base_name = self._create_files()
1178 tmpdir2 = self.mkdtemp() 1224 tmpdir2 = self.mkdtemp()
1179 filename = make_archive(base_name, format, root_dir, base_dir) 1225 filename = make_archive(base_name, format, root_dir, base_dir)
1180 1226
1181 # let's try to unpack it now 1227 # let's try to unpack it now
1182 unpack_archive(filename, tmpdir2) 1228 unpack_archive(filename, tmpdir2)
1183 diff = self._compare_dirs(tmpdir, tmpdir2) 1229 diff = self._compare_dirs(tmpdir, tmpdir2)
(...skipping 601 matching lines...) Expand 10 before | Expand all | Expand 10 after
1785 expected = (int(size[1]), int(size[0])) # reversed order 1831 expected = (int(size[1]), int(size[0])) # reversed order
1786 1832
1787 with support.EnvironmentVarGuard() as env: 1833 with support.EnvironmentVarGuard() as env:
1788 del env['LINES'] 1834 del env['LINES']
1789 del env['COLUMNS'] 1835 del env['COLUMNS']
1790 actual = shutil.get_terminal_size() 1836 actual = shutil.get_terminal_size()
1791 1837
1792 self.assertEqual(expected, actual) 1838 self.assertEqual(expected, actual)
1793 1839
1794 1840
1841 class PublicAPITests(unittest.TestCase):
1842 """Ensures that the correct values are exposed in the public API."""
1843
1844 def test_module_all_attribute(self):
1845 self.assertTrue(hasattr(shutil, '__all__'))
1846 target_api = ['copyfileobj', 'copyfile', 'copymode', 'copystat',
1847 'copy', 'copy2', 'copytree', 'move', 'rmtree', 'Error',
1848 'SpecialFileError', 'ExecError', 'make_archive',
1849 'get_archive_formats', 'register_archive_format',
1850 'unregister_archive_format', 'get_unpack_formats',
1851 'register_unpack_format', 'unregister_unpack_format',
1852 'unpack_archive', 'ignore_patterns', 'chown', 'which',
1853 'get_terminal_size', 'SameFileError']
1854 if hasattr(os, 'statvfs') or os.name == 'nt':
1855 target_api.append('disk_usage')
1856 self.assertEqual(set(shutil.__all__), set(target_api))
1857
1858
1795 if __name__ == '__main__': 1859 if __name__ == '__main__':
1796 unittest.main() 1860 unittest.main()
LEFTRIGHT

RSS Feeds Recent Issues | This issue
This is Rietveld 894c83f36cb7+