xref: /aosp_15_r20/external/yapf/yapftests/file_resources_test.py (revision 7249d1a64f4850ccf838e62a46276f891f72998e)
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
17import contextlib
18import os
19import shutil
20import tempfile
21import unittest
22
23from yapf.yapflib import errors
24from yapf.yapflib import file_resources
25from yapf.yapflib import py3compat
26
27from yapftests import utils
28
29
30@contextlib.contextmanager
31def _restore_working_dir():
32  curdir = os.getcwd()
33  try:
34    yield
35  finally:
36    os.chdir(curdir)
37
38
39@contextlib.contextmanager
40def _exists_mocked_in_module(module, mock_implementation):
41  unmocked_exists = getattr(module, 'exists')
42  setattr(module, 'exists', mock_implementation)
43  try:
44    yield
45  finally:
46    setattr(module, 'exists', unmocked_exists)
47
48
49class GetExcludePatternsForDir(unittest.TestCase):
50
51  def setUp(self):  # pylint: disable=g-missing-super-call
52    self.test_tmpdir = tempfile.mkdtemp()
53
54  def tearDown(self):  # pylint: disable=g-missing-super-call
55    shutil.rmtree(self.test_tmpdir)
56
57  def test_get_exclude_file_patterns_from_yapfignore(self):
58    local_ignore_file = os.path.join(self.test_tmpdir, '.yapfignore')
59    ignore_patterns = ['temp/**/*.py', 'temp2/*.py']
60    with open(local_ignore_file, 'w') as f:
61      f.writelines('\n'.join(ignore_patterns))
62
63    self.assertEqual(
64        sorted(file_resources.GetExcludePatternsForDir(self.test_tmpdir)),
65        sorted(ignore_patterns))
66
67  def test_get_exclude_file_patterns_from_yapfignore_with_wrong_syntax(self):
68    local_ignore_file = os.path.join(self.test_tmpdir, '.yapfignore')
69    ignore_patterns = ['temp/**/*.py', './wrong/syntax/*.py']
70    with open(local_ignore_file, 'w') as f:
71      f.writelines('\n'.join(ignore_patterns))
72
73    with self.assertRaises(errors.YapfError):
74      file_resources.GetExcludePatternsForDir(self.test_tmpdir)
75
76  def test_get_exclude_file_patterns_from_pyproject(self):
77    try:
78      import toml
79    except ImportError:
80      return
81    local_ignore_file = os.path.join(self.test_tmpdir, 'pyproject.toml')
82    ignore_patterns = ['temp/**/*.py', 'temp2/*.py']
83    with open(local_ignore_file, 'w') as f:
84      f.write('[tool.yapfignore]\n')
85      f.write('ignore_patterns=[')
86      f.writelines('\n,'.join(['"{}"'.format(p) for p in ignore_patterns]))
87      f.write(']')
88
89    self.assertEqual(
90        sorted(file_resources.GetExcludePatternsForDir(self.test_tmpdir)),
91        sorted(ignore_patterns))
92
93  @unittest.skipUnless(py3compat.PY36, 'Requires Python 3.6')
94  def test_get_exclude_file_patterns_from_pyproject_with_wrong_syntax(self):
95    try:
96      import toml
97    except ImportError:
98      return
99    local_ignore_file = os.path.join(self.test_tmpdir, 'pyproject.toml')
100    ignore_patterns = ['temp/**/*.py', './wrong/syntax/*.py']
101    with open(local_ignore_file, 'w') as f:
102      f.write('[tool.yapfignore]\n')
103      f.write('ignore_patterns=[')
104      f.writelines('\n,'.join(['"{}"'.format(p) for p in ignore_patterns]))
105      f.write(']')
106
107    with self.assertRaises(errors.YapfError):
108      file_resources.GetExcludePatternsForDir(self.test_tmpdir)
109
110  def test_get_exclude_file_patterns_from_pyproject_no_ignore_section(self):
111    try:
112      import toml
113    except ImportError:
114      return
115    local_ignore_file = os.path.join(self.test_tmpdir, 'pyproject.toml')
116    ignore_patterns = []
117    open(local_ignore_file, 'w').close()
118
119    self.assertEqual(
120        sorted(file_resources.GetExcludePatternsForDir(self.test_tmpdir)),
121        sorted(ignore_patterns))
122
123  def test_get_exclude_file_patterns_from_pyproject_ignore_section_empty(self):
124    try:
125      import toml
126    except ImportError:
127      return
128    local_ignore_file = os.path.join(self.test_tmpdir, 'pyproject.toml')
129    ignore_patterns = []
130    with open(local_ignore_file, 'w') as f:
131      f.write('[tool.yapfignore]\n')
132
133    self.assertEqual(
134        sorted(file_resources.GetExcludePatternsForDir(self.test_tmpdir)),
135        sorted(ignore_patterns))
136
137  def test_get_exclude_file_patterns_with_no_config_files(self):
138    ignore_patterns = []
139
140    self.assertEqual(
141        sorted(file_resources.GetExcludePatternsForDir(self.test_tmpdir)),
142        sorted(ignore_patterns))
143
144
145class GetDefaultStyleForDirTest(unittest.TestCase):
146
147  def setUp(self):  # pylint: disable=g-missing-super-call
148    self.test_tmpdir = tempfile.mkdtemp()
149
150  def tearDown(self):  # pylint: disable=g-missing-super-call
151    shutil.rmtree(self.test_tmpdir)
152
153  def test_no_local_style(self):
154    test_file = os.path.join(self.test_tmpdir, 'file.py')
155    style_name = file_resources.GetDefaultStyleForDir(test_file)
156    self.assertEqual(style_name, 'pep8')
157
158  def test_no_local_style_custom_default(self):
159    test_file = os.path.join(self.test_tmpdir, 'file.py')
160    style_name = file_resources.GetDefaultStyleForDir(
161        test_file, default_style='custom-default')
162    self.assertEqual(style_name, 'custom-default')
163
164  def test_with_local_style(self):
165    # Create an empty .style.yapf file in test_tmpdir
166    style_file = os.path.join(self.test_tmpdir, '.style.yapf')
167    open(style_file, 'w').close()
168
169    test_filename = os.path.join(self.test_tmpdir, 'file.py')
170    self.assertEqual(style_file,
171                     file_resources.GetDefaultStyleForDir(test_filename))
172
173    test_filename = os.path.join(self.test_tmpdir, 'dir1', 'file.py')
174    self.assertEqual(style_file,
175                     file_resources.GetDefaultStyleForDir(test_filename))
176
177  def test_setup_config(self):
178    # An empty setup.cfg file should not be used
179    setup_config = os.path.join(self.test_tmpdir, 'setup.cfg')
180    open(setup_config, 'w').close()
181
182    test_dir = os.path.join(self.test_tmpdir, 'dir1')
183    style_name = file_resources.GetDefaultStyleForDir(test_dir)
184    self.assertEqual(style_name, 'pep8')
185
186    # One with a '[yapf]' section should be used
187    with open(setup_config, 'w') as f:
188      f.write('[yapf]\n')
189    self.assertEqual(setup_config,
190                     file_resources.GetDefaultStyleForDir(test_dir))
191
192  def test_pyproject_toml(self):
193    # An empty pyproject.toml file should not be used
194    try:
195      import toml
196    except ImportError:
197      return
198
199    pyproject_toml = os.path.join(self.test_tmpdir, 'pyproject.toml')
200    open(pyproject_toml, 'w').close()
201
202    test_dir = os.path.join(self.test_tmpdir, 'dir1')
203    style_name = file_resources.GetDefaultStyleForDir(test_dir)
204    self.assertEqual(style_name, 'pep8')
205
206    # One with a '[tool.yapf]' section should be used
207    with open(pyproject_toml, 'w') as f:
208      f.write('[tool.yapf]\n')
209    self.assertEqual(pyproject_toml,
210                     file_resources.GetDefaultStyleForDir(test_dir))
211
212  def test_local_style_at_root(self):
213    # Test behavior of files located on the root, and under root.
214    rootdir = os.path.abspath(os.path.sep)
215    test_dir_at_root = os.path.join(rootdir, 'dir1')
216    test_dir_under_root = os.path.join(rootdir, 'dir1', 'dir2')
217
218    # Fake placing only a style file at the root by mocking `os.path.exists`.
219    style_file = os.path.join(rootdir, '.style.yapf')
220
221    def mock_exists_implementation(path):
222      return path == style_file
223
224    with _exists_mocked_in_module(file_resources.os.path,
225                                  mock_exists_implementation):
226      # Both files should find the style file at the root.
227      default_style_at_root = file_resources.GetDefaultStyleForDir(
228          test_dir_at_root)
229      self.assertEqual(style_file, default_style_at_root)
230      default_style_under_root = file_resources.GetDefaultStyleForDir(
231          test_dir_under_root)
232      self.assertEqual(style_file, default_style_under_root)
233
234
235def _touch_files(filenames):
236  for name in filenames:
237    open(name, 'a').close()
238
239
240class GetCommandLineFilesTest(unittest.TestCase):
241
242  def setUp(self):  # pylint: disable=g-missing-super-call
243    self.test_tmpdir = tempfile.mkdtemp()
244    self.old_dir = os.getcwd()
245
246  def tearDown(self):  # pylint: disable=g-missing-super-call
247    os.chdir(self.old_dir)
248    shutil.rmtree(self.test_tmpdir)
249
250  def _make_test_dir(self, name):
251    fullpath = os.path.normpath(os.path.join(self.test_tmpdir, name))
252    os.makedirs(fullpath)
253    return fullpath
254
255  def test_find_files_not_dirs(self):
256    tdir1 = self._make_test_dir('test1')
257    tdir2 = self._make_test_dir('test2')
258    file1 = os.path.join(tdir1, 'testfile1.py')
259    file2 = os.path.join(tdir2, 'testfile2.py')
260    _touch_files([file1, file2])
261
262    self.assertEqual(
263        file_resources.GetCommandLineFiles([file1, file2],
264                                           recursive=False,
265                                           exclude=None), [file1, file2])
266    self.assertEqual(
267        file_resources.GetCommandLineFiles([file1, file2],
268                                           recursive=True,
269                                           exclude=None), [file1, file2])
270
271  def test_nonrecursive_find_in_dir(self):
272    tdir1 = self._make_test_dir('test1')
273    tdir2 = self._make_test_dir('test1/foo')
274    file1 = os.path.join(tdir1, 'testfile1.py')
275    file2 = os.path.join(tdir2, 'testfile2.py')
276    _touch_files([file1, file2])
277
278    self.assertRaises(
279        errors.YapfError,
280        file_resources.GetCommandLineFiles,
281        command_line_file_list=[tdir1],
282        recursive=False,
283        exclude=None)
284
285  def test_recursive_find_in_dir(self):
286    tdir1 = self._make_test_dir('test1')
287    tdir2 = self._make_test_dir('test2/testinner/')
288    tdir3 = self._make_test_dir('test3/foo/bar/bas/xxx')
289    files = [
290        os.path.join(tdir1, 'testfile1.py'),
291        os.path.join(tdir2, 'testfile2.py'),
292        os.path.join(tdir3, 'testfile3.py'),
293    ]
294    _touch_files(files)
295
296    self.assertEqual(
297        sorted(
298            file_resources.GetCommandLineFiles([self.test_tmpdir],
299                                               recursive=True,
300                                               exclude=None)), sorted(files))
301
302  def test_recursive_find_in_dir_with_exclude(self):
303    tdir1 = self._make_test_dir('test1')
304    tdir2 = self._make_test_dir('test2/testinner/')
305    tdir3 = self._make_test_dir('test3/foo/bar/bas/xxx')
306    files = [
307        os.path.join(tdir1, 'testfile1.py'),
308        os.path.join(tdir2, 'testfile2.py'),
309        os.path.join(tdir3, 'testfile3.py'),
310    ]
311    _touch_files(files)
312
313    self.assertEqual(
314        sorted(
315            file_resources.GetCommandLineFiles([self.test_tmpdir],
316                                               recursive=True,
317                                               exclude=['*test*3.py'])),
318        sorted([
319            os.path.join(tdir1, 'testfile1.py'),
320            os.path.join(tdir2, 'testfile2.py'),
321        ]))
322
323  def test_find_with_excluded_hidden_dirs(self):
324    tdir1 = self._make_test_dir('.test1')
325    tdir2 = self._make_test_dir('test_2')
326    tdir3 = self._make_test_dir('test.3')
327    files = [
328        os.path.join(tdir1, 'testfile1.py'),
329        os.path.join(tdir2, 'testfile2.py'),
330        os.path.join(tdir3, 'testfile3.py'),
331    ]
332    _touch_files(files)
333
334    actual = file_resources.GetCommandLineFiles([self.test_tmpdir],
335                                                recursive=True,
336                                                exclude=['*.test1*'])
337
338    self.assertEqual(
339        sorted(actual),
340        sorted([
341            os.path.join(tdir2, 'testfile2.py'),
342            os.path.join(tdir3, 'testfile3.py'),
343        ]))
344
345  def test_find_with_excluded_hidden_dirs_relative(self):
346    """Test find with excluded hidden dirs.
347
348    A regression test against a specific case where a hidden directory (one
349    beginning with a period) is being excluded, but it is also an immediate
350    child of the current directory which has been specified in a relative
351    manner.
352
353    At its core, the bug has to do with overzelous stripping of "./foo" so that
354    it removes too much from "./.foo" .
355    """
356    tdir1 = self._make_test_dir('.test1')
357    tdir2 = self._make_test_dir('test_2')
358    tdir3 = self._make_test_dir('test.3')
359    files = [
360        os.path.join(tdir1, 'testfile1.py'),
361        os.path.join(tdir2, 'testfile2.py'),
362        os.path.join(tdir3, 'testfile3.py'),
363    ]
364    _touch_files(files)
365
366    # We must temporarily change the current directory, so that we test against
367    # patterns like ./.test1/file instead of /tmp/foo/.test1/file
368    with _restore_working_dir():
369
370      os.chdir(self.test_tmpdir)
371      actual = file_resources.GetCommandLineFiles(
372          [os.path.relpath(self.test_tmpdir)],
373          recursive=True,
374          exclude=['*.test1*'])
375
376      self.assertEqual(
377          sorted(actual),
378          sorted([
379              os.path.join(
380                  os.path.relpath(self.test_tmpdir), os.path.basename(tdir2),
381                  'testfile2.py'),
382              os.path.join(
383                  os.path.relpath(self.test_tmpdir), os.path.basename(tdir3),
384                  'testfile3.py'),
385          ]))
386
387  def test_find_with_excluded_dirs(self):
388    tdir1 = self._make_test_dir('test1')
389    tdir2 = self._make_test_dir('test2/testinner/')
390    tdir3 = self._make_test_dir('test3/foo/bar/bas/xxx')
391    files = [
392        os.path.join(tdir1, 'testfile1.py'),
393        os.path.join(tdir2, 'testfile2.py'),
394        os.path.join(tdir3, 'testfile3.py'),
395    ]
396    _touch_files(files)
397
398    os.chdir(self.test_tmpdir)
399
400    found = sorted(
401        file_resources.GetCommandLineFiles(['test1', 'test2', 'test3'],
402                                           recursive=True,
403                                           exclude=[
404                                               'test1',
405                                               'test2/testinner/',
406                                           ]))
407
408    self.assertEqual(
409        found, ['test3/foo/bar/bas/xxx/testfile3.py'.replace("/", os.path.sep)])
410
411    found = sorted(
412        file_resources.GetCommandLineFiles(['.'],
413                                           recursive=True,
414                                           exclude=[
415                                               'test1',
416                                               'test3',
417                                           ]))
418
419    self.assertEqual(
420        found, ['./test2/testinner/testfile2.py'.replace("/", os.path.sep)])
421
422  def test_find_with_excluded_current_dir(self):
423    with self.assertRaises(errors.YapfError):
424      file_resources.GetCommandLineFiles([], False, exclude=['./z'])
425
426
427class IsPythonFileTest(unittest.TestCase):
428
429  def setUp(self):  # pylint: disable=g-missing-super-call
430    self.test_tmpdir = tempfile.mkdtemp()
431
432  def tearDown(self):  # pylint: disable=g-missing-super-call
433    shutil.rmtree(self.test_tmpdir)
434
435  def test_with_py_extension(self):
436    file1 = os.path.join(self.test_tmpdir, 'testfile1.py')
437    self.assertTrue(file_resources.IsPythonFile(file1))
438
439  def test_empty_without_py_extension(self):
440    file1 = os.path.join(self.test_tmpdir, 'testfile1')
441    self.assertFalse(file_resources.IsPythonFile(file1))
442    file2 = os.path.join(self.test_tmpdir, 'testfile1.rb')
443    self.assertFalse(file_resources.IsPythonFile(file2))
444
445  def test_python_shebang(self):
446    file1 = os.path.join(self.test_tmpdir, 'testfile1')
447    with open(file1, 'w') as f:
448      f.write(u'#!/usr/bin/python\n')
449    self.assertTrue(file_resources.IsPythonFile(file1))
450
451    file2 = os.path.join(self.test_tmpdir, 'testfile2.run')
452    with open(file2, 'w') as f:
453      f.write(u'#! /bin/python2\n')
454    self.assertTrue(file_resources.IsPythonFile(file1))
455
456  def test_with_latin_encoding(self):
457    file1 = os.path.join(self.test_tmpdir, 'testfile1')
458    with py3compat.open_with_encoding(file1, mode='w', encoding='latin-1') as f:
459      f.write(u'#! /bin/python2\n')
460    self.assertTrue(file_resources.IsPythonFile(file1))
461
462  def test_with_invalid_encoding(self):
463    file1 = os.path.join(self.test_tmpdir, 'testfile1')
464    with open(file1, 'w') as f:
465      f.write(u'#! /bin/python2\n')
466      f.write(u'# -*- coding: iso-3-14159 -*-\n')
467    self.assertFalse(file_resources.IsPythonFile(file1))
468
469
470class IsIgnoredTest(unittest.TestCase):
471
472  def test_root_path(self):
473    self.assertTrue(file_resources.IsIgnored('media', ['media']))
474    self.assertFalse(file_resources.IsIgnored('media', ['media/*']))
475
476  def test_sub_path(self):
477    self.assertTrue(file_resources.IsIgnored('media/a', ['*/a']))
478    self.assertTrue(file_resources.IsIgnored('media/b', ['media/*']))
479    self.assertTrue(file_resources.IsIgnored('media/b/c', ['*/*/c']))
480
481  def test_trailing_slash(self):
482    self.assertTrue(file_resources.IsIgnored('z', ['z']))
483    self.assertTrue(file_resources.IsIgnored('z', ['z' + os.path.sep]))
484
485
486class BufferedByteStream(object):
487
488  def __init__(self):
489    self.stream = py3compat.BytesIO()
490
491  def getvalue(self):  # pylint: disable=invalid-name
492    return self.stream.getvalue().decode('utf-8')
493
494  @property
495  def buffer(self):
496    return self.stream
497
498
499class WriteReformattedCodeTest(unittest.TestCase):
500
501  @classmethod
502  def setUpClass(cls):  # pylint: disable=g-missing-super-call
503    cls.test_tmpdir = tempfile.mkdtemp()
504
505  @classmethod
506  def tearDownClass(cls):  # pylint: disable=g-missing-super-call
507    shutil.rmtree(cls.test_tmpdir)
508
509  def test_write_to_file(self):
510    s = u'foobar\n'
511    with utils.NamedTempFile(dirname=self.test_tmpdir) as (f, fname):
512      file_resources.WriteReformattedCode(
513          fname, s, in_place=True, encoding='utf-8')
514      f.flush()
515
516      with open(fname) as f2:
517        self.assertEqual(f2.read(), s)
518
519  def test_write_to_stdout(self):
520    s = u'foobar'
521    stream = BufferedByteStream() if py3compat.PY3 else py3compat.StringIO()
522    with utils.stdout_redirector(stream):
523      file_resources.WriteReformattedCode(
524          None, s, in_place=False, encoding='utf-8')
525    self.assertEqual(stream.getvalue(), s)
526
527  def test_write_encoded_to_stdout(self):
528    s = '\ufeff# -*- coding: utf-8 -*-\nresult = "passed"\n'  # pylint: disable=anomalous-unicode-escape-in-string # noqa
529    stream = BufferedByteStream() if py3compat.PY3 else py3compat.StringIO()
530    with utils.stdout_redirector(stream):
531      file_resources.WriteReformattedCode(
532          None, s, in_place=False, encoding='utf-8')
533    self.assertEqual(stream.getvalue(), s)
534
535
536class LineEndingTest(unittest.TestCase):
537
538  def test_line_ending_linefeed(self):
539    lines = ['spam\n', 'spam\n']
540    actual = file_resources.LineEnding(lines)
541    self.assertEqual(actual, '\n')
542
543  def test_line_ending_carriage_return(self):
544    lines = ['spam\r', 'spam\r']
545    actual = file_resources.LineEnding(lines)
546    self.assertEqual(actual, '\r')
547
548  def test_line_ending_combo(self):
549    lines = ['spam\r\n', 'spam\r\n']
550    actual = file_resources.LineEnding(lines)
551    self.assertEqual(actual, '\r\n')
552
553  def test_line_ending_weighted(self):
554    lines = [
555        'spam\n',
556        'spam\n',
557        'spam\r',
558        'spam\r\n',
559    ]
560    actual = file_resources.LineEnding(lines)
561    self.assertEqual(actual, '\n')
562
563
564if __name__ == '__main__':
565  unittest.main()
566