test_site.py 18.6 KB
Newer Older
1 2 3 4 5 6 7
"""Tests for 'site'.

Tests assume the initial paths in sys.path once the interpreter has begun
executing have not been removed.

"""
import unittest
8
import test.support
9
from test.support import captured_stderr, TESTFN, EnvironmentVarGuard
10
import builtins
11 12
import os
import sys
13
import re
14
import encodings
15 16
import urllib.request
import urllib.error
17
import subprocess
18 19 20
import sysconfig
from copy import copy

21 22 23 24 25 26 27
# These tests are not particularly useful if Python was invoked with -S.
# If you add tests that are useful under -S, this skip should be moved
# to the class level.
if sys.flags.no_site:
    raise unittest.SkipTest("Python was invoked with -S")

import site
28

29
if site.ENABLE_USER_SITE and not os.path.isdir(site.USER_SITE):
30 31 32 33
    # need to add user site directory for tests
    os.makedirs(site.USER_SITE)
    site.addsitedir(site.USER_SITE)

34 35 36 37 38 39 40
class HelperFunctionsTests(unittest.TestCase):
    """Tests for helper functions.
    """

    def setUp(self):
        """Save a copy of sys.path"""
        self.sys_path = sys.path[:]
41 42 43
        self.old_base = site.USER_BASE
        self.old_site = site.USER_SITE
        self.old_prefixes = site.PREFIXES
44
        self.original_vars = sysconfig._CONFIG_VARS
45
        self.old_vars = copy(sysconfig._CONFIG_VARS)
46

47
    def tearDown(self):
48
        """Restore sys.path"""
49
        sys.path[:] = self.sys_path
50 51 52
        site.USER_BASE = self.old_base
        site.USER_SITE = self.old_site
        site.PREFIXES = self.old_prefixes
53 54 55
        sysconfig._CONFIG_VARS = self.original_vars
        sysconfig._CONFIG_VARS.clear()
        sysconfig._CONFIG_VARS.update(self.old_vars)
56

57 58 59 60 61 62 63
    def test_makepath(self):
        # Test makepath() have an absolute path for its first return value
        # and a case-normalized version of the absolute path for its
        # second value.
        path_parts = ("Beginning", "End")
        original_dir = os.path.join(*path_parts)
        abs_dir, norm_dir = site.makepath(*path_parts)
64
        self.assertEqual(os.path.abspath(original_dir), abs_dir)
65
        if original_dir == os.path.normcase(original_dir):
66
            self.assertEqual(abs_dir, norm_dir)
67
        else:
68
            self.assertEqual(os.path.normcase(abs_dir), norm_dir)
69 70 71 72 73

    def test_init_pathinfo(self):
        dir_set = site._init_pathinfo()
        for entry in [site.makepath(path)[1] for path in sys.path
                        if path and os.path.isdir(path)]:
74 75 76
            self.assertIn(entry, dir_set,
                          "%s from sys.path not found in set returned "
                          "by _init_pathinfo(): %s" % (entry, dir_set))
77

78 79
    def pth_file_tests(self, pth_file):
        """Contain common code for testing results of reading a .pth file"""
80 81
        self.assertIn(pth_file.imported, sys.modules,
                      "%s not in sys.modules" % pth_file.imported)
82 83
        self.assertIn(site.makepath(pth_file.good_dir_path)[0], sys.path)
        self.assertFalse(os.path.exists(pth_file.bad_dir_path))
84

85 86
    def test_addpackage(self):
        # Make sure addpackage() imports if the line starts with 'import',
87 88 89 90
        # adds directories to sys.path for any line in the file that is not a
        # comment or import that is a valid directory name for where the .pth
        # file resides; invalid directories are not added
        pth_file = PthFile()
91 92
        pth_file.cleanup(prep=True)  # to make sure that nothing is
                                      # pre-existing that shouldn't be
93
        try:
94 95
            pth_file.create()
            site.addpackage(pth_file.base_dir, pth_file.filename, set())
96
            self.pth_file_tests(pth_file)
97
        finally:
98
            pth_file.cleanup()
99

