(root)/
Python-3.12.0/
Lib/
test/
test_site.py
       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 import support
      10  from test.support import os_helper
      11  from test.support import socket_helper
      12  from test.support import captured_stderr
      13  from test.support.os_helper import TESTFN, EnvironmentVarGuard
      14  import ast
      15  import builtins
      16  import glob
      17  import io
      18  import os
      19  import re
      20  import shutil
      21  import subprocess
      22  import sys
      23  import sysconfig
      24  import tempfile
      25  import urllib.error
      26  import urllib.request
      27  from unittest import mock
      28  from copy import copy
      29  
      30  # These tests are not particularly useful if Python was invoked with -S.
      31  # If you add tests that are useful under -S, this skip should be moved
      32  # to the class level.
      33  if sys.flags.no_site:
      34      raise unittest.SkipTest("Python was invoked with -S")
      35  
      36  import site
      37  
      38  
      39  HAS_USER_SITE = (site.USER_SITE is not None)
      40  OLD_SYS_PATH = None
      41  
      42  
      43  def setUpModule():
      44      global OLD_SYS_PATH
      45      OLD_SYS_PATH = sys.path[:]
      46  
      47      if site.ENABLE_USER_SITE and not os.path.isdir(site.USER_SITE):
      48          # need to add user site directory for tests
      49          try:
      50              os.makedirs(site.USER_SITE)
      51              # modify sys.path: will be restored by tearDownModule()
      52              site.addsitedir(site.USER_SITE)
      53          except PermissionError as exc:
      54              raise unittest.SkipTest('unable to create user site directory (%r): %s'
      55                                      % (site.USER_SITE, exc))
      56  
      57  
      58  def tearDownModule():
      59      sys.path[:] = OLD_SYS_PATH
      60  
      61  
      62  class ESC[4;38;5;81mHelperFunctionsTests(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
      63      """Tests for helper functions.
      64      """
      65  
      66      def setUp(self):
      67          """Save a copy of sys.path"""
      68          self.sys_path = sys.path[:]
      69          self.old_base = site.USER_BASE
      70          self.old_site = site.USER_SITE
      71          self.old_prefixes = site.PREFIXES
      72          self.original_vars = sysconfig._CONFIG_VARS
      73          self.old_vars = copy(sysconfig._CONFIG_VARS)
      74  
      75      def tearDown(self):
      76          """Restore sys.path"""
      77          sys.path[:] = self.sys_path
      78          site.USER_BASE = self.old_base
      79          site.USER_SITE = self.old_site
      80          site.PREFIXES = self.old_prefixes
      81          sysconfig._CONFIG_VARS = self.original_vars
      82          # _CONFIG_VARS is None before get_config_vars() is called
      83          if sysconfig._CONFIG_VARS is not None:
      84              sysconfig._CONFIG_VARS.clear()
      85              sysconfig._CONFIG_VARS.update(self.old_vars)
      86  
      87      def test_makepath(self):
      88          # Test makepath() have an absolute path for its first return value
      89          # and a case-normalized version of the absolute path for its
      90          # second value.
      91          path_parts = ("Beginning", "End")
      92          original_dir = os.path.join(*path_parts)
      93          abs_dir, norm_dir = site.makepath(*path_parts)
      94          self.assertEqual(os.path.abspath(original_dir), abs_dir)
      95          if original_dir == os.path.normcase(original_dir):
      96              self.assertEqual(abs_dir, norm_dir)
      97          else:
      98              self.assertEqual(os.path.normcase(abs_dir), norm_dir)
      99  
     100      def test_init_pathinfo(self):
     101          dir_set = site._init_pathinfo()
     102          for entry in [site.makepath(path)[1] for path in sys.path
     103                          if path and os.path.exists(path)]:
     104              self.assertIn(entry, dir_set,
     105                            "%s from sys.path not found in set returned "
     106                            "by _init_pathinfo(): %s" % (entry, dir_set))
     107  
     108      def pth_file_tests(self, pth_file):
     109          """Contain common code for testing results of reading a .pth file"""
     110          self.assertIn(pth_file.imported, sys.modules,
     111                        "%s not in sys.modules" % pth_file.imported)
     112          self.assertIn(site.makepath(pth_file.good_dir_path)[0], sys.path)
     113          self.assertFalse(os.path.exists(pth_file.bad_dir_path))
     114  
     115      def test_addpackage(self):
     116          # Make sure addpackage() imports if the line starts with 'import',
     117          # adds directories to sys.path for any line in the file that is not a
     118          # comment or import that is a valid directory name for where the .pth
     119          # file resides; invalid directories are not added
     120          pth_file = PthFile()
     121          pth_file.cleanup(prep=True)  # to make sure that nothing is
     122                                        # pre-existing that shouldn't be
     123          try:
     124              pth_file.create()
     125              site.addpackage(pth_file.base_dir, pth_file.filename, set())
     126              self.pth_file_tests(pth_file)
     127          finally:
     128              pth_file.cleanup()
     129  
     130      def make_pth(self, contents, pth_dir='.', pth_name=TESTFN):
     131          # Create a .pth file and return its (abspath, basename).
     132          pth_dir = os.path.abspath(pth_dir)
     133          pth_basename = pth_name + '.pth'
     134          pth_fn = os.path.join(pth_dir, pth_basename)
     135          with open(pth_fn, 'w', encoding='utf-8') as pth_file:
     136              self.addCleanup(lambda: os.remove(pth_fn))
     137              pth_file.write(contents)
     138          return pth_dir, pth_basename
     139  
     140      def test_addpackage_import_bad_syntax(self):
     141          # Issue 10642
     142          pth_dir, pth_fn = self.make_pth("import bad-syntax\n")
     143          with captured_stderr() as err_out:
     144              site.addpackage(pth_dir, pth_fn, set())
     145          self.assertRegex(err_out.getvalue(), "line 1")
     146          self.assertRegex(err_out.getvalue(),
     147              re.escape(os.path.join(pth_dir, pth_fn)))
     148          # XXX: the previous two should be independent checks so that the
     149          # order doesn't matter.  The next three could be a single check
     150          # but my regex foo isn't good enough to write it.
     151          self.assertRegex(err_out.getvalue(), 'Traceback')
     152          self.assertRegex(err_out.getvalue(), r'import bad-syntax')
     153          self.assertRegex(err_out.getvalue(), 'SyntaxError')
     154  
     155      def test_addpackage_import_bad_exec(self):
     156          # Issue 10642
     157          pth_dir, pth_fn = self.make_pth("randompath\nimport nosuchmodule\n")
     158          with captured_stderr() as err_out:
     159              site.addpackage(pth_dir, pth_fn, set())
     160          self.assertRegex(err_out.getvalue(), "line 2")
     161          self.assertRegex(err_out.getvalue(),
     162              re.escape(os.path.join(pth_dir, pth_fn)))
     163          # XXX: ditto previous XXX comment.
     164          self.assertRegex(err_out.getvalue(), 'Traceback')
     165          self.assertRegex(err_out.getvalue(), 'ModuleNotFoundError')
     166  
     167      def test_addpackage_empty_lines(self):
     168          # Issue 33689
     169          pth_dir, pth_fn = self.make_pth("\n\n  \n\n")
     170          known_paths = site.addpackage(pth_dir, pth_fn, set())
     171          self.assertEqual(known_paths, set())
     172  
     173      def test_addpackage_import_bad_pth_file(self):
     174          # Issue 5258
     175          pth_dir, pth_fn = self.make_pth("abc\x00def\n")
     176          with captured_stderr() as err_out:
     177              self.assertFalse(site.addpackage(pth_dir, pth_fn, set()))
     178          self.maxDiff = None
     179          self.assertEqual(err_out.getvalue(), "")
     180          for path in sys.path:
     181              if isinstance(path, str):
     182                  self.assertNotIn("abc\x00def", path)
     183  
     184      def test_addsitedir(self):
     185          # Same tests for test_addpackage since addsitedir() essentially just
     186          # calls addpackage() for every .pth file in the directory
     187          pth_file = PthFile()
     188          pth_file.cleanup(prep=True) # Make sure that nothing is pre-existing
     189                                      # that is tested for
     190          try:
     191              pth_file.create()
     192              site.addsitedir(pth_file.base_dir, set())
     193              self.pth_file_tests(pth_file)
     194          finally:
     195              pth_file.cleanup()
     196  
     197      # This tests _getuserbase, hence the double underline
     198      # to distinguish from a test for getuserbase
     199      def test__getuserbase(self):
     200          self.assertEqual(site._getuserbase(), sysconfig._getuserbase())
     201  
     202      @unittest.skipUnless(HAS_USER_SITE, 'need user site')
     203      def test_get_path(self):
     204          if sys.platform == 'darwin' and sys._framework:
     205              scheme = 'osx_framework_user'
     206          else:
     207              scheme = os.name + '_user'
     208          self.assertEqual(os.path.normpath(site._get_path(site._getuserbase())),
     209                           sysconfig.get_path('purelib', scheme))
     210  
     211      @unittest.skipUnless(site.ENABLE_USER_SITE, "requires access to PEP 370 "
     212                            "user-site (site.ENABLE_USER_SITE)")
     213      @support.requires_subprocess()
     214      def test_s_option(self):
     215          # (ncoghlan) Change this to use script_helper...
     216          usersite = os.path.normpath(site.USER_SITE)
     217          self.assertIn(usersite, sys.path)
     218  
     219          env = os.environ.copy()
     220          rc = subprocess.call([sys.executable, '-c',
     221              'import sys; sys.exit(%r in sys.path)' % usersite],
     222              env=env)
     223          self.assertEqual(rc, 1)
     224  
     225          env = os.environ.copy()
     226          rc = subprocess.call([sys.executable, '-s', '-c',
     227              'import sys; sys.exit(%r in sys.path)' % usersite],
     228              env=env)
     229          if usersite == site.getsitepackages()[0]:
     230              self.assertEqual(rc, 1)
     231          else:
     232              self.assertEqual(rc, 0, "User site still added to path with -s")
     233  
     234          env = os.environ.copy()
     235          env["PYTHONNOUSERSITE"] = "1"
     236          rc = subprocess.call([sys.executable, '-c',
     237              'import sys; sys.exit(%r in sys.path)' % usersite],
     238              env=env)
     239          if usersite == site.getsitepackages()[0]:
     240              self.assertEqual(rc, 1)
     241          else:
     242              self.assertEqual(rc, 0,
     243                          "User site still added to path with PYTHONNOUSERSITE")
     244  
     245          env = os.environ.copy()
     246          env["PYTHONUSERBASE"] = "/tmp"
     247          rc = subprocess.call([sys.executable, '-c',
     248              'import sys, site; sys.exit(site.USER_BASE.startswith("/tmp"))'],
     249              env=env)
     250          self.assertEqual(rc, 1,
     251                          "User base not set by PYTHONUSERBASE")
     252  
     253      @unittest.skipUnless(HAS_USER_SITE, 'need user site')
     254      def test_getuserbase(self):
     255          site.USER_BASE = None
     256          user_base = site.getuserbase()
     257  
     258          # the call sets site.USER_BASE
     259          self.assertEqual(site.USER_BASE, user_base)
     260  
     261          # let's set PYTHONUSERBASE and see if it uses it
     262          site.USER_BASE = None
     263          import sysconfig
     264          sysconfig._CONFIG_VARS = None
     265  
     266          with EnvironmentVarGuard() as environ:
     267              environ['PYTHONUSERBASE'] = 'xoxo'
     268              self.assertTrue(site.getuserbase().startswith('xoxo'),
     269                              site.getuserbase())
     270  
     271      @unittest.skipUnless(HAS_USER_SITE, 'need user site')
     272      def test_getusersitepackages(self):
     273          site.USER_SITE = None
     274          site.USER_BASE = None
     275          user_site = site.getusersitepackages()
     276  
     277          # the call sets USER_BASE *and* USER_SITE
     278          self.assertEqual(site.USER_SITE, user_site)
     279          self.assertTrue(user_site.startswith(site.USER_BASE), user_site)
     280          self.assertEqual(site.USER_BASE, site.getuserbase())
     281  
     282      def test_getsitepackages(self):
     283          site.PREFIXES = ['xoxo']
     284          dirs = site.getsitepackages()
     285          if os.sep == '/':
     286              # OS X, Linux, FreeBSD, etc
     287              if sys.platlibdir != "lib":
     288                  self.assertEqual(len(dirs), 2)
     289                  wanted = os.path.join('xoxo', sys.platlibdir,
     290                                        'python%d.%d' % sys.version_info[:2],
     291                                        'site-packages')
     292                  self.assertEqual(dirs[0], wanted)
     293              else:
     294                  self.assertEqual(len(dirs), 1)
     295              wanted = os.path.join('xoxo', 'lib',
     296                                    'python%d.%d' % sys.version_info[:2],
     297                                    'site-packages')
     298              self.assertEqual(dirs[-1], wanted)
     299          else:
     300              # other platforms
     301              self.assertEqual(len(dirs), 2)
     302              self.assertEqual(dirs[0], 'xoxo')
     303              wanted = os.path.join('xoxo', 'lib', 'site-packages')
     304              self.assertEqual(os.path.normcase(dirs[1]),
     305                               os.path.normcase(wanted))
     306  
     307      @unittest.skipUnless(HAS_USER_SITE, 'need user site')
     308      def test_no_home_directory(self):
     309          # bpo-10496: getuserbase() and getusersitepackages() must not fail if
     310          # the current user has no home directory (if expanduser() returns the
     311          # path unchanged).
     312          site.USER_SITE = None
     313          site.USER_BASE = None
     314  
     315          with EnvironmentVarGuard() as environ, \
     316               mock.patch('os.path.expanduser', lambda path: path):
     317  
     318              del environ['PYTHONUSERBASE']
     319              del environ['APPDATA']
     320  
     321              user_base = site.getuserbase()
     322              self.assertTrue(user_base.startswith('~' + os.sep),
     323                              user_base)
     324  
     325              user_site = site.getusersitepackages()
     326              self.assertTrue(user_site.startswith(user_base), user_site)
     327  
     328          with mock.patch('os.path.isdir', return_value=False) as mock_isdir, \
     329               mock.patch.object(site, 'addsitedir') as mock_addsitedir, \
     330               support.swap_attr(site, 'ENABLE_USER_SITE', True):
     331  
     332              # addusersitepackages() must not add user_site to sys.path
     333              # if it is not an existing directory
     334              known_paths = set()
     335              site.addusersitepackages(known_paths)
     336  
     337              mock_isdir.assert_called_once_with(user_site)
     338              mock_addsitedir.assert_not_called()
     339              self.assertFalse(known_paths)
     340  
     341      def test_trace(self):
     342          message = "bla-bla-bla"
     343          for verbose, out in (True, message + "\n"), (False, ""):
     344              with mock.patch('sys.flags', mock.Mock(verbose=verbose)), \
     345                      mock.patch('sys.stderr', io.StringIO()):
     346                  site._trace(message)
     347                  self.assertEqual(sys.stderr.getvalue(), out)
     348  
     349  
     350  class ESC[4;38;5;81mPthFile(ESC[4;38;5;149mobject):
     351      """Helper class for handling testing of .pth files"""
     352  
     353      def __init__(self, filename_base=TESTFN, imported="time",
     354                      good_dirname="__testdir__", bad_dirname="__bad"):
     355          """Initialize instance variables"""
     356          self.filename = filename_base + ".pth"
     357          self.base_dir = os.path.abspath('')
     358          self.file_path = os.path.join(self.base_dir, self.filename)
     359          self.imported = imported
     360          self.good_dirname = good_dirname
     361          self.bad_dirname = bad_dirname
     362          self.good_dir_path = os.path.join(self.base_dir, self.good_dirname)
     363          self.bad_dir_path = os.path.join(self.base_dir, self.bad_dirname)
     364  
     365      def create(self):
     366          """Create a .pth file with a comment, blank lines, an ``import
     367          <self.imported>``, a line with self.good_dirname, and a line with
     368          self.bad_dirname.
     369  
     370          Creation of the directory for self.good_dir_path (based off of
     371          self.good_dirname) is also performed.
     372  
     373          Make sure to call self.cleanup() to undo anything done by this method.
     374  
     375          """
     376          FILE = open(self.file_path, 'w')
     377          try:
     378              print("#import @bad module name", file=FILE)
     379              print("\n", file=FILE)
     380              print("import %s" % self.imported, file=FILE)
     381              print(self.good_dirname, file=FILE)
     382              print(self.bad_dirname, file=FILE)
     383          finally:
     384              FILE.close()
     385          os.mkdir(self.good_dir_path)
     386  
     387      def cleanup(self, prep=False):
     388          """Make sure that the .pth file is deleted, self.imported is not in
     389          sys.modules, and that both self.good_dirname and self.bad_dirname are
     390          not existing directories."""
     391          if os.path.exists(self.file_path):
     392              os.remove(self.file_path)
     393          if prep:
     394              self.imported_module = sys.modules.get(self.imported)
     395              if self.imported_module:
     396                  del sys.modules[self.imported]
     397          else:
     398              if self.imported_module:
     399                  sys.modules[self.imported] = self.imported_module
     400          if os.path.exists(self.good_dir_path):
     401              os.rmdir(self.good_dir_path)
     402          if os.path.exists(self.bad_dir_path):
     403              os.rmdir(self.bad_dir_path)
     404  
     405  class ESC[4;38;5;81mImportSideEffectTests(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
     406      """Test side-effects from importing 'site'."""
     407  
     408      def setUp(self):
     409          """Make a copy of sys.path"""
     410          self.sys_path = sys.path[:]
     411  
     412      def tearDown(self):
     413          """Restore sys.path"""
     414          sys.path[:] = self.sys_path
     415  
     416      def test_abs_paths_cached_None(self):
     417          """Test for __cached__ is None.
     418  
     419          Regarding to PEP 3147, __cached__ can be None.
     420  
     421          See also: https://bugs.python.org/issue30167
     422          """
     423          sys.modules['test'].__cached__ = None
     424          site.abs_paths()
     425          self.assertIsNone(sys.modules['test'].__cached__)
     426  
     427      def test_no_duplicate_paths(self):
     428          # No duplicate paths should exist in sys.path
     429          # Handled by removeduppaths()
     430          site.removeduppaths()
     431          seen_paths = set()
     432          for path in sys.path:
     433              self.assertNotIn(path, seen_paths)
     434              seen_paths.add(path)
     435  
     436      @unittest.skip('test not implemented')
     437      def test_add_build_dir(self):
     438          # Test that the build directory's Modules directory is used when it
     439          # should be.
     440          # XXX: implement
     441          pass
     442  
     443      def test_setting_quit(self):
     444          # 'quit' and 'exit' should be injected into builtins
     445          self.assertTrue(hasattr(builtins, "quit"))
     446          self.assertTrue(hasattr(builtins, "exit"))
     447  
     448      def test_setting_copyright(self):
     449          # 'copyright', 'credits', and 'license' should be in builtins
     450          self.assertTrue(hasattr(builtins, "copyright"))
     451          self.assertTrue(hasattr(builtins, "credits"))
     452          self.assertTrue(hasattr(builtins, "license"))
     453  
     454      def test_setting_help(self):
     455          # 'help' should be set in builtins
     456          self.assertTrue(hasattr(builtins, "help"))
     457  
     458      def test_sitecustomize_executed(self):
     459          # If sitecustomize is available, it should have been imported.
     460          if "sitecustomize" not in sys.modules:
     461              try:
     462                  import sitecustomize
     463              except ImportError:
     464                  pass
     465              else:
     466                  self.fail("sitecustomize not imported automatically")
     467  
     468      @unittest.skipUnless(hasattr(urllib.request, "HTTPSHandler"),
     469                           'need SSL support to download license')
     470      @test.support.requires_resource('network')
     471      @test.support.system_must_validate_cert
     472      def test_license_exists_at_url(self):
     473          # This test is a bit fragile since it depends on the format of the
     474          # string displayed by license in the absence of a LICENSE file.
     475          url = license._Printer__data.split()[1]
     476          req = urllib.request.Request(url, method='HEAD')
     477          # Reset global urllib.request._opener
     478          self.addCleanup(urllib.request.urlcleanup)
     479          try:
     480              with socket_helper.transient_internet(url):
     481                  with urllib.request.urlopen(req) as data:
     482                      code = data.getcode()
     483          except urllib.error.HTTPError as e:
     484              code = e.code
     485          self.assertEqual(code, 200, msg="Can't find " + url)
     486  
     487  
     488  class ESC[4;38;5;81mStartupImportTests(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
     489  
     490      @support.requires_subprocess()
     491      def test_startup_imports(self):
     492          # Get sys.path in isolated mode (python3 -I)
     493          popen = subprocess.Popen([sys.executable, '-X', 'utf8', '-I',
     494                                    '-c', 'import sys; print(repr(sys.path))'],
     495                                   stdout=subprocess.PIPE,
     496                                   encoding='utf-8',
     497                                   errors='surrogateescape')
     498          stdout = popen.communicate()[0]
     499          self.assertEqual(popen.returncode, 0, repr(stdout))
     500          isolated_paths = ast.literal_eval(stdout)
     501  
     502          # bpo-27807: Even with -I, the site module executes all .pth files
     503          # found in sys.path (see site.addpackage()). Skip the test if at least
     504          # one .pth file is found.
     505          for path in isolated_paths:
     506              pth_files = glob.glob(os.path.join(glob.escape(path), "*.pth"))
     507              if pth_files:
     508                  self.skipTest(f"found {len(pth_files)} .pth files in: {path}")
     509  
     510          # This tests checks which modules are loaded by Python when it
     511          # initially starts upon startup.
     512          popen = subprocess.Popen([sys.executable, '-X', 'utf8', '-I', '-v',
     513                                    '-c', 'import sys; print(set(sys.modules))'],
     514                                   stdout=subprocess.PIPE,
     515                                   stderr=subprocess.PIPE,
     516                                   encoding='utf-8',
     517                                   errors='surrogateescape')
     518          stdout, stderr = popen.communicate()
     519          self.assertEqual(popen.returncode, 0, (stdout, stderr))
     520          modules = ast.literal_eval(stdout)
     521  
     522          self.assertIn('site', modules)
     523  
     524          # http://bugs.python.org/issue19205
     525          re_mods = {'re', '_sre', 're._compiler', 're._constants', 're._parser'}
     526          self.assertFalse(modules.intersection(re_mods), stderr)
     527  
     528          # http://bugs.python.org/issue9548
     529          self.assertNotIn('locale', modules, stderr)
     530  
     531          # http://bugs.python.org/issue19209
     532          self.assertNotIn('copyreg', modules, stderr)
     533  
     534          # http://bugs.python.org/issue19218
     535          collection_mods = {'_collections', 'collections', 'functools',
     536                             'heapq', 'itertools', 'keyword', 'operator',
     537                             'reprlib', 'types', 'weakref'
     538                            }.difference(sys.builtin_module_names)
     539          self.assertFalse(modules.intersection(collection_mods), stderr)
     540  
     541      @support.requires_subprocess()
     542      def test_startup_interactivehook(self):
     543          r = subprocess.Popen([sys.executable, '-c',
     544              'import sys; sys.exit(hasattr(sys, "__interactivehook__"))']).wait()
     545          self.assertTrue(r, "'__interactivehook__' not added by site")
     546  
     547      @support.requires_subprocess()
     548      def test_startup_interactivehook_isolated(self):
     549          # issue28192 readline is not automatically enabled in isolated mode
     550          r = subprocess.Popen([sys.executable, '-I', '-c',
     551              'import sys; sys.exit(hasattr(sys, "__interactivehook__"))']).wait()
     552          self.assertFalse(r, "'__interactivehook__' added in isolated mode")
     553  
     554      @support.requires_subprocess()
     555      def test_startup_interactivehook_isolated_explicit(self):
     556          # issue28192 readline can be explicitly enabled in isolated mode
     557          r = subprocess.Popen([sys.executable, '-I', '-c',
     558              'import site, sys; site.enablerlcompleter(); sys.exit(hasattr(sys, "__interactivehook__"))']).wait()
     559          self.assertTrue(r, "'__interactivehook__' not added by enablerlcompleter()")
     560  
     561  class ESC[4;38;5;81m_pthFileTests(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
     562  
     563      if sys.platform == 'win32':
     564          def _create_underpth_exe(self, lines, exe_pth=True):
     565              import _winapi
     566              temp_dir = tempfile.mkdtemp()
     567              self.addCleanup(os_helper.rmtree, temp_dir)
     568              exe_file = os.path.join(temp_dir, os.path.split(sys.executable)[1])
     569              dll_src_file = _winapi.GetModuleFileName(sys.dllhandle)
     570              dll_file = os.path.join(temp_dir, os.path.split(dll_src_file)[1])
     571              shutil.copy(sys.executable, exe_file)
     572              shutil.copy(dll_src_file, dll_file)
     573              for fn in glob.glob(os.path.join(os.path.split(dll_src_file)[0], "vcruntime*.dll")):
     574                  shutil.copy(fn, os.path.join(temp_dir, os.path.split(fn)[1]))
     575              if exe_pth:
     576                  _pth_file = os.path.splitext(exe_file)[0] + '._pth'
     577              else:
     578                  _pth_file = os.path.splitext(dll_file)[0] + '._pth'
     579              with open(_pth_file, 'w', encoding='utf8') as f:
     580                  for line in lines:
     581                      print(line, file=f)
     582              return exe_file
     583      else:
     584          def _create_underpth_exe(self, lines, exe_pth=True):
     585              if not exe_pth:
     586                  raise unittest.SkipTest("library ._pth file not supported on this platform")
     587              temp_dir = tempfile.mkdtemp()
     588              self.addCleanup(os_helper.rmtree, temp_dir)
     589              exe_file = os.path.join(temp_dir, os.path.split(sys.executable)[1])
     590              os.symlink(sys.executable, exe_file)
     591              _pth_file = exe_file + '._pth'
     592              with open(_pth_file, 'w') as f:
     593                  for line in lines:
     594                      print(line, file=f)
     595              return exe_file
     596  
     597      def _calc_sys_path_for_underpth_nosite(self, sys_prefix, lines):
     598          sys_path = []
     599          for line in lines:
     600              if not line or line[0] == '#':
     601                  continue
     602              abs_path = os.path.abspath(os.path.join(sys_prefix, line))
     603              sys_path.append(abs_path)
     604          return sys_path
     605  
     606      @support.requires_subprocess()
     607      def test_underpth_basic(self):
     608          libpath = test.support.STDLIB_DIR
     609          exe_prefix = os.path.dirname(sys.executable)
     610          pth_lines = ['#.', '# ..', *sys.path, '.', '..']
     611          exe_file = self._create_underpth_exe(pth_lines)
     612          sys_path = self._calc_sys_path_for_underpth_nosite(
     613              os.path.dirname(exe_file),
     614              pth_lines)
     615  
     616          output = subprocess.check_output([exe_file, '-X', 'utf8', '-c',
     617              'import sys; print("\\n".join(sys.path) if sys.flags.no_site else "")'
     618          ], encoding='utf-8', errors='surrogateescape')
     619          actual_sys_path = output.rstrip().split('\n')
     620          self.assertTrue(actual_sys_path, "sys.flags.no_site was False")
     621          self.assertEqual(
     622              actual_sys_path,
     623              sys_path,
     624              "sys.path is incorrect"
     625          )
     626  
     627      @support.requires_subprocess()
     628      def test_underpth_nosite_file(self):
     629          libpath = test.support.STDLIB_DIR
     630          exe_prefix = os.path.dirname(sys.executable)
     631          pth_lines = [
     632              'fake-path-name',
     633              *[libpath for _ in range(200)],
     634              '',
     635              '# comment',
     636          ]
     637          exe_file = self._create_underpth_exe(pth_lines)
     638          sys_path = self._calc_sys_path_for_underpth_nosite(
     639              os.path.dirname(exe_file),
     640              pth_lines)
     641  
     642          env = os.environ.copy()
     643          env['PYTHONPATH'] = 'from-env'
     644          env['PATH'] = '{}{}{}'.format(exe_prefix, os.pathsep, os.getenv('PATH'))
     645          output = subprocess.check_output([exe_file, '-c',
     646              'import sys; print("\\n".join(sys.path) if sys.flags.no_site else "")'
     647          ], env=env, encoding='utf-8', errors='surrogateescape')
     648          actual_sys_path = output.rstrip().split('\n')
     649          self.assertTrue(actual_sys_path, "sys.flags.no_site was False")
     650          self.assertEqual(
     651              actual_sys_path,
     652              sys_path,
     653              "sys.path is incorrect"
     654          )
     655  
     656      @support.requires_subprocess()
     657      def test_underpth_file(self):
     658          libpath = test.support.STDLIB_DIR
     659          exe_prefix = os.path.dirname(sys.executable)
     660          exe_file = self._create_underpth_exe([
     661              'fake-path-name',
     662              *[libpath for _ in range(200)],
     663              '',
     664              '# comment',
     665              'import site'
     666          ])
     667          sys_prefix = os.path.dirname(exe_file)
     668          env = os.environ.copy()
     669          env['PYTHONPATH'] = 'from-env'
     670          env['PATH'] = '{};{}'.format(exe_prefix, os.getenv('PATH'))
     671          rc = subprocess.call([exe_file, '-c',
     672              'import sys; sys.exit(not sys.flags.no_site and '
     673              '%r in sys.path and %r in sys.path and %r not in sys.path and '
     674              'all("\\r" not in p and "\\n" not in p for p in sys.path))' % (
     675                  os.path.join(sys_prefix, 'fake-path-name'),
     676                  libpath,
     677                  os.path.join(sys_prefix, 'from-env'),
     678              )], env=env)
     679          self.assertTrue(rc, "sys.path is incorrect")
     680  
     681      @support.requires_subprocess()
     682      def test_underpth_dll_file(self):
     683          libpath = test.support.STDLIB_DIR
     684          exe_prefix = os.path.dirname(sys.executable)
     685          exe_file = self._create_underpth_exe([
     686              'fake-path-name',
     687              *[libpath for _ in range(200)],
     688              '',
     689              '# comment',
     690              'import site'
     691          ], exe_pth=False)
     692          sys_prefix = os.path.dirname(exe_file)
     693          env = os.environ.copy()
     694          env['PYTHONPATH'] = 'from-env'
     695          env['PATH'] = '{};{}'.format(exe_prefix, os.getenv('PATH'))
     696          rc = subprocess.call([exe_file, '-c',
     697              'import sys; sys.exit(not sys.flags.no_site and '
     698              '%r in sys.path and %r in sys.path and %r not in sys.path and '
     699              'all("\\r" not in p and "\\n" not in p for p in sys.path))' % (
     700                  os.path.join(sys_prefix, 'fake-path-name'),
     701                  libpath,
     702                  os.path.join(sys_prefix, 'from-env'),
     703              )], env=env)
     704          self.assertTrue(rc, "sys.path is incorrect")
     705  
     706  
     707  if __name__ == "__main__":
     708      unittest.main()