test_scandir.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. """Tests for scandir.scandir()."""
  2. from __future__ import unicode_literals
  3. import os
  4. import shutil
  5. import sys
  6. import time
  7. import unittest
  8. import yatest.common
  9. try:
  10. import scandir
  11. has_scandir = True
  12. except ImportError:
  13. has_scandir = False
  14. FILE_ATTRIBUTE_DIRECTORY = 16
  15. IS_PY3 = sys.version_info >= (3, 0)
  16. if IS_PY3:
  17. int_types = int
  18. else:
  19. int_types = (int, long)
  20. str = unicode
  21. if hasattr(os, 'symlink'):
  22. try:
  23. #link_name = os.path.join(os.path.dirname(__file__), '_testlink')
  24. #os.symlink(__file__, link_name)
  25. #os.remove(link_name)
  26. symlinks_supported = True
  27. except NotImplementedError:
  28. # Windows versions before Vista don't support symbolic links
  29. symlinks_supported = False
  30. else:
  31. symlinks_supported = False
  32. def create_file(path, contents='1234'):
  33. with open(path, 'w') as f:
  34. f.write(contents)
  35. def setup_main():
  36. join = os.path.join
  37. os.mkdir(TEST_PATH)
  38. os.mkdir(join(TEST_PATH, 'subdir'))
  39. create_file(join(TEST_PATH, 'file1.txt'))
  40. create_file(join(TEST_PATH, 'file2.txt'), contents='12345678')
  41. os.mkdir(join(TEST_PATH, 'subdir', 'unidir\u018F'))
  42. create_file(join(TEST_PATH, 'subdir', 'file1.txt'))
  43. create_file(join(TEST_PATH, 'subdir', 'unicod\u018F.txt'))
  44. create_file(join(TEST_PATH, 'subdir', 'unidir\u018F', 'file1.txt'))
  45. os.mkdir(join(TEST_PATH, 'linkdir'))
  46. def setup_symlinks():
  47. join = os.path.join
  48. os.mkdir(join(TEST_PATH, 'linkdir', 'linksubdir'))
  49. create_file(join(TEST_PATH, 'linkdir', 'file1.txt'))
  50. os.symlink(os.path.abspath(join(TEST_PATH, 'linkdir', 'file1.txt')),
  51. join(TEST_PATH, 'linkdir', 'link_to_file'))
  52. dir_name = os.path.abspath(join(TEST_PATH, 'linkdir', 'linksubdir'))
  53. dir_link = join(TEST_PATH, 'linkdir', 'link_to_dir')
  54. if IS_PY3:
  55. os.symlink(dir_name, dir_link, target_is_directory=True)
  56. else:
  57. os.symlink(dir_name, dir_link)
  58. def teardown():
  59. try:
  60. shutil.rmtree(TEST_PATH)
  61. except OSError:
  62. # why does the above fail sometimes?
  63. time.sleep(0.1)
  64. shutil.rmtree(TEST_PATH)
  65. class TestMixin(unittest.TestCase):
  66. def setUp(self):
  67. global TEST_PATH
  68. TEST_PATH = yatest.common.test_output_path('../test')
  69. if not os.path.exists(TEST_PATH):
  70. setup_main()
  71. if symlinks_supported and not os.path.exists(
  72. os.path.join(TEST_PATH, 'linkdir', 'linksubdir')):
  73. setup_symlinks()
  74. if not hasattr(unittest.TestCase, 'skipTest'):
  75. def skipTest(self, reason):
  76. sys.stdout.write('skipped {0!r} '.format(reason))
  77. def test_basic(self):
  78. if not hasattr(self, 'scandir_func'):
  79. self.skipTest('skip mixin')
  80. entries = sorted(self.scandir_func(TEST_PATH), key=lambda e: e.name)
  81. self.assertEqual([(e.name, e.is_dir()) for e in entries],
  82. [('file1.txt', False), ('file2.txt', False),
  83. ('linkdir', True), ('subdir', True)])
  84. self.assertEqual([e.path for e in entries],
  85. [os.path.join(TEST_PATH, e.name) for e in entries])
  86. def test_dir_entry(self):
  87. if not hasattr(self, 'scandir_func'):
  88. self.skipTest('skip mixin')
  89. entries = dict((e.name, e) for e in self.scandir_func(TEST_PATH))
  90. e = entries['file1.txt']
  91. self.assertEqual([e.is_dir(), e.is_file(), e.is_symlink()], [False, True, False])
  92. e = entries['file2.txt']
  93. self.assertEqual([e.is_dir(), e.is_file(), e.is_symlink()], [False, True, False])
  94. e = entries['subdir']
  95. self.assertEqual([e.is_dir(), e.is_file(), e.is_symlink()], [True, False, False])
  96. self.assertEqual(entries['file1.txt'].stat().st_size, 4)
  97. self.assertEqual(entries['file2.txt'].stat().st_size, 8)
  98. def test_stat(self):
  99. if not hasattr(self, 'scandir_func'):
  100. self.skipTest('skip mixin')
  101. entries = list(self.scandir_func(TEST_PATH))
  102. for entry in entries:
  103. os_stat = os.stat(os.path.join(TEST_PATH, entry.name))
  104. scandir_stat = entry.stat()
  105. self.assertEqual(os_stat.st_mode, scandir_stat.st_mode)
  106. # TODO: be nice to figure out why these aren't identical on Windows and on PyPy
  107. # * Windows: they seem to be a few microseconds to tens of seconds out
  108. # * PyPy: for some reason os_stat's times are nanosecond, scandir's are not
  109. self.assertAlmostEqual(os_stat.st_mtime, scandir_stat.st_mtime, delta=1)
  110. self.assertAlmostEqual(os_stat.st_ctime, scandir_stat.st_ctime, delta=1)
  111. if entry.is_file():
  112. self.assertEqual(os_stat.st_size, scandir_stat.st_size)
  113. def test_returns_iter(self):
  114. if not hasattr(self, 'scandir_func'):
  115. self.skipTest('skip mixin')
  116. it = self.scandir_func(TEST_PATH)
  117. entry = next(it)
  118. assert hasattr(entry, 'name')
  119. def check_file_attributes(self, result):
  120. self.assertTrue(hasattr(result, 'st_file_attributes'))
  121. self.assertTrue(isinstance(result.st_file_attributes, int_types))
  122. self.assertTrue(0 <= result.st_file_attributes <= 0xFFFFFFFF)
  123. def test_file_attributes(self):
  124. if not hasattr(self, 'scandir_func'):
  125. self.skipTest('skip mixin')
  126. if sys.platform != 'win32' or not self.has_file_attributes:
  127. # st_file_attributes is Win32 specific
  128. return self.skipTest('st_file_attributes not supported')
  129. entries = dict((e.name, e) for e in self.scandir_func(TEST_PATH))
  130. # test st_file_attributes on a file (FILE_ATTRIBUTE_DIRECTORY not set)
  131. result = entries['file1.txt'].stat()
  132. self.check_file_attributes(result)
  133. self.assertEqual(result.st_file_attributes & FILE_ATTRIBUTE_DIRECTORY, 0)
  134. # test st_file_attributes on a directory (FILE_ATTRIBUTE_DIRECTORY set)
  135. result = entries['subdir'].stat()
  136. self.check_file_attributes(result)
  137. self.assertEqual(result.st_file_attributes & FILE_ATTRIBUTE_DIRECTORY,
  138. FILE_ATTRIBUTE_DIRECTORY)
  139. def test_path(self):
  140. if not hasattr(self, 'scandir_func'):
  141. self.skipTest('skip mixin')
  142. entries = sorted(self.scandir_func(TEST_PATH), key=lambda e: e.name)
  143. self.assertEqual([os.path.basename(e.name) for e in entries],
  144. ['file1.txt', 'file2.txt', 'linkdir', 'subdir'])
  145. self.assertEqual([os.path.normpath(os.path.join(TEST_PATH, e.name)) for e in entries],
  146. [os.path.normpath(e.path) for e in entries])
  147. def test_symlink(self):
  148. if not hasattr(self, 'scandir_func'):
  149. self.skipTest('skip mixin')
  150. if not symlinks_supported:
  151. return self.skipTest('symbolic links not supported')
  152. entries = sorted(self.scandir_func(os.path.join(TEST_PATH, 'linkdir')),
  153. key=lambda e: e.name)
  154. self.assertEqual([(e.name, e.is_symlink()) for e in entries],
  155. [('file1.txt', False),
  156. ('link_to_dir', True),
  157. ('link_to_file', True),
  158. ('linksubdir', False)])
  159. self.assertEqual([(e.name, e.is_file(), e.is_file(follow_symlinks=False))
  160. for e in entries],
  161. [('file1.txt', True, True),
  162. ('link_to_dir', False, False),
  163. ('link_to_file', True, False),
  164. ('linksubdir', False, False)])
  165. self.assertEqual([(e.name, e.is_dir(), e.is_dir(follow_symlinks=False))
  166. for e in entries],
  167. [('file1.txt', False, False),
  168. ('link_to_dir', True, False),
  169. ('link_to_file', False, False),
  170. ('linksubdir', True, True)])
  171. def test_bytes(self):
  172. if not hasattr(self, 'scandir_func'):
  173. self.skipTest('skip mixin')
  174. # Check that unicode filenames are returned correctly as bytes in output
  175. path = os.path.join(TEST_PATH, 'subdir').encode(sys.getfilesystemencoding(), 'replace')
  176. self.assertTrue(isinstance(path, bytes))
  177. # Python 3.6 on Windows fixes the bytes filename thing by using UTF-8
  178. if IS_PY3 and sys.platform == 'win32':
  179. if not (sys.version_info >= (3, 6) and self.scandir_func == os.scandir):
  180. self.assertRaises(TypeError, self.scandir_func, path)
  181. return
  182. entries = [e for e in self.scandir_func(path) if e.name.startswith(b'unicod')]
  183. self.assertEqual(len(entries), 1)
  184. entry = entries[0]
  185. self.assertTrue(isinstance(entry.name, bytes))
  186. self.assertTrue(isinstance(entry.path, bytes))
  187. # b'unicod?.txt' on Windows, b'unicod\xc6\x8f.txt' (UTF-8) or similar on POSIX
  188. entry_name = 'unicod\u018f.txt'.encode(sys.getfilesystemencoding(), 'replace')
  189. self.assertEqual(entry.name, entry_name)
  190. self.assertEqual(entry.path, os.path.join(path, entry_name))
  191. def test_unicode(self):
  192. if not hasattr(self, 'scandir_func'):
  193. self.skipTest('skip mixin')
  194. # Check that unicode filenames are returned correctly as (unicode) str in output
  195. path = os.path.join(TEST_PATH, 'subdir')
  196. if not IS_PY3:
  197. path = path.decode(sys.getfilesystemencoding(), 'replace')
  198. self.assertTrue(isinstance(path, str))
  199. entries = [e for e in self.scandir_func(path) if e.name.startswith('unicod')]
  200. self.assertEqual(len(entries), 1)
  201. entry = entries[0]
  202. self.assertTrue(isinstance(entry.name, str))
  203. self.assertTrue(isinstance(entry.path, str))
  204. entry_name = 'unicod\u018f.txt'
  205. self.assertEqual(entry.name, entry_name)
  206. self.assertEqual(entry.path, os.path.join(path, 'unicod\u018f.txt'))
  207. # Check that it handles unicode input properly
  208. path = os.path.join(TEST_PATH, 'subdir', 'unidir\u018f')
  209. self.assertTrue(isinstance(path, str))
  210. entries = list(self.scandir_func(path))
  211. self.assertEqual(len(entries), 1)
  212. entry = entries[0]
  213. self.assertTrue(isinstance(entry.name, str))
  214. self.assertTrue(isinstance(entry.path, str))
  215. self.assertEqual(entry.name, 'file1.txt')
  216. self.assertEqual(entry.path, os.path.join(path, 'file1.txt'))
  217. def test_walk_unicode_handling(self):
  218. if not hasattr(self, 'scandir_func'):
  219. self.skipTest('skip mixin')
  220. encoding = sys.getfilesystemencoding()
  221. dirname_unicode = u'test_unicode_dir'
  222. dirname_bytes = dirname_unicode.encode(encoding)
  223. dirpath = os.path.join(TEST_PATH.encode(encoding), dirname_bytes)
  224. try:
  225. os.makedirs(dirpath)
  226. if sys.platform != 'win32':
  227. # test bytes
  228. self.assertTrue(isinstance(dirpath, bytes))
  229. for (path, dirs, files) in scandir.walk(dirpath):
  230. self.assertTrue(isinstance(path, bytes))
  231. # test unicode
  232. text_type = str if IS_PY3 else unicode
  233. dirpath_unicode = text_type(dirpath, encoding)
  234. self.assertTrue(isinstance(dirpath_unicode, text_type))
  235. for (path, dirs, files) in scandir.walk(dirpath_unicode):
  236. self.assertTrue(isinstance(path, text_type))
  237. finally:
  238. shutil.rmtree(dirpath)
  239. if has_scandir:
  240. class TestScandirGeneric(TestMixin, unittest.TestCase):
  241. def setUp(self):
  242. self.scandir_func = scandir.scandir_generic
  243. self.has_file_attributes = False
  244. TestMixin.setUp(self)
  245. if getattr(scandir, 'scandir_python', None):
  246. class TestScandirPython(TestMixin, unittest.TestCase):
  247. def setUp(self):
  248. self.scandir_func = scandir.scandir_python
  249. self.has_file_attributes = True
  250. TestMixin.setUp(self)
  251. if getattr(scandir, 'scandir_c', None):
  252. class TestScandirC(TestMixin, unittest.TestCase):
  253. def setUp(self):
  254. self.scandir_func = scandir.scandir_c
  255. self.has_file_attributes = True
  256. TestMixin.setUp(self)
  257. class TestScandirDirEntry(unittest.TestCase):
  258. def setUp(self):
  259. if not os.path.exists(TEST_PATH):
  260. setup_main()
  261. def test_iter_returns_dir_entry(self):
  262. it = scandir.scandir(TEST_PATH)
  263. entry = next(it)
  264. assert isinstance(entry, scandir.DirEntry)
  265. if hasattr(os, 'scandir'):
  266. class TestScandirOS(TestMixin, unittest.TestCase):
  267. def setUp(self):
  268. self.scandir_func = os.scandir
  269. self.has_file_attributes = True
  270. TestMixin.setUp(self)