test_site.py 18.5 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
            sysconfig.get_config_var("PYTHONFRAMEWORK")):
            # OS X framework builds
            site.PREFIXES = ['Python.framework']
            dirs = site.getsitepackages()
238
            self.assertEqual(len(dirs), 2)
239 240 241 242
            wanted = os.path.join('/Library',
                                  sysconfig.get_config_var("PYTHONFRAMEWORK"),
                                  sys.version[:3],
                                  'site-packages')
243
            self.assertEqual(dirs[1], wanted)
244
        elif os.sep == '/':
245
            # OS X non-framwework builds, Linux, FreeBSD, etc
246
            self.assertEqual(len(dirs), 1)
247 248
            wanted = os.path.join('xoxo', 'lib', 'python' + sys.version[:3],
                                  'site-packages')
249
            self.assertEqual(dirs[0], wanted)
250
        else:
251
            # other platforms
252
            self.assertEqual(len(dirs), 2)
253
            self.assertEqual(dirs[0], 'xoxo')
254
            wanted = os.path.join('xoxo', 'lib', 'site-packages')
255
            self.assertEqual(dirs[1], wanted)
256

257 258 259 260 261 262 263 264 265
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)
266
        self.imported = imported
267 268 269 270 271 272 273 274 275
        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.
276

277 278 279 280
        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.
281

282
        """
283
        FILE = open(self.file_path, 'w')
284
        try:
285 286 287 288 289
            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)
290 291 292
        finally:
            FILE.close()
        os.mkdir(self.good_dir_path)
293

294
    def cleanup(self, prep=False):
295 296 297
        """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."""
298
        if os.path.exists(self.file_path):
299
            os.remove(self.file_path)
300 301 302 303 304 305 306 307
        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):
308
            os.rmdir(self.good_dir_path)
309
        if os.path.exists(self.bad_dir_path):
310
            os.rmdir(self.bad_dir_path)
311 312 313 314 315 316 317 318 319 320

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"""
321
        sys.path[:] = self.sys_path
322

Barry Warsaw's avatar
Barry Warsaw committed
323 324 325 326 327 328 329 330 331 332
    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
333 334 335 336 337 338 339
        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
340 341 342 343
        # First, prove that with -S (no 'import site'), the paths are
        # relative.
        proc = subprocess.Popen([sys.executable, '-S', '-c', command],
                                env=env,
344
                                stdout=subprocess.PIPE)
Barry Warsaw's avatar
Barry Warsaw committed
345
        stdout, stderr = proc.communicate()
346

Barry Warsaw's avatar
Barry Warsaw committed
347
        self.assertEqual(proc.returncode, 0)
348
        os__file__, os__cached__ = stdout.splitlines()[:2]
Barry Warsaw's avatar
Barry Warsaw committed
349 350 351 352 353
        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,
354
                                stdout=subprocess.PIPE)
Barry Warsaw's avatar
Barry Warsaw committed
355 356
        stdout, stderr = proc.communicate()
        self.assertEqual(proc.returncode, 0)
357
        os__file__, os__cached__ = stdout.splitlines()[:2]
Barry Warsaw's avatar
Barry Warsaw committed
358 359
        self.assertTrue(os.path.isabs(os__file__))
        self.assertTrue(os.path.isabs(os__cached__))
360 361 362 363 364 365 366

    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:
367
            self.assertNotIn(path, seen_paths)
368 369
            seen_paths.add(path)

370
    @unittest.skip('test not implemented')
371 372 373 374 375 376 377
    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):
378
        # 'quit' and 'exit' should be injected into builtins
379 380
        self.assertTrue(hasattr(builtins, "quit"))
        self.assertTrue(hasattr(builtins, "exit"))
381 382

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

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

    def test_aliasing_mbcs(self):
        if sys.platform == "win32":
            import locale
            if locale.getdefaultlocale()[1].startswith('cp'):
396
                for value in encodings.aliases.aliases.values():
397 398 399 400 401 402 403
                    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.
404
        if "sitecustomize" not in sys.modules:
405 406 407 408 409 410 411
            try:
                import sitecustomize
            except ImportError:
                pass
            else:
                self.fail("sitecustomize not imported automatically")

412
    @test.support.requires_resource('network')
413 414
    @unittest.skipUnless(sys.version_info[3] == 'final',
                         'only for released versions')
415 416
    @unittest.skipUnless(hasattr(urllib.request, "HTTPSHandler"),
                         'need SSL support to download license')
417
    def test_license_exists_at_url(self):
418
        # This test is a bit fragile since it depends on the format of the
419 420 421 422
        # 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:
423
            with test.support.transient_internet(url):
424 425 426 427 428
                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)
429

430

431 432 433 434 435
class StartupImportTests(unittest.TestCase):

    def test_startup_imports(self):
        # This tests checks which modules are loaded by Python when it
        # initially starts upon startup.
436 437 438 439 440 441 442 443 444
        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)

445 446
        self.assertIn('site', modules)

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

464

465
if __name__ == "__main__":
466
    unittest.main()