100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
    def make_pth(self, contents, pth_dir='.', pth_name=TESTFN):
        # Create a .pth file and return its (abspath, basename).
        pth_dir = os.path.abspath(pth_dir)
        pth_basename = pth_name + '.pth'
        pth_fn = os.path.join(pth_dir, pth_basename)
        pth_file = open(pth_fn, 'w', encoding='utf-8')
        self.addCleanup(lambda: os.remove(pth_fn))
        pth_file.write(contents)
        pth_file.close()
        return pth_dir, pth_basename

    def test_addpackage_import_bad_syntax(self):
        # Issue 10642
        pth_dir, pth_fn = self.make_pth("import bad)syntax\n")
        with captured_stderr() as err_out:
            site.addpackage(pth_dir, pth_fn, set())
        self.assertRegex(err_out.getvalue(), "line 1")
117 118
        self.assertRegex(err_out.getvalue(),
            re.escape(os.path.join(pth_dir, pth_fn)))
119 120 121 122 123 124 125 126 127 128 129 130 131
        # XXX: the previous two should be independent checks so that the
        # order doesn't matter.  The next three could be a single check
        # but my regex foo isn't good enough to write it.
        self.assertRegex(err_out.getvalue(), 'Traceback')
        self.assertRegex(err_out.getvalue(), r'import bad\)syntax')
        self.assertRegex(err_out.getvalue(), 'SyntaxError')

    def test_addpackage_import_bad_exec(self):
        # Issue 10642
        pth_dir, pth_fn = self.make_pth("randompath\nimport nosuchmodule\n")
        with captured_stderr() as err_out:
            site.addpackage(pth_dir, pth_fn, set())
        self.assertRegex(err_out.getvalue(), "line 2")
132 133
        self.assertRegex(err_out.getvalue(),
            re.escape(os.path.join(pth_dir, pth_fn)))
134 135
        # XXX: ditto previous XXX comment.
        self.assertRegex(err_out.getvalue(), 'Traceback')
136
        self.assertRegex(err_out.getvalue(), 'ImportError')
137

138 139
    @unittest.skipIf(sys.platform == "win32", "Windows does not raise an "
                      "error for file paths containing null characters")
140 141 142 143 144 145
    def test_addpackage_import_bad_pth_file(self):
        # Issue 5258
        pth_dir, pth_fn = self.make_pth("abc\x00def\n")
        with captured_stderr() as err_out:
            site.addpackage(pth_dir, pth_fn, set())
        self.assertRegex(err_out.getvalue(), "line 1")
146 147
        self.assertRegex(err_out.getvalue(),
            re.escape(os.path.join(pth_dir, pth_fn)))
148 149 150 151
        # XXX: ditto previous XXX comment.
        self.assertRegex(err_out.getvalue(), 'Traceback')
        self.assertRegex(err_out.getvalue(), 'TypeError')

152
    def test_addsitedir(self):
153 154 155
        # Same tests for test_addpackage since addsitedir() essentially just
        # calls addpackage() for every .pth file in the directory
        pth_file = PthFile()
156 157
        pth_file.cleanup(prep=True) # Make sure that nothing is pre-existing
                                    # that is tested for
158
        try:
159
            pth_file.create()
160
            site.addsitedir(pth_file.base_dir, set())
161
            self.pth_file_tests(pth_file)
162
        finally:
163 164
            pth_file.cleanup()

165 166
    @unittest.skipUnless(site.ENABLE_USER_SITE, "requires access to PEP 370 "
                          "user-site (site.ENABLE_USER_SITE)")
167 168
    def test_s_option(self):
        usersite = site.USER_SITE
169
        self.assertIn(usersite, sys.path)
170

171
        env = os.environ.copy()
172
        rc = subprocess.call([sys.executable, '-c',
173 174
            'import sys; sys.exit(%r in sys.path)' % usersite],
            env=env)
175 176
        self.assertEqual(rc, 1)

177
        env = os.environ.copy()
178
        rc = subprocess.call([sys.executable, '-s', '-c',
179 180
            'import sys; sys.exit(%r in sys.path)' % usersite],
            env=env)
181 182 183 184
        if usersite == site.getsitepackages()[0]:
            self.assertEqual(rc, 1)
        else:
            self.assertEqual(rc, 0)
