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 from test.test_support import run_unittest, TESTFN, EnvironmentVarGuard
9 from test.test_support import captured_output
10 import __builtin__
11 import errno
12 import os
13 import sys
14 import re
15 import encodings
16 import subprocess
17 import sysconfig
18 from copy import copy
19 
20 # Need to make sure to not import 'site' if someone specified ``-S`` at the
21 # command-line.  Detect this by just making sure 'site' has not been imported
22 # already.
23 if "site" in sys.modules:
24     import site
25 else:
26     raise unittest.SkipTest("importation of site.py suppressed")
27 
28 
29 OLD_SYS_PATH = None
30 
31 
32 def setUpModule():
33     global OLD_SYS_PATH
34     OLD_SYS_PATH = sys.path[:]
35 
36     if site.ENABLE_USER_SITE and not os.path.isdir(site.USER_SITE):
37         # need to add user site directory for tests
38         try:
39             os.makedirs(site.USER_SITE)
40             # modify sys.path: will be restored by tearDownModule()
41             site.addsitedir(site.USER_SITE)
42         except OSError as exc:
43             if exc.errno in (errno.EACCES, errno.EPERM):
44                 raise unittest.SkipTest('unable to create user site directory (%r): %s'
45                                         % (site.USER_SITE, exc))
46             else:
47                 raise
48 
49 
50 def tearDownModule():
51     sys.path[:] = OLD_SYS_PATH
52 
53 
54 class HelperFunctionsTests(unittest.TestCase):
55     """Tests for helper functions.
56 
57     The setting of the encoding (set using sys.setdefaultencoding) used by
58     the Unicode implementation is not tested.
59 
60     """
61 
62     def setUp(self):
63         """Save a copy of sys.path"""
64         self.sys_path = sys.path[:]
65         self.old_base = site.USER_BASE
66         self.old_site = site.USER_SITE
67         self.old_prefixes = site.PREFIXES
68         self.old_vars = copy(sysconfig._CONFIG_VARS)
69 
70     def tearDown(self):
71         """Restore sys.path"""
72         sys.path[:] = self.sys_path
73         site.USER_BASE = self.old_base
74         site.USER_SITE = self.old_site
75         site.PREFIXES = self.old_prefixes
76         sysconfig._CONFIG_VARS = self.old_vars
77 
78     def test_makepath(self):
79         # Test makepath() have an absolute path for its first return value
80         # and a case-normalized version of the absolute path for its
81         # second value.
82         path_parts = ("Beginning", "End")
83         original_dir = os.path.join(*path_parts)
84         abs_dir, norm_dir = site.makepath(*path_parts)
85         self.assertEqual(os.path.abspath(original_dir), abs_dir)
86         if original_dir == os.path.normcase(original_dir):
87             self.assertEqual(abs_dir, norm_dir)
88         else:
89             self.assertEqual(os.path.normcase(abs_dir), norm_dir)
90 
91     def test_init_pathinfo(self):
92         dir_set = site._init_pathinfo()
93         for entry in [site.makepath(path)[1] for path in sys.path
94                         if path and os.path.isdir(path)]:
95             self.assertIn(entry, dir_set,
96                           "%s from sys.path not found in set returned "
97                           "by _init_pathinfo(): %s" % (entry, dir_set))
98 
99     def pth_file_tests(self, pth_file):
100         """Contain common code for testing results of reading a .pth file"""
101         self.assertIn(pth_file.imported, sys.modules,
102                       "%s not in sys.modules" % pth_file.imported)
103         self.assertIn(site.makepath(pth_file.good_dir_path)[0], sys.path)
104         self.assertFalse(os.path.exists(pth_file.bad_dir_path))
105 
106     def test_addpackage(self):
107         # Make sure addpackage() imports if the line starts with 'import',
108         # adds directories to sys.path for any line in the file that is not a
109         # comment or import that is a valid directory name for where the .pth
110         # file resides; invalid directories are not added
111         pth_file = PthFile()
112         pth_file.cleanup(prep=True)  # to make sure that nothing is
113                                       # pre-existing that shouldn't be
114         try:
115             pth_file.create()
116             site.addpackage(pth_file.base_dir, pth_file.filename, set())
117             self.pth_file_tests(pth_file)
118         finally:
119             pth_file.cleanup()
120 
121     def make_pth(self, contents, pth_dir='.', pth_name=TESTFN):
122         # Create a .pth file and return its (abspath, basename).
123         pth_dir = os.path.abspath(pth_dir)
124         pth_basename = pth_name + '.pth'
125         pth_fn = os.path.join(pth_dir, pth_basename)
126         pth_file = open(pth_fn, 'w')
127         self.addCleanup(lambda: os.remove(pth_fn))
128         pth_file.write(contents)
129         pth_file.close()
130         return pth_dir, pth_basename
131 
132     def test_addpackage_import_bad_syntax(self):
133         # Issue 10642
134         pth_dir, pth_fn = self.make_pth("import bad)syntax\n")
135         with captured_output("stderr") as err_out:
136             site.addpackage(pth_dir, pth_fn, set())
137         self.assertRegexpMatches(err_out.getvalue(), "line 1")
138         self.assertRegexpMatches(err_out.getvalue(),
139             re.escape(os.path.join(pth_dir, pth_fn)))
140         # XXX: the previous two should be independent checks so that the
141         # order doesn't matter.  The next three could be a single check
142         # but my regex foo isn't good enough to write it.
143         self.assertRegexpMatches(err_out.getvalue(), 'Traceback')
144         self.assertRegexpMatches(err_out.getvalue(), r'import bad\)syntax')
145         self.assertRegexpMatches(err_out.getvalue(), 'SyntaxError')
146 
147     def test_addpackage_import_bad_exec(self):
148         # Issue 10642
149         pth_dir, pth_fn = self.make_pth("randompath\nimport nosuchmodule\n")
150         with captured_output("stderr") as err_out:
151             site.addpackage(pth_dir, pth_fn, set())
152         self.assertRegexpMatches(err_out.getvalue(), "line 2")
153         self.assertRegexpMatches(err_out.getvalue(),
154             re.escape(os.path.join(pth_dir, pth_fn)))
155         # XXX: ditto previous XXX comment.
156         self.assertRegexpMatches(err_out.getvalue(), 'Traceback')
157         self.assertRegexpMatches(err_out.getvalue(), 'ImportError')
158 
159     @unittest.skipIf(sys.platform == "win32", "Windows does not raise an "
160                       "error for file paths containing null characters")
161     def test_addpackage_import_bad_pth_file(self):
162         # Issue 5258
163         pth_dir, pth_fn = self.make_pth("abc\x00def\n")
164         with captured_output("stderr") as err_out:
165             site.addpackage(pth_dir, pth_fn, set())
166         self.assertRegexpMatches(err_out.getvalue(), "line 1")
167         self.assertRegexpMatches(err_out.getvalue(),
168             re.escape(os.path.join(pth_dir, pth_fn)))
169         # XXX: ditto previous XXX comment.
170         self.assertRegexpMatches(err_out.getvalue(), 'Traceback')
171         self.assertRegexpMatches(err_out.getvalue(), 'TypeError')
172 
173     def test_addsitedir(self):
174         # Same tests for test_addpackage since addsitedir() essentially just
175         # calls addpackage() for every .pth file in the directory
176         pth_file = PthFile()
177         pth_file.cleanup(prep=True) # Make sure that nothing is pre-existing
178                                     # that is tested for
179         try:
180             pth_file.create()
181             site.addsitedir(pth_file.base_dir, set())
182             self.pth_file_tests(pth_file)
183         finally:
184             pth_file.cleanup()
185 
186     @unittest.skipUnless(site.ENABLE_USER_SITE, "requires access to PEP 370 "
187                           "user-site (site.ENABLE_USER_SITE)")
188     def test_s_option(self):
189         usersite = site.USER_SITE
190         self.assertIn(usersite, sys.path)
191 
192         env = os.environ.copy()
193         rc = subprocess.call([sys.executable, '-c',
194             'import sys; sys.exit(%r in sys.path)' % usersite],
195             env=env)
196         self.assertEqual(rc, 1, "%r is not in sys.path (sys.exit returned %r)"
197                 % (usersite, rc))
198 
199         env = os.environ.copy()
200         rc = subprocess.call([sys.executable, '-s', '-c',
201             'import sys; sys.exit(%r in sys.path)' % usersite],
202             env=env)
203         self.assertEqual(rc, 0)
204 
205         env = os.environ.copy()
206         env["PYTHONNOUSERSITE"] = "1"
207         rc = subprocess.call([sys.executable, '-c',
208             'import sys; sys.exit(%r in sys.path)' % usersite],
209             env=env)
210         self.assertEqual(rc, 0)
211 
212         env = os.environ.copy()
213         env["PYTHONUSERBASE"] = "/tmp"
214         rc = subprocess.call([sys.executable, '-c',
215             'import sys, site; sys.exit(site.USER_BASE.startswith("/tmp"))'],
216             env=env)
217         self.assertEqual(rc, 1)
218 
219     def test_getuserbase(self):
220         site.USER_BASE = None
221         user_base = site.getuserbase()
222 
223         # the call sets site.USER_BASE
224         self.assertEqual(site.USER_BASE, user_base)
225 
226         # let's set PYTHONUSERBASE and see if it uses it
227         site.USER_BASE = None
228         import sysconfig
229         sysconfig._CONFIG_VARS = None
230 
231         with EnvironmentVarGuard() as environ:
232             environ['PYTHONUSERBASE'] = 'xoxo'
233             self.assertTrue(site.getuserbase().startswith('xoxo'),
234                             site.getuserbase())
235 
236     def test_getusersitepackages(self):
237         site.USER_SITE = None
238         site.USER_BASE = None
239         user_site = site.getusersitepackages()
240 
241         # the call sets USER_BASE *and* USER_SITE
242         self.assertEqual(site.USER_SITE, user_site)
243         self.assertTrue(user_site.startswith(site.USER_BASE), user_site)
244 
245     def test_getsitepackages(self):
246         site.PREFIXES = ['xoxo']
247         dirs = site.getsitepackages()
248 
249         if sys.platform in ('os2emx', 'riscos'):
250             self.assertEqual(len(dirs), 1)
251             wanted = os.path.join('xoxo', 'Lib', 'site-packages')
252             self.assertEqual(dirs[0], wanted)
253         elif os.sep == '/':
254             # OS X, Linux, FreeBSD, etc
255             self.assertEqual(len(dirs), 2)
256             wanted = os.path.join('xoxo', 'lib', 'python' + sys.version[:3],
257                                   'site-packages')
258             self.assertEqual(dirs[0], wanted)
259             wanted = os.path.join('xoxo', 'lib', 'site-python')
260             self.assertEqual(dirs[1], wanted)
261         else:
262             # other platforms
263             self.assertEqual(len(dirs), 2)
264             self.assertEqual(dirs[0], 'xoxo')
265             wanted = os.path.join('xoxo', 'lib', 'site-packages')
266             self.assertEqual(dirs[1], wanted)
267 
268 class PthFile(object):
269     """Helper class for handling testing of .pth files"""
270 
271     def __init__(self, filename_base=TESTFN, imported="time",
272                     good_dirname="__testdir__", bad_dirname="__bad"):
273         """Initialize instance variables"""
274         self.filename = filename_base + ".pth"
275         self.base_dir = os.path.abspath('')
276         self.file_path = os.path.join(self.base_dir, self.filename)
277         self.imported = imported
278         self.good_dirname = good_dirname
279         self.bad_dirname = bad_dirname
280         self.good_dir_path = os.path.join(self.base_dir, self.good_dirname)
281         self.bad_dir_path = os.path.join(self.base_dir, self.bad_dirname)
282 
283     def create(self):
284         """Create a .pth file with a comment, blank lines, an ``import
285         <self.imported>``, a line with self.good_dirname, and a line with
286         self.bad_dirname.
287 
288         Creation of the directory for self.good_dir_path (based off of
289         self.good_dirname) is also performed.
290 
291         Make sure to call self.cleanup() to undo anything done by this method.
292 
293         """
294         FILE = open(self.file_path, 'w')
295         try:
296             print>>FILE, "#import @bad module name"
297             print>>FILE, "\n"
298             print>>FILE, "import %s" % self.imported
299             print>>FILE, self.good_dirname
300             print>>FILE, self.bad_dirname
301         finally:
302             FILE.close()
303         os.mkdir(self.good_dir_path)
304 
305     def cleanup(self, prep=False):
306         """Make sure that the .pth file is deleted, self.imported is not in
307         sys.modules, and that both self.good_dirname and self.bad_dirname are
308         not existing directories."""
309         if os.path.exists(self.file_path):
310             os.remove(self.file_path)
311         if prep:
312             self.imported_module = sys.modules.get(self.imported)
313             if self.imported_module:
314                 del sys.modules[self.imported]
315         else:
316             if self.imported_module:
317                 sys.modules[self.imported] = self.imported_module
318         if os.path.exists(self.good_dir_path):
319             os.rmdir(self.good_dir_path)
320         if os.path.exists(self.bad_dir_path):
321             os.rmdir(self.bad_dir_path)
322 
323 class ImportSideEffectTests(unittest.TestCase):
324     """Test side-effects from importing 'site'."""
325 
326     def setUp(self):
327         """Make a copy of sys.path"""
328         self.sys_path = sys.path[:]
329 
330     def tearDown(self):
331         """Restore sys.path"""
332         sys.path[:] = self.sys_path
333 
334     def test_abs__file__(self):
335         # Make sure all imported modules have their __file__ attribute
336         # as an absolute path.
337         # Handled by abs__file__()
338         site.abs__file__()
339         for module in (sys, os, __builtin__):
340             try:
341                 self.assertTrue(os.path.isabs(module.__file__), repr(module))
342             except AttributeError:
343                 continue
344         # We could try everything in sys.modules; however, when regrtest.py
345         # runs something like test_frozen before test_site, then we will
346         # be testing things loaded *after* test_site did path normalization
347 
348     def test_no_duplicate_paths(self):
349         # No duplicate paths should exist in sys.path
350         # Handled by removeduppaths()
351         site.removeduppaths()
352         seen_paths = set()
353         for path in sys.path:
354             self.assertNotIn(path, seen_paths)
355             seen_paths.add(path)
356 
357     @unittest.skip('test not implemented')
358     def test_add_build_dir(self):
359         # Test that the build directory's Modules directory is used when it
360         # should be.
361         # XXX: implement
362         pass
363 
364     def test_setting_quit(self):
365         # 'quit' and 'exit' should be injected into __builtin__
366         self.assertTrue(hasattr(__builtin__, "quit"))
367         self.assertTrue(hasattr(__builtin__, "exit"))
368 
369     def test_setting_copyright(self):
370         # 'copyright' and 'credits' should be in __builtin__
371         self.assertTrue(hasattr(__builtin__, "copyright"))
372         self.assertTrue(hasattr(__builtin__, "credits"))
373 
374     def test_setting_help(self):
375         # 'help' should be set in __builtin__
376         self.assertTrue(hasattr(__builtin__, "help"))
377 
378     def test_aliasing_mbcs(self):
379         if sys.platform == "win32":
380             import locale
381             if locale.getdefaultlocale()[1].startswith('cp'):
382                 for value in encodings.aliases.aliases.itervalues():
383                     if value == "mbcs":
384                         break
385                 else:
386                     self.fail("did not alias mbcs")
387 
388     def test_setdefaultencoding_removed(self):
389         # Make sure sys.setdefaultencoding is gone
390         self.assertTrue(not hasattr(sys, "setdefaultencoding"))
391 
392     def test_sitecustomize_executed(self):
393         # If sitecustomize is available, it should have been imported.
394         if "sitecustomize" not in sys.modules:
395             try:
396                 import sitecustomize
397             except ImportError:
398                 pass
399             else:
400                 self.fail("sitecustomize not imported automatically")
401 
402 def test_main():
403     run_unittest(HelperFunctionsTests, ImportSideEffectTests)
404 
405 if __name__ == "__main__":
406     test_main()
407