1 """Tests for 'site'. 2 3 Tests assume the initial paths in sys.path once the interpreter has begun 4 executing have not been removed. 5 6 """ 7 import unittest 8 import test.support 9 from test.support import captured_stderr, TESTFN, EnvironmentVarGuard 10 import builtins 11 import os 12 import sys 13 import re 14 import encodings 15 import urllib.request 16 import urllib.error 17 import shutil 18 import subprocess 19 import sysconfig 20 from copy import copy 21 22 # These tests are not particularly useful if Python was invoked with -S. 23 # If you add tests that are useful under -S, this skip should be moved 24 # to the class level. 25 if sys.flags.no_site: 26 raise unittest.SkipTest("Python was invoked with -S") 27 28 import site 29 30 if site.ENABLE_USER_SITE and not os.path.isdir(site.USER_SITE): 31 # need to add user site directory for tests 32 try: 33 os.makedirs(site.USER_SITE) 34 site.addsitedir(site.USER_SITE) 35 except PermissionError as exc: 36 raise unittest.SkipTest('unable to create user site directory (%r): %s' 37 % (site.USER_SITE, exc)) 38 39 40 class HelperFunctionsTests(unittest.TestCase): 41 """Tests for helper functions. 42 """ 43 44 def setUp(self): 45 """Save a copy of sys.path""" 46 self.sys_path = sys.path[:] 47 self.old_base = site.USER_BASE 48 self.old_site = site.USER_SITE 49 self.old_prefixes = site.PREFIXES 50 self.original_vars = sysconfig._CONFIG_VARS 51 self.old_vars = copy(sysconfig._CONFIG_VARS) 52 53 def tearDown(self): 54 """Restore sys.path""" 55 sys.path[:] = self.sys_path 56 site.USER_BASE = self.old_base 57 site.USER_SITE = self.old_site 58 site.PREFIXES = self.old_prefixes 59 sysconfig._CONFIG_VARS = self.original_vars 60 sysconfig._CONFIG_VARS.clear() 61 sysconfig._CONFIG_VARS.update(self.old_vars) 62 63 def test_makepath(self): 64 # Test makepath() have an absolute path for its first return value 65 # and a case-normalized version of the absolute path for its 66 # second value. 67 path_parts = ("Beginning", "End") 68 original_dir = os.path.join(*path_parts) 69 abs_dir, norm_dir = site.makepath(*path_parts) 70 self.assertEqual(os.path.abspath(original_dir), abs_dir) 71 if original_dir == os.path.normcase(original_dir): 72 self.assertEqual(abs_dir, norm_dir) 73 else: 74 self.assertEqual(os.path.normcase(abs_dir), norm_dir) 75 76 def test_init_pathinfo(self): 77 dir_set = site._init_pathinfo() 78 for entry in [site.makepath(path)[1] for path in sys.path 79 if path and os.path.exists(path)]: 80 self.assertIn(entry, dir_set, 81 "%s from sys.path not found in set returned " 82 "by _init_pathinfo(): %s" % (entry, dir_set)) 83 84 def pth_file_tests(self, pth_file): 85 """Contain common code for testing results of reading a .pth file""" 86 self.assertIn(pth_file.imported, sys.modules, 87 "%s not in sys.modules" % pth_file.imported) 88 self.assertIn(site.makepath(pth_file.good_dir_path)[0], sys.path) 89 self.assertFalse(os.path.exists(pth_file.bad_dir_path)) 90 91 def test_addpackage(self): 92 # Make sure addpackage() imports if the line starts with 'import', 93 # adds directories to sys.path for any line in the file that is not a 94 # comment or import that is a valid directory name for where the .pth 95 # file resides; invalid directories are not added 96 pth_file = PthFile() 97 pth_file.cleanup(prep=True) # to make sure that nothing is 98 # pre-existing that shouldn't be 99 try: 100 pth_file.create() 101 site.addpackage(pth_file.base_dir, pth_file.filename, set()) 102 self.pth_file_tests(pth_file) 103 finally: 104 pth_file.cleanup() 105 106 def make_pth(self, contents, pth_dir='.', pth_name=TESTFN): 107 # Create a .pth file and return its (abspath, basename). 108 pth_dir = os.path.abspath(pth_dir) 109 pth_basename = pth_name + '.pth' 110 pth_fn = os.path.join(pth_dir, pth_basename) 111 pth_file = open(pth_fn, 'w', encoding='utf-8') 112 self.addCleanup(lambda: os.remove(pth_fn)) 113 pth_file.write(contents) 114 pth_file.close() 115 return pth_dir, pth_basename 116 117 def test_addpackage_import_bad_syntax(self): 118 # Issue 10642 119 pth_dir, pth_fn = self.make_pth("import bad)syntax\n") 120 with captured_stderr() as err_out: 121 site.addpackage(pth_dir, pth_fn, set()) 122 self.assertRegex(err_out.getvalue(), "line 1") 123 self.assertRegex(err_out.getvalue(), 124 re.escape(os.path.join(pth_dir, pth_fn))) 125 # XXX: the previous two should be independent checks so that the 126 # order doesn't matter. The next three could be a single check 127 # but my regex foo isn't good enough to write it. 128 self.assertRegex(err_out.getvalue(), 'Traceback') 129 self.assertRegex(err_out.getvalue(), r'import bad\)syntax') 130 self.assertRegex(err_out.getvalue(), 'SyntaxError') 131 132 def test_addpackage_import_bad_exec(self): 133 # Issue 10642 134 pth_dir, pth_fn = self.make_pth("randompath\nimport nosuchmodule\n") 135 with captured_stderr() as err_out: 136 site.addpackage(pth_dir, pth_fn, set()) 137 self.assertRegex(err_out.getvalue(), "line 2") 138 self.assertRegex(err_out.getvalue(), 139 re.escape(os.path.join(pth_dir, pth_fn))) 140 # XXX: ditto previous XXX comment. 141 self.assertRegex(err_out.getvalue(), 'Traceback') 142 self.assertRegex(err_out.getvalue(), 'ModuleNotFoundError') 143 144 def test_addpackage_import_bad_pth_file(self): 145 # Issue 5258 146 pth_dir, pth_fn = self.make_pth("abc\x00def\n") 147 with captured_stderr() as err_out: 148 site.addpackage(pth_dir, pth_fn, set()) 149 self.assertRegex(err_out.getvalue(), "line 1") 150 self.assertRegex(err_out.getvalue(), 151 re.escape(os.path.join(pth_dir, pth_fn))) 152 # XXX: ditto previous XXX comment. 153 self.assertRegex(err_out.getvalue(), 'Traceback') 154 self.assertRegex(err_out.getvalue(), 'ValueError') 155 156 def test_addsitedir(self): 157 # Same tests for test_addpackage since addsitedir() essentially just 158 # calls addpackage() for every .pth file in the directory 159 pth_file = PthFile() 160 pth_file.cleanup(prep=True) # Make sure that nothing is pre-existing 161 # that is tested for 162 try: 163 pth_file.create() 164 site.addsitedir(pth_file.base_dir, set()) 165 self.pth_file_tests(pth_file) 166 finally: 167 pth_file.cleanup() 168 169 @unittest.skipUnless(site.ENABLE_USER_SITE, "requires access to PEP 370 " 170 "user-site (site.ENABLE_USER_SITE)") 171 def test_s_option(self): 172 usersite = site.USER_SITE 173 self.assertIn(usersite, sys.path) 174 175 env = os.environ.copy() 176 rc = subprocess.call([sys.executable, '-c', 177 'import sys; sys.exit(%r in sys.path)' % usersite], 178 env=env) 179 self.assertEqual(rc, 1) 180 181 env = os.environ.copy() 182 rc = subprocess.call([sys.executable, '-s', '-c', 183 'import sys; sys.exit(%r in sys.path)' % usersite], 184 env=env) 185 if usersite == site.getsitepackages()[0]: 186 self.assertEqual(rc, 1) 187 else: 188 self.assertEqual(rc, 0) 189 190 env = os.environ.copy() 191 env["PYTHONNOUSERSITE"] = "1" 192 rc = subprocess.call([sys.executable, '-c', 193 'import sys; sys.exit(%r in sys.path)' % usersite], 194 env=env) 195 if usersite == site.getsitepackages()[0]: 196 self.assertEqual(rc, 1) 197 else: 198 self.assertEqual(rc, 0) 199 200 env = os.environ.copy() 201 env["PYTHONUSERBASE"] = "/tmp" 202 rc = subprocess.call([sys.executable, '-c', 203 'import sys, site; sys.exit(site.USER_BASE.startswith("/tmp"))'], 204 env=env) 205 self.assertEqual(rc, 1) 206 207 def test_getuserbase(self): 208 site.USER_BASE = None 209 user_base = site.getuserbase() 210 211 # the call sets site.USER_BASE 212 self.assertEqual(site.USER_BASE, user_base) 213 214 # let's set PYTHONUSERBASE and see if it uses it 215 site.USER_BASE = None 216 import sysconfig 217 sysconfig._CONFIG_VARS = None 218 219 with EnvironmentVarGuard() as environ: 220 environ['PYTHONUSERBASE'] = 'xoxo' 221 self.assertTrue(site.getuserbase().startswith('xoxo'), 222 site.getuserbase()) 223 224 def test_getusersitepackages(self): 225 site.USER_SITE = None 226 site.USER_BASE = None 227 user_site = site.getusersitepackages() 228 229 # the call sets USER_BASE *and* USER_SITE 230 self.assertEqual(site.USER_SITE, user_site) 231 self.assertTrue(user_site.startswith(site.USER_BASE), user_site) 232 233 def test_getsitepackages(self): 234 site.PREFIXES = ['xoxo'] 235 dirs = site.getsitepackages() 236 237 if (sys.platform == "darwin" and 238 sysconfig.get_config_var("PYTHONFRAMEWORK")): 239 # OS X framework builds 240 site.PREFIXES = ['Python.framework'] 241 dirs = site.getsitepackages() 242 self.assertEqual(len(dirs), 2) 243 wanted = os.path.join('/Library', 244 sysconfig.get_config_var("PYTHONFRAMEWORK"), 245 '%d.%d' % sys.version_info[:2], 246 'site-packages') 247 self.assertEqual(dirs[1], wanted) 248 elif os.sep == '/': 249 # OS X non-framwework builds, Linux, FreeBSD, etc 250 self.assertEqual(len(dirs), 1) 251 wanted = os.path.join('xoxo', 'lib', 252 'python%d.%d' % sys.version_info[:2], 253 'site-packages') 254 self.assertEqual(dirs[0], wanted) 255 else: 256 # other platforms 257 self.assertEqual(len(dirs), 2) 258 self.assertEqual(dirs[0], 'xoxo') 259 wanted = os.path.join('xoxo', 'lib', 'site-packages') 260 self.assertEqual(dirs[1], wanted) 261 262 class PthFile(object): 263 """Helper class for handling testing of .pth files""" 264 265 def __init__(self, filename_base=TESTFN, imported="time", 266 good_dirname="__testdir__", bad_dirname="__bad"): 267 """Initialize instance variables""" 268 self.filename = filename_base + ".pth" 269 self.base_dir = os.path.abspath('') 270 self.file_path = os.path.join(self.base_dir, self.filename) 271 self.imported = imported 272 self.good_dirname = good_dirname 273 self.bad_dirname = bad_dirname 274 self.good_dir_path = os.path.join(self.base_dir, self.good_dirname) 275 self.bad_dir_path = os.path.join(self.base_dir, self.bad_dirname) 276 277 def create(self): 278 """Create a .pth file with a comment, blank lines, an ``import 279 <self.imported>``, a line with self.good_dirname, and a line with 280 self.bad_dirname. 281 282 Creation of the directory for self.good_dir_path (based off of 283 self.good_dirname) is also performed. 284 285 Make sure to call self.cleanup() to undo anything done by this method. 286 287 """ 288 FILE = open(self.file_path, 'w') 289 try: 290 print("#import @bad module name", file=FILE) 291 print("\n", file=FILE) 292 print("import %s" % self.imported, file=FILE) 293 print(self.good_dirname, file=FILE) 294 print(self.bad_dirname, file=FILE) 295 finally: 296 FILE.close() 297 os.mkdir(self.good_dir_path) 298 299 def cleanup(self, prep=False): 300 """Make sure that the .pth file is deleted, self.imported is not in 301 sys.modules, and that both self.good_dirname and self.bad_dirname are 302 not existing directories.""" 303 if os.path.exists(self.file_path): 304 os.remove(self.file_path) 305 if prep: 306 self.imported_module = sys.modules.get(self.imported) 307 if self.imported_module: 308 del sys.modules[self.imported] 309 else: 310 if self.imported_module: 311 sys.modules[self.imported] = self.imported_module 312 if os.path.exists(self.good_dir_path): 313 os.rmdir(self.good_dir_path) 314 if os.path.exists(self.bad_dir_path): 315 os.rmdir(self.bad_dir_path) 316 317 class ImportSideEffectTests(unittest.TestCase): 318 """Test side-effects from importing 'site'.""" 319 320 def setUp(self): 321 """Make a copy of sys.path""" 322 self.sys_path = sys.path[:] 323 324 def tearDown(self): 325 """Restore sys.path""" 326 sys.path[:] = self.sys_path 327 328 def test_abs_paths(self): 329 # Make sure all imported modules have their __file__ and __cached__ 330 # attributes as absolute paths. Arranging to put the Lib directory on 331 # PYTHONPATH would cause the os module to have a relative path for 332 # __file__ if abs_paths() does not get run. sys and builtins (the 333 # only other modules imported before site.py runs) do not have 334 # __file__ or __cached__ because they are built-in. 335 parent = os.path.relpath(os.path.dirname(os.__file__)) 336 env = os.environ.copy() 337 env['PYTHONPATH'] = parent 338 code = ('import os, sys', 339 # use ASCII to avoid locale issues with non-ASCII directories 340 'os_file = os.__file__.encode("ascii", "backslashreplace")', 341 r'sys.stdout.buffer.write(os_file + b"\n")', 342 'os_cached = os.__cached__.encode("ascii", "backslashreplace")', 343 r'sys.stdout.buffer.write(os_cached + b"\n")') 344 command = '\n'.join(code) 345 # First, prove that with -S (no 'import site'), the paths are 346 # relative. 347 proc = subprocess.Popen([sys.executable, '-S', '-c', command], 348 env=env, 349 stdout=subprocess.PIPE) 350 stdout, stderr = proc.communicate() 351 352 self.assertEqual(proc.returncode, 0) 353 os__file__, os__cached__ = stdout.splitlines()[:2] 354 self.assertFalse(os.path.isabs(os__file__)) 355 self.assertFalse(os.path.isabs(os__cached__)) 356 # Now, with 'import site', it works. 357 proc = subprocess.Popen([sys.executable, '-c', command], 358 env=env, 359 stdout=subprocess.PIPE) 360 stdout, stderr = proc.communicate() 361 self.assertEqual(proc.returncode, 0) 362 os__file__, os__cached__ = stdout.splitlines()[:2] 363 self.assertTrue(os.path.isabs(os__file__), 364 "expected absolute path, got {}" 365 .format(os__file__.decode('ascii'))) 366 self.assertTrue(os.path.isabs(os__cached__), 367 "expected absolute path, got {}" 368 .format(os__cached__.decode('ascii'))) 369 370 def test_no_duplicate_paths(self): 371 # No duplicate paths should exist in sys.path 372 # Handled by removeduppaths() 373 site.removeduppaths() 374 seen_paths = set() 375 for path in sys.path: 376 self.assertNotIn(path, seen_paths) 377 seen_paths.add(path) 378 379 @unittest.skip('test not implemented') 380 def test_add_build_dir(self): 381 # Test that the build directory's Modules directory is used when it 382 # should be. 383 # XXX: implement 384 pass 385 386 def test_setting_quit(self): 387 # 'quit' and 'exit' should be injected into builtins 388 self.assertTrue(hasattr(builtins, "quit")) 389 self.assertTrue(hasattr(builtins, "exit")) 390 391 def test_setting_copyright(self): 392 # 'copyright', 'credits', and 'license' should be in builtins 393 self.assertTrue(hasattr(builtins, "copyright")) 394 self.assertTrue(hasattr(builtins, "credits")) 395 self.assertTrue(hasattr(builtins, "license")) 396 397 def test_setting_help(self): 398 # 'help' should be set in builtins 399 self.assertTrue(hasattr(builtins, "help")) 400 401 def test_aliasing_mbcs(self): 402 if sys.platform == "win32": 403 import locale 404 if locale.getdefaultlocale()[1].startswith('cp'): 405 for value in encodings.aliases.aliases.values(): 406 if value == "mbcs": 407 break 408 else: 409 self.fail("did not alias mbcs") 410 411 def test_sitecustomize_executed(self): 412 # If sitecustomize is available, it should have been imported. 413 if "sitecustomize" not in sys.modules: 414 try: 415 import sitecustomize 416 except ImportError: 417 pass 418 else: 419 self.fail("sitecustomize not imported automatically") 420 421 @test.support.requires_resource('network') 422 @test.support.system_must_validate_cert 423 @unittest.skipUnless(sys.version_info[3] == 'final', 424 'only for released versions') 425 @unittest.skipUnless(hasattr(urllib.request, "HTTPSHandler"), 426 'need SSL support to download license') 427 def test_license_exists_at_url(self): 428 # This test is a bit fragile since it depends on the format of the 429 # string displayed by license in the absence of a LICENSE file. 430 url = license._Printer__data.split()[1] 431 req = urllib.request.Request(url, method='HEAD') 432 try: 433 with test.support.transient_internet(url): 434 with urllib.request.urlopen(req) as data: 435 code = data.getcode() 436 except urllib.error.HTTPError as e: 437 code = e.code 438 self.assertEqual(code, 200, msg="Can't find " + url) 439 440 441 class StartupImportTests(unittest.TestCase): 442 443 def test_startup_imports(self): 444 # This tests checks which modules are loaded by Python when it 445 # initially starts upon startup. 446 popen = subprocess.Popen([sys.executable, '-I', '-v', '-c', 447 'import sys; print(set(sys.modules))'], 448 stdout=subprocess.PIPE, 449 stderr=subprocess.PIPE, 450 encoding='utf-8') 451 stdout, stderr = popen.communicate() 452 modules = eval(stdout) 453 454 self.assertIn('site', modules) 455 456 # http://bugs.python.org/issue19205 457 re_mods = {'re', '_sre', 'sre_compile', 'sre_constants', 'sre_parse'} 458 # _osx_support uses the re module in many placs 459 if sys.platform != 'darwin': 460 self.assertFalse(modules.intersection(re_mods), stderr) 461 # http://bugs.python.org/issue9548 462 self.assertNotIn('locale', modules, stderr) 463 if sys.platform != 'darwin': 464 # http://bugs.python.org/issue19209 465 self.assertNotIn('copyreg', modules, stderr) 466 # http://bugs.python.org/issue19218> 467 collection_mods = {'_collections', 'collections', 'functools', 468 'heapq', 'itertools', 'keyword', 'operator', 469 'reprlib', 'types', 'weakref' 470 }.difference(sys.builtin_module_names) 471 # http://bugs.python.org/issue28095 472 if sys.platform != 'darwin': 473 self.assertFalse(modules.intersection(collection_mods), stderr) 474 475 def test_startup_interactivehook(self): 476 r = subprocess.Popen([sys.executable, '-c', 477 'import sys; sys.exit(hasattr(sys, "__interactivehook__"))']).wait() 478 self.assertTrue(r, "'__interactivehook__' not added by site") 479 480 def test_startup_interactivehook_isolated(self): 481 # issue28192 readline is not automatically enabled in isolated mode 482 r = subprocess.Popen([sys.executable, '-I', '-c', 483 'import sys; sys.exit(hasattr(sys, "__interactivehook__"))']).wait() 484 self.assertFalse(r, "'__interactivehook__' added in isolated mode") 485 486 def test_startup_interactivehook_isolated_explicit(self): 487 # issue28192 readline can be explicitly enabled in isolated mode 488 r = subprocess.Popen([sys.executable, '-I', '-c', 489 'import site, sys; site.enablerlcompleter(); sys.exit(hasattr(sys, "__interactivehook__"))']).wait() 490 self.assertTrue(r, "'__interactivehook__' not added by enablerlcompleter()") 491 492 @classmethod 493 def _create_underpth_exe(self, lines): 494 exe_file = os.path.join(os.getenv('TEMP'), os.path.split(sys.executable)[1]) 495 shutil.copy(sys.executable, exe_file) 496 497 _pth_file = os.path.splitext(exe_file)[0] + '._pth' 498 try: 499 with open(_pth_file, 'w') as f: 500 for line in lines: 501 print(line, file=f) 502 return exe_file 503 except: 504 os.unlink(_pth_file) 505 os.unlink(exe_file) 506 raise 507 508 @classmethod 509 def _cleanup_underpth_exe(self, exe_file): 510 _pth_file = os.path.splitext(exe_file)[0] + '._pth' 511 os.unlink(_pth_file) 512 os.unlink(exe_file) 513 514 @classmethod 515 def _calc_sys_path_for_underpth_nosite(self, sys_prefix, lines): 516 sys_path = [] 517 for line in lines: 518 if not line or line[0] == '#': 519 continue 520 abs_path = os.path.abspath(os.path.join(sys_prefix, line)) 521 sys_path.append(abs_path) 522 return sys_path 523 524 @unittest.skipUnless(sys.platform == 'win32', "only supported on Windows") 525 def test_underpth_nosite_file(self): 526 libpath = os.path.dirname(os.path.dirname(encodings.__file__)) 527 exe_prefix = os.path.dirname(sys.executable) 528 pth_lines = [ 529 'fake-path-name', 530 *[libpath for _ in range(200)], 531 '', 532 '# comment', 533 ] 534 exe_file = self._create_underpth_exe(pth_lines) 535 sys_path = self._calc_sys_path_for_underpth_nosite( 536 os.path.dirname(exe_file), 537 pth_lines) 538 539 try: 540 env = os.environ.copy() 541 env['PYTHONPATH'] = 'from-env' 542 env['PATH'] = '{};{}'.format(exe_prefix, os.getenv('PATH')) 543 rc = subprocess.call([exe_file, '-c', 544 'import sys; sys.exit(sys.flags.no_site and ' 545 'len(sys.path) > 200 and ' 546 'sys.path == %r)' % sys_path, 547 ], env=env) 548 finally: 549 self._cleanup_underpth_exe(exe_file) 550 self.assertTrue(rc, "sys.path is incorrect") 551 552 @unittest.skipUnless(sys.platform == 'win32', "only supported on Windows") 553 def test_underpth_file(self): 554 libpath = os.path.dirname(os.path.dirname(encodings.__file__)) 555 exe_prefix = os.path.dirname(sys.executable) 556 exe_file = self._create_underpth_exe([ 557 'fake-path-name', 558 *[libpath for _ in range(200)], 559 '', 560 '# comment', 561 'import site' 562 ]) 563 sys_prefix = os.path.dirname(exe_file) 564 try: 565 env = os.environ.copy() 566 env['PYTHONPATH'] = 'from-env' 567 env['PATH'] = '{};{}'.format(exe_prefix, os.getenv('PATH')) 568 rc = subprocess.call([exe_file, '-c', 569 'import sys; sys.exit(not sys.flags.no_site and ' 570 '%r in sys.path and %r in sys.path and %r not in sys.path and ' 571 'all("\\r" not in p and "\\n" not in p for p in sys.path))' % ( 572 os.path.join(sys_prefix, 'fake-path-name'), 573 libpath, 574 os.path.join(sys_prefix, 'from-env'), 575 )], env=env) 576 finally: 577 self._cleanup_underpth_exe(exe_file) 578 self.assertTrue(rc, "sys.path is incorrect") 579 580 581 if __name__ == "__main__": 582 unittest.main() 583