file_resources_test.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569
  1. # -*- coding: utf-8 -*-
  2. # Copyright 2015 Google Inc. All Rights Reserved.
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. """Tests for yapf.file_resources."""
  16. import codecs
  17. import contextlib
  18. import os
  19. import shutil
  20. import tempfile
  21. import unittest
  22. from io import BytesIO
  23. from yapf.yapflib import errors
  24. from yapf.yapflib import file_resources
  25. from yapftests import utils
  26. @contextlib.contextmanager
  27. def _restore_working_dir():
  28. curdir = os.getcwd()
  29. try:
  30. yield
  31. finally:
  32. os.chdir(curdir)
  33. @contextlib.contextmanager
  34. def _exists_mocked_in_module(module, mock_implementation):
  35. unmocked_exists = getattr(module, 'exists')
  36. setattr(module, 'exists', mock_implementation)
  37. try:
  38. yield
  39. finally:
  40. setattr(module, 'exists', unmocked_exists)
  41. class GetExcludePatternsForDir(unittest.TestCase):
  42. def setUp(self): # pylint: disable=g-missing-super-call
  43. self.test_tmpdir = tempfile.mkdtemp()
  44. def tearDown(self): # pylint: disable=g-missing-super-call
  45. shutil.rmtree(self.test_tmpdir)
  46. def test_get_exclude_file_patterns_from_yapfignore(self):
  47. local_ignore_file = os.path.join(self.test_tmpdir, '.yapfignore')
  48. ignore_patterns = ['temp/**/*.py', 'temp2/*.py']
  49. with open(local_ignore_file, 'w') as f:
  50. f.writelines('\n'.join(ignore_patterns))
  51. self.assertEqual(
  52. sorted(file_resources.GetExcludePatternsForDir(self.test_tmpdir)),
  53. sorted(ignore_patterns))
  54. def test_get_exclude_file_patterns_from_yapfignore_with_wrong_syntax(self):
  55. local_ignore_file = os.path.join(self.test_tmpdir, '.yapfignore')
  56. ignore_patterns = ['temp/**/*.py', './wrong/syntax/*.py']
  57. with open(local_ignore_file, 'w') as f:
  58. f.writelines('\n'.join(ignore_patterns))
  59. with self.assertRaises(errors.YapfError):
  60. file_resources.GetExcludePatternsForDir(self.test_tmpdir)
  61. def test_get_exclude_file_patterns_from_pyproject(self):
  62. try:
  63. import tomli
  64. except ImportError:
  65. return
  66. local_ignore_file = os.path.join(self.test_tmpdir, 'pyproject.toml')
  67. ignore_patterns = ['temp/**/*.py', 'temp2/*.py']
  68. with open(local_ignore_file, 'w') as f:
  69. f.write('[tool.yapfignore]\n')
  70. f.write('ignore_patterns=[')
  71. f.writelines('\n,'.join(['"{}"'.format(p) for p in ignore_patterns]))
  72. f.write(']')
  73. self.assertEqual(
  74. sorted(file_resources.GetExcludePatternsForDir(self.test_tmpdir)),
  75. sorted(ignore_patterns))
  76. def test_get_exclude_file_patterns_from_pyproject_no_ignore_section(self):
  77. try:
  78. import tomli
  79. except ImportError:
  80. return
  81. local_ignore_file = os.path.join(self.test_tmpdir, 'pyproject.toml')
  82. ignore_patterns = []
  83. open(local_ignore_file, 'w').close()
  84. self.assertEqual(
  85. sorted(file_resources.GetExcludePatternsForDir(self.test_tmpdir)),
  86. sorted(ignore_patterns))
  87. def test_get_exclude_file_patterns_from_pyproject_ignore_section_empty(self):
  88. try:
  89. import tomli
  90. except ImportError:
  91. return
  92. local_ignore_file = os.path.join(self.test_tmpdir, 'pyproject.toml')
  93. ignore_patterns = []
  94. with open(local_ignore_file, 'w') as f:
  95. f.write('[tool.yapfignore]\n')
  96. self.assertEqual(
  97. sorted(file_resources.GetExcludePatternsForDir(self.test_tmpdir)),
  98. sorted(ignore_patterns))
  99. def test_get_exclude_file_patterns_with_no_config_files(self):
  100. ignore_patterns = []
  101. self.assertEqual(
  102. sorted(file_resources.GetExcludePatternsForDir(self.test_tmpdir)),
  103. sorted(ignore_patterns))
  104. class GetDefaultStyleForDirTest(unittest.TestCase):
  105. def setUp(self): # pylint: disable=g-missing-super-call
  106. self.test_tmpdir = tempfile.mkdtemp()
  107. def tearDown(self): # pylint: disable=g-missing-super-call
  108. shutil.rmtree(self.test_tmpdir)
  109. def test_no_local_style(self):
  110. test_file = os.path.join(self.test_tmpdir, 'file.py')
  111. style_name = file_resources.GetDefaultStyleForDir(test_file)
  112. self.assertEqual(style_name, 'pep8')
  113. def test_no_local_style_custom_default(self):
  114. test_file = os.path.join(self.test_tmpdir, 'file.py')
  115. style_name = file_resources.GetDefaultStyleForDir(
  116. test_file, default_style='custom-default')
  117. self.assertEqual(style_name, 'custom-default')
  118. def test_with_local_style(self):
  119. # Create an empty .style.yapf file in test_tmpdir
  120. style_file = os.path.join(self.test_tmpdir, '.style.yapf')
  121. open(style_file, 'w').close()
  122. test_filename = os.path.join(self.test_tmpdir, 'file.py')
  123. self.assertEqual(style_file,
  124. file_resources.GetDefaultStyleForDir(test_filename))
  125. test_filename = os.path.join(self.test_tmpdir, 'dir1', 'file.py')
  126. self.assertEqual(style_file,
  127. file_resources.GetDefaultStyleForDir(test_filename))
  128. def test_setup_config(self):
  129. # An empty setup.cfg file should not be used
  130. setup_config = os.path.join(self.test_tmpdir, 'setup.cfg')
  131. open(setup_config, 'w').close()
  132. test_dir = os.path.join(self.test_tmpdir, 'dir1')
  133. style_name = file_resources.GetDefaultStyleForDir(test_dir)
  134. self.assertEqual(style_name, 'pep8')
  135. # One with a '[yapf]' section should be used
  136. with open(setup_config, 'w') as f:
  137. f.write('[yapf]\n')
  138. self.assertEqual(setup_config,
  139. file_resources.GetDefaultStyleForDir(test_dir))
  140. def test_pyproject_toml(self):
  141. # An empty pyproject.toml file should not be used
  142. try:
  143. import tomli
  144. except ImportError:
  145. return
  146. pyproject_toml = os.path.join(self.test_tmpdir, 'pyproject.toml')
  147. open(pyproject_toml, 'w').close()
  148. test_dir = os.path.join(self.test_tmpdir, 'dir1')
  149. style_name = file_resources.GetDefaultStyleForDir(test_dir)
  150. self.assertEqual(style_name, 'pep8')
  151. # One with a '[tool.yapf]' section should be used
  152. with open(pyproject_toml, 'w') as f:
  153. f.write('[tool.yapf]\n')
  154. self.assertEqual(pyproject_toml,
  155. file_resources.GetDefaultStyleForDir(test_dir))
  156. def test_local_style_at_root(self):
  157. # Test behavior of files located on the root, and under root.
  158. rootdir = os.path.abspath(os.path.sep)
  159. test_dir_at_root = os.path.join(rootdir, 'dir1')
  160. test_dir_under_root = os.path.join(rootdir, 'dir1', 'dir2')
  161. # Fake placing only a style file at the root by mocking `os.path.exists`.
  162. style_file = os.path.join(rootdir, '.style.yapf')
  163. def mock_exists_implementation(path):
  164. return path == style_file
  165. with _exists_mocked_in_module(file_resources.os.path,
  166. mock_exists_implementation):
  167. # Both files should find the style file at the root.
  168. default_style_at_root = file_resources.GetDefaultStyleForDir(
  169. test_dir_at_root)
  170. self.assertEqual(style_file, default_style_at_root)
  171. default_style_under_root = file_resources.GetDefaultStyleForDir(
  172. test_dir_under_root)
  173. self.assertEqual(style_file, default_style_under_root)
  174. def _touch_files(filenames):
  175. for name in filenames:
  176. open(name, 'a').close()
  177. class GetCommandLineFilesTest(unittest.TestCase):
  178. def setUp(self): # pylint: disable=g-missing-super-call
  179. self.test_tmpdir = tempfile.mkdtemp()
  180. self.old_dir = os.getcwd()
  181. def tearDown(self): # pylint: disable=g-missing-super-call
  182. os.chdir(self.old_dir)
  183. shutil.rmtree(self.test_tmpdir)
  184. def _make_test_dir(self, name):
  185. fullpath = os.path.normpath(os.path.join(self.test_tmpdir, name))
  186. os.makedirs(fullpath)
  187. return fullpath
  188. def test_find_files_not_dirs(self):
  189. tdir1 = self._make_test_dir('test1')
  190. tdir2 = self._make_test_dir('test2')
  191. file1 = os.path.join(tdir1, 'testfile1.py')
  192. file2 = os.path.join(tdir2, 'testfile2.py')
  193. _touch_files([file1, file2])
  194. self.assertEqual(
  195. file_resources.GetCommandLineFiles([file1, file2],
  196. recursive=False,
  197. exclude=None), [file1, file2])
  198. self.assertEqual(
  199. file_resources.GetCommandLineFiles([file1, file2],
  200. recursive=True,
  201. exclude=None), [file1, file2])
  202. def test_nonrecursive_find_in_dir(self):
  203. tdir1 = self._make_test_dir('test1')
  204. tdir2 = self._make_test_dir('test1/foo')
  205. file1 = os.path.join(tdir1, 'testfile1.py')
  206. file2 = os.path.join(tdir2, 'testfile2.py')
  207. _touch_files([file1, file2])
  208. self.assertRaises(
  209. errors.YapfError,
  210. file_resources.GetCommandLineFiles,
  211. command_line_file_list=[tdir1],
  212. recursive=False,
  213. exclude=None)
  214. def test_recursive_find_in_dir(self):
  215. tdir1 = self._make_test_dir('test1')
  216. tdir2 = self._make_test_dir('test2/testinner/')
  217. tdir3 = self._make_test_dir('test3/foo/bar/bas/xxx')
  218. files = [
  219. os.path.join(tdir1, 'testfile1.py'),
  220. os.path.join(tdir2, 'testfile2.py'),
  221. os.path.join(tdir3, 'testfile3.py'),
  222. ]
  223. _touch_files(files)
  224. self.assertEqual(
  225. sorted(
  226. file_resources.GetCommandLineFiles([self.test_tmpdir],
  227. recursive=True,
  228. exclude=None)), sorted(files))
  229. def test_recursive_find_in_dir_with_exclude(self):
  230. tdir1 = self._make_test_dir('test1')
  231. tdir2 = self._make_test_dir('test2/testinner/')
  232. tdir3 = self._make_test_dir('test3/foo/bar/bas/xxx')
  233. files = [
  234. os.path.join(tdir1, 'testfile1.py'),
  235. os.path.join(tdir2, 'testfile2.py'),
  236. os.path.join(tdir3, 'testfile3.py'),
  237. ]
  238. _touch_files(files)
  239. self.assertEqual(
  240. sorted(
  241. file_resources.GetCommandLineFiles([self.test_tmpdir],
  242. recursive=True,
  243. exclude=['*test*3.py'])),
  244. sorted([
  245. os.path.join(tdir1, 'testfile1.py'),
  246. os.path.join(tdir2, 'testfile2.py'),
  247. ]))
  248. def test_find_with_excluded_hidden_dirs(self):
  249. tdir1 = self._make_test_dir('.test1')
  250. tdir2 = self._make_test_dir('test_2')
  251. tdir3 = self._make_test_dir('test.3')
  252. files = [
  253. os.path.join(tdir1, 'testfile1.py'),
  254. os.path.join(tdir2, 'testfile2.py'),
  255. os.path.join(tdir3, 'testfile3.py'),
  256. ]
  257. _touch_files(files)
  258. actual = file_resources.GetCommandLineFiles([self.test_tmpdir],
  259. recursive=True,
  260. exclude=['*.test1*'])
  261. self.assertEqual(
  262. sorted(actual),
  263. sorted([
  264. os.path.join(tdir2, 'testfile2.py'),
  265. os.path.join(tdir3, 'testfile3.py'),
  266. ]))
  267. def test_find_with_excluded_hidden_dirs_relative(self):
  268. """Test find with excluded hidden dirs.
  269. A regression test against a specific case where a hidden directory (one
  270. beginning with a period) is being excluded, but it is also an immediate
  271. child of the current directory which has been specified in a relative
  272. manner.
  273. At its core, the bug has to do with overzealous stripping of "./foo" so that
  274. it removes too much from "./.foo" .
  275. """
  276. tdir1 = self._make_test_dir('.test1')
  277. tdir2 = self._make_test_dir('test_2')
  278. tdir3 = self._make_test_dir('test.3')
  279. files = [
  280. os.path.join(tdir1, 'testfile1.py'),
  281. os.path.join(tdir2, 'testfile2.py'),
  282. os.path.join(tdir3, 'testfile3.py'),
  283. ]
  284. _touch_files(files)
  285. # We must temporarily change the current directory, so that we test against
  286. # patterns like ./.test1/file instead of /tmp/foo/.test1/file
  287. with _restore_working_dir():
  288. os.chdir(self.test_tmpdir)
  289. actual = file_resources.GetCommandLineFiles(
  290. [os.path.relpath(self.test_tmpdir)],
  291. recursive=True,
  292. exclude=['*.test1*'])
  293. self.assertEqual(
  294. sorted(actual),
  295. sorted([
  296. os.path.join(
  297. os.path.relpath(self.test_tmpdir), os.path.basename(tdir2),
  298. 'testfile2.py'),
  299. os.path.join(
  300. os.path.relpath(self.test_tmpdir), os.path.basename(tdir3),
  301. 'testfile3.py'),
  302. ]))
  303. def test_find_with_excluded_dirs(self):
  304. tdir1 = self._make_test_dir('test1')
  305. tdir2 = self._make_test_dir('test2/testinner/')
  306. tdir3 = self._make_test_dir('test3/foo/bar/bas/xxx')
  307. files = [
  308. os.path.join(tdir1, 'testfile1.py'),
  309. os.path.join(tdir2, 'testfile2.py'),
  310. os.path.join(tdir3, 'testfile3.py'),
  311. ]
  312. _touch_files(files)
  313. os.chdir(self.test_tmpdir)
  314. found = sorted(
  315. file_resources.GetCommandLineFiles(['test1', 'test2', 'test3'],
  316. recursive=True,
  317. exclude=[
  318. 'test1',
  319. 'test2/testinner/',
  320. ]))
  321. self.assertEqual(
  322. found, ['test3/foo/bar/bas/xxx/testfile3.py'.replace('/', os.path.sep)])
  323. found = sorted(
  324. file_resources.GetCommandLineFiles(['.'],
  325. recursive=True,
  326. exclude=[
  327. 'test1',
  328. 'test3',
  329. ]))
  330. self.assertEqual(
  331. found, ['./test2/testinner/testfile2.py'.replace('/', os.path.sep)])
  332. def test_find_with_excluded_current_dir(self):
  333. with self.assertRaises(errors.YapfError):
  334. file_resources.GetCommandLineFiles([], False, exclude=['./z'])
  335. class IsPythonFileTest(unittest.TestCase):
  336. def setUp(self): # pylint: disable=g-missing-super-call
  337. self.test_tmpdir = tempfile.mkdtemp()
  338. def tearDown(self): # pylint: disable=g-missing-super-call
  339. shutil.rmtree(self.test_tmpdir)
  340. def test_with_py_extension(self):
  341. file1 = os.path.join(self.test_tmpdir, 'testfile1.py')
  342. self.assertTrue(file_resources.IsPythonFile(file1))
  343. def test_empty_without_py_extension(self):
  344. file1 = os.path.join(self.test_tmpdir, 'testfile1')
  345. self.assertFalse(file_resources.IsPythonFile(file1))
  346. file2 = os.path.join(self.test_tmpdir, 'testfile1.rb')
  347. self.assertFalse(file_resources.IsPythonFile(file2))
  348. def test_python_shebang(self):
  349. file1 = os.path.join(self.test_tmpdir, 'testfile1')
  350. with open(file1, 'w') as f:
  351. f.write('#!/usr/bin/python\n')
  352. self.assertTrue(file_resources.IsPythonFile(file1))
  353. file2 = os.path.join(self.test_tmpdir, 'testfile2.run')
  354. with open(file2, 'w') as f:
  355. f.write('#! /bin/python2\n')
  356. self.assertTrue(file_resources.IsPythonFile(file1))
  357. def test_with_latin_encoding(self):
  358. file1 = os.path.join(self.test_tmpdir, 'testfile1')
  359. with codecs.open(file1, mode='w', encoding='latin-1') as f:
  360. f.write('#! /bin/python2\n')
  361. self.assertTrue(file_resources.IsPythonFile(file1))
  362. def test_with_invalid_encoding(self):
  363. file1 = os.path.join(self.test_tmpdir, 'testfile1')
  364. with open(file1, 'w') as f:
  365. f.write('#! /bin/python2\n')
  366. f.write('# -*- coding: iso-3-14159 -*-\n')
  367. self.assertFalse(file_resources.IsPythonFile(file1))
  368. class IsIgnoredTest(unittest.TestCase):
  369. def test_root_path(self):
  370. self.assertTrue(file_resources.IsIgnored('media', ['media']))
  371. self.assertFalse(file_resources.IsIgnored('media', ['media/*']))
  372. def test_sub_path(self):
  373. self.assertTrue(file_resources.IsIgnored('media/a', ['*/a']))
  374. self.assertTrue(file_resources.IsIgnored('media/b', ['media/*']))
  375. self.assertTrue(file_resources.IsIgnored('media/b/c', ['*/*/c']))
  376. def test_trailing_slash(self):
  377. self.assertTrue(file_resources.IsIgnored('z', ['z']))
  378. self.assertTrue(file_resources.IsIgnored('z', ['z' + os.path.sep]))
  379. class BufferedByteStream(object):
  380. def __init__(self):
  381. self.stream = BytesIO()
  382. def getvalue(self): # pylint: disable=invalid-name
  383. return self.stream.getvalue().decode('utf-8')
  384. @property
  385. def buffer(self):
  386. return self.stream
  387. class WriteReformattedCodeTest(unittest.TestCase):
  388. @classmethod
  389. def setUpClass(cls): # pylint: disable=g-missing-super-call
  390. cls.test_tmpdir = tempfile.mkdtemp()
  391. @classmethod
  392. def tearDownClass(cls): # pylint: disable=g-missing-super-call
  393. shutil.rmtree(cls.test_tmpdir)
  394. def test_write_to_file(self):
  395. s = 'foobar\n'
  396. with utils.NamedTempFile(dirname=self.test_tmpdir) as (f, fname):
  397. file_resources.WriteReformattedCode(
  398. fname, s, in_place=True, encoding='utf-8')
  399. f.flush()
  400. with open(fname) as f2:
  401. self.assertEqual(f2.read(), s)
  402. def test_write_to_stdout(self):
  403. s = 'foobar'
  404. stream = BufferedByteStream()
  405. with utils.stdout_redirector(stream):
  406. file_resources.WriteReformattedCode(
  407. None, s, in_place=False, encoding='utf-8')
  408. self.assertEqual(stream.getvalue(), s)
  409. def test_write_encoded_to_stdout(self):
  410. s = '\ufeff# -*- coding: utf-8 -*-\nresult = "passed"\n' # pylint: disable=anomalous-unicode-escape-in-string # noqa
  411. stream = BufferedByteStream()
  412. with utils.stdout_redirector(stream):
  413. file_resources.WriteReformattedCode(
  414. None, s, in_place=False, encoding='utf-8')
  415. self.assertEqual(stream.getvalue(), s)
  416. class LineEndingTest(unittest.TestCase):
  417. def test_line_ending_linefeed(self):
  418. lines = ['spam\n', 'spam\n']
  419. actual = file_resources.LineEnding(lines)
  420. self.assertEqual(actual, '\n')
  421. def test_line_ending_carriage_return(self):
  422. lines = ['spam\r', 'spam\r']
  423. actual = file_resources.LineEnding(lines)
  424. self.assertEqual(actual, '\r')
  425. def test_line_ending_combo(self):
  426. lines = ['spam\r\n', 'spam\r\n']
  427. actual = file_resources.LineEnding(lines)
  428. self.assertEqual(actual, '\r\n')
  429. def test_line_ending_weighted(self):
  430. lines = [
  431. 'spam\n',
  432. 'spam\n',
  433. 'spam\r',
  434. 'spam\r\n',
  435. ]
  436. actual = file_resources.LineEnding(lines)
  437. self.assertEqual(actual, '\n')
  438. def test_line_ending_empty(self):
  439. lines = []
  440. actual = file_resources.LineEnding(lines)
  441. self.assertEqual(actual, '\n')
  442. def test_line_ending_no_newline(self):
  443. lines = ['spam']
  444. actual = file_resources.LineEnding(lines)
  445. self.assertEqual(actual, '\n')
  446. def test_line_ending_tie(self):
  447. lines = [
  448. 'spam\n',
  449. 'spam\n',
  450. 'spam\r\n',
  451. 'spam\r\n',
  452. ]
  453. actual = file_resources.LineEnding(lines)
  454. self.assertEqual(actual, '\n')
  455. if __name__ == '__main__':
  456. unittest.main()