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