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