185 186 187 188

        env = os.environ.copy()
        env["PYTHONNOUSERSITE"] = "1"
        rc = subprocess.call([sys.executable, '-c',
Benjamin Peterson's avatar
Benjamin Peterson committed
189
            'import sys; sys.exit(%r in sys.path)' % usersite],
190
            env=env)
191 192 193 194
        if usersite == site.getsitepackages()[0]:
            self.assertEqual(rc, 1)
        else:
            self.assertEqual(rc, 0)
195 196 197 198 199 200 201 202

        env = os.environ.copy()
        env["PYTHONUSERBASE"] = "/tmp"
        rc = subprocess.call([sys.executable, '-c',
            'import sys, site; sys.exit(site.USER_BASE.startswith("/tmp"))'],
            env=env)
        self.assertEqual(rc, 1)

203 204 205 206 207
    def test_getuserbase(self):
        site.USER_BASE = None
        user_base = site.getuserbase()

        # the call sets site.USER_BASE
208
        self.assertEqual(site.USER_BASE, user_base)
209 210 211

        # let's set PYTHONUSERBASE and see if it uses it
        site.USER_BASE = None
212 213 214
        import sysconfig
        sysconfig._CONFIG_VARS = None

215 216
        with EnvironmentVarGuard() as environ:
            environ['PYTHONUSERBASE'] = 'xoxo'
217 218
            self.assertTrue(site.getuserbase().startswith('xoxo'),
                            site.getuserbase())
219 220 221 222 223 224 225

    def test_getusersitepackages(self):
        site.USER_SITE = None
        site.USER_BASE = None
        user_site = site.getusersitepackages()

        # the call sets USER_BASE *and* USER_SITE
226
        self.assertEqual(site.USER_SITE, user_site)
227
        self.assertTrue(user_site.startswith(site.USER_BASE), user_site)
228 229 230 231 232

    def test_getsitepackages(self):
        site.PREFIXES = ['xoxo']
        dirs = site.getsitepackages()

233
        if (sys.platform == "darwin" and
234 235 236 237 238 239 240 241 242 243
            sysconfig.get_config_var("PYTHONFRAMEWORK")):
            # OS X framework builds
            site.PREFIXES = ['Python.framework']
            dirs = site.getsitepackages()
            self.assertEqual(len(dirs), 3)
            wanted = os.path.join('/Library',
                                  sysconfig.get_config_var("PYTHONFRAMEWORK"),
                                  sys.version[:3],
                                  'site-packages')
            self.assertEqual(dirs[2], wanted)
244
        elif os.sep == '/':
245
            # OS X non-framwework builds, Linux, FreeBSD, etc
246
            self.assertEqual(len(dirs), 2)
247 248
            wanted = os.path.join('xoxo', 'lib', 'python' + sys.version[:3],
                                  'site-packages')
249
            self.assertEqual(dirs[0], wanted)
250
            wanted = os.path.join('xoxo', 'lib', 'site-python')
251
            self.assertEqual(dirs[1], wanted)
252
        else:
253
            # other platforms
254
            self.assertEqual(len(dirs), 2)
255
            self.assertEqual(dirs[0], 'xoxo')
256
            wanted = os.path.join('xoxo', 'lib', 'site-packages')
257
            self.assertEqual(dirs[1], wanted)
258

259 260 261 262 263 264 265 266 267
class PthFile(object):
    """Helper class for handling testing of .pth files"""

    def __init__(self, filename_base=TESTFN, imported="time",
                    good_dirname="__testdir__", bad_dirname="__bad"):
        """Initialize instance variables"""
        self.filename = filename_base + ".pth"
        self.base_dir = os.path.abspath('')
        self.file_path = os.path.join(self.base_dir, self.filename)
268
        self.imported = imported
269 270 271 272 273 274 275 276 277
        self.good_dirname = good_dirname
        self.bad_dirname = bad_dirname
        self.good_dir_path = os.path.join(self.base_dir, self.good_dirname)
        self.bad_dir_path = os.path.join(self.base_dir, self.bad_dirname)

    def create(self):
        """Create a .pth file with a comment, blank lines, an ``import
        <self.imported>``, a line with self.good_dirname, and a line with
        self.bad_dirname.
278

279 280 281 282
        Creation of the directory for self.good_dir_path (based off of
        self.good_dirname) is also performed.

        Make sure to call self.cleanup() to undo anything done by this method.
283

284
        """
285
        FILE = open(self.file_path, 'w')
286
        try:
287 288 289 290 291
            print("#import @bad module name", file=FILE)
            print("\n", file=FILE)
            print("import %s" % self.imported, file=FILE)
            print(self.good_dirname, file=FILE)
            print(self.bad_dirname, file=FILE)
292 293 294
        finally:
            FILE.close()
        os.mkdir(self.good_dir_path)
295

296
    def cleanup(self, prep=False):
297 298 299
        """Make sure that the .pth file is deleted, self.imported is not in
        sys.modules, and that both self.good_dirname and self.bad_dirname are
        not existing directories."""
300
        if os.path.exists(self.file_path):
301
            os.remove(self.file_path)
302 303 304 305 306 307 308 309
        if prep:
            self.imported_module = sys.modules.get(self.imported)
            if self.imported_module:
                del sys.modules[self.imported]
        else:
            if self.imported_module:
                sys.modules[self.imported] = self.imported_module
        if os.path.exists(self.good_dir_path):
310
            os.rmdir(self.good_dir_path)
311
        if os.path.exists(self.bad_dir_path):
312
            os.rmdir(self.bad_dir_path)
313 314 315 316 317 318 319 320 321 322

class ImportSideEffectTests(unittest.TestCase):
    """Test side-effects from importing 'site'."""

    def setUp(self):
        """Make a copy of sys.path"""
        self.sys_path = sys.path[:]

    def tearDown(self):
        """Restore sys.path"""
323
        sys.path[:] = self.sys_path
324

Barry Warsaw's avatar
Barry Warsaw committed
325 326 327 328 329 330 331 332 333 334
    def test_abs_paths(self):
        # Make sure all imported modules have their __file__ and __cached__
        # attributes as absolute paths.  Arranging to put the Lib directory on
        # PYTHONPATH would cause the os module to have a relative path for
        # __file__ if abs_paths() does not get run.  sys and builtins (the
        # only other modules imported before site.py runs) do not have
        # __file__ or __cached__ because they are built-in.
        parent = os.path.relpath(os.path.dirname(os.__file__))
        env = os.environ.copy()
        env['PYTHONPATH'] = parent
335 336 337 338 339 340 341
        code = ('import os, sys',
            # use ASCII to avoid locale issues with non-ASCII directories
            'os_file = os.__file__.encode("ascii", "backslashreplace")',
            r'sys.stdout.buffer.write(os_file + b"\n")',
            'os_cached = os.__cached__.encode("ascii", "backslashreplace")',
            r'sys.stdout.buffer.write(os_cached + b"\n")')
        command = '\n'.join(code)
Barry Warsaw's avatar
Barry Warsaw committed
342 343 344 345
        # First, prove that with -S (no 'import site'), the paths are
        # relative.
        proc = subprocess.Popen([sys.executable, '-S', '-c', command],
                                env=env,
346
                                stdout=subprocess.PIPE)
Barry Warsaw's avatar
Barry Warsaw committed
347
        stdout, stderr = proc.communicate()
348

Barry Warsaw's avatar
Barry Warsaw committed
349
        self.assertEqual(proc.returncode, 0)
350
        os__file__, os__cached__ = stdout.splitlines()[:2]
Barry Warsaw's avatar
Barry Warsaw committed
351 352 353 354 355
        self.assertFalse(os.path.isabs(os__file__))
        self.assertFalse(os.path.isabs(os__cached__))
        # Now, with 'import site', it works.
        proc = subprocess.Popen([sys.executable, '-c', command],
                                env=env,
356
                                stdout=subprocess.PIPE)
Barry Warsaw's avatar
Barry Warsaw committed
357 358
        stdout, stderr = proc.communicate()
        self.assertEqual(proc.returncode, 0)
359
        os__file__, os__cached__ = stdout.splitlines()[:2]
Barry Warsaw's avatar
Barry Warsaw committed
360 361
        self.assertTrue(os.path.isabs(os__file__))
        self.assertTrue(os.path.isabs(os__cached__))
362 363 364 365 366 367 368

    def test_no_duplicate_paths(self):
        # No duplicate paths should exist in sys.path
        # Handled by removeduppaths()
        site.removeduppaths()
        seen_paths = set()
        for path in sys.path:
369
            self.assertNotIn(path, seen_paths)
370 371
            seen_paths.add(path)

372
    @unittest.skip('test not implemented')
373 374 375 376 377 378 379
    def test_add_build_dir(self):
        # Test that the build directory's Modules directory is used when it
        # should be.
        # XXX: implement
        pass

    def test_setting_quit(self):
380
        # 'quit' and 'exit' should be injected into builtins
381 382
        self.assertTrue(hasattr(builtins, "quit"))
        self.assertTrue(hasattr(builtins, "exit"))
383 384

    def test_setting_copyright(self):
385
        # 'copyright', 'credits', and 'license' should be in builtins
386 387
        self.assertTrue(hasattr(builtins, "copyright"))
        self.assertTrue(hasattr(builtins, "credits"))
388
        self.assertTrue(hasattr(builtins, "license"))
389 390

    def test_setting_help(self):
391
        # 'help' should be set in builtins
392
        self.assertTrue(hasattr(builtins, "help"))
393 394 395 396 397

    def test_aliasing_mbcs(self):
        if sys.platform == "win32":
            import locale
            if locale.getdefaultlocale()[1].startswith('cp'):
398
                for value in encodings.aliases.aliases.values():
399 400 401 402 403 404 405
                    if value == "mbcs":
                        break
                else:
                    self.fail("did not alias mbcs")

    def test_sitecustomize_executed(self):
        # If sitecustomize is available, it should have been imported.
406
        if "sitecustomize" not in sys.modules:
407 408 409 410 411 412 413
            try:
                import sitecustomize
            except ImportError:
                pass
            else:
                self.fail("sitecustomize not imported automatically")

414
    @test.support.requires_resource('network')
415
    @test.support.system_must_validate_cert
416 417
    @unittest.skipUnless(sys.version_info[3] == 'final',
                         'only for released versions')
418 419
    @unittest.skipUnless(hasattr(urllib.request, "HTTPSHandler"),
                         'need SSL support to download license')
420
    def test_license_exists_at_url(self):
421
        # This test is a bit fragile since it depends on the format of the
422 423 424 425
        # string displayed by license in the absence of a LICENSE file.
        url = license._Printer__data.split()[1]
        req = urllib.request.Request(url, method='HEAD')
        try:
426
            with test.support.transient_internet(url):
427 428 429 430 431
                with urllib.request.urlopen(req) as data:
                    code = data.getcode()
        except urllib.error.HTTPError as e:
            code = e.code
        self.assertEqual(code, 200, msg="Can't find " + url)
432

433

434 435 436 437 438
class StartupImportTests(unittest.TestCase):

    def test_startup_imports(self):
        # This tests checks which modules are loaded by Python when it
        # initially starts upon startup.
439 440 441 442 443 444 445 446 447
        popen = subprocess.Popen([sys.executable, '-I', '-v', '-c',
                                  'import sys; print(set(sys.modules))'],
                                 stdout=subprocess.PIPE,
                                 stderr=subprocess.PIPE)
        stdout, stderr = popen.communicate()
        stdout = stdout.decode('utf-8')
        stderr = stderr.decode('utf-8')
        modules = eval(stdout)

448 449
        self.assertIn('site', modules)

450
        # http://bugs.python.org/issue19205
451
        re_mods = {'re', '_sre', 'sre_compile', 'sre_constants', 'sre_parse'}
452 453 454
        # _osx_support uses the re module in many placs
        if sys.platform != 'darwin':
            self.assertFalse(modules.intersection(re_mods), stderr)
455
        # http://bugs.python.org/issue9548
456
        self.assertNotIn('locale', modules, stderr)
457 458 459
        if sys.platform != 'darwin':
            # http://bugs.python.org/issue19209
            self.assertNotIn('copyreg', modules, stderr)
460 461 462
        # http://bugs.python.org/issue19218>
        collection_mods = {'_collections', 'collections', 'functools',
                           'heapq', 'itertools', 'keyword', 'operator',
463 464
                           'reprlib', 'types', 'weakref'
                          }.difference(sys.builtin_module_names)
465
        self.assertFalse(modules.intersection(collection_mods), stderr)
466

467

468
if __name__ == "__main__":
469
    unittest.main()