python (3.12.0)

(root)/
lib/
python3.12/
test/
test_getpath.py
       1  import copy
       2  import ntpath
       3  import pathlib
       4  import posixpath
       5  import unittest
       6  
       7  from test.support import verbose
       8  
       9  try:
      10      # If we are in a source tree, use the original source file for tests
      11      SOURCE = (pathlib.Path(__file__).absolute().parent.parent.parent / "Modules/getpath.py").read_bytes()
      12  except FileNotFoundError:
      13      # Try from _testcapimodule instead
      14      from _testinternalcapi import get_getpath_codeobject
      15      SOURCE = get_getpath_codeobject()
      16  
      17  
      18  class ESC[4;38;5;81mMockGetPathTests(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
      19      def __init__(self, *a, **kw):
      20          super().__init__(*a, **kw)
      21          self.maxDiff = None
      22  
      23      def test_normal_win32(self):
      24          "Test a 'standard' install layout on Windows."
      25          ns = MockNTNamespace(
      26              argv0=r"C:\Python\python.exe",
      27              real_executable=r"C:\Python\python.exe",
      28          )
      29          ns.add_known_xfile(r"C:\Python\python.exe")
      30          ns.add_known_file(r"C:\Python\Lib\os.py")
      31          ns.add_known_dir(r"C:\Python\DLLs")
      32          expected = dict(
      33              executable=r"C:\Python\python.exe",
      34              base_executable=r"C:\Python\python.exe",
      35              prefix=r"C:\Python",
      36              exec_prefix=r"C:\Python",
      37              module_search_paths_set=1,
      38              module_search_paths=[
      39                  r"C:\Python\python98.zip",
      40                  r"C:\Python\DLLs",
      41                  r"C:\Python\Lib",
      42                  r"C:\Python",
      43              ],
      44          )
      45          actual = getpath(ns, expected)
      46          self.assertEqual(expected, actual)
      47  
      48      def test_buildtree_win32(self):
      49          "Test an in-build-tree layout on Windows."
      50          ns = MockNTNamespace(
      51              argv0=r"C:\CPython\PCbuild\amd64\python.exe",
      52              real_executable=r"C:\CPython\PCbuild\amd64\python.exe",
      53          )
      54          ns.add_known_xfile(r"C:\CPython\PCbuild\amd64\python.exe")
      55          ns.add_known_file(r"C:\CPython\Lib\os.py")
      56          ns.add_known_file(r"C:\CPython\PCbuild\amd64\pybuilddir.txt", [""])
      57          expected = dict(
      58              executable=r"C:\CPython\PCbuild\amd64\python.exe",
      59              base_executable=r"C:\CPython\PCbuild\amd64\python.exe",
      60              prefix=r"C:\CPython",
      61              exec_prefix=r"C:\CPython",
      62              build_prefix=r"C:\CPython",
      63              _is_python_build=1,
      64              module_search_paths_set=1,
      65              module_search_paths=[
      66                  r"C:\CPython\PCbuild\amd64\python98.zip",
      67                  r"C:\CPython\PCbuild\amd64",
      68                  r"C:\CPython\Lib",
      69              ],
      70          )
      71          actual = getpath(ns, expected)
      72          self.assertEqual(expected, actual)
      73  
      74      def test_venv_win32(self):
      75          """Test a venv layout on Windows.
      76  
      77          This layout is discovered by the presence of %__PYVENV_LAUNCHER__%,
      78          specifying the original launcher executable. site.py is responsible
      79          for updating prefix and exec_prefix.
      80          """
      81          ns = MockNTNamespace(
      82              argv0=r"C:\Python\python.exe",
      83              ENV___PYVENV_LAUNCHER__=r"C:\venv\Scripts\python.exe",
      84              real_executable=r"C:\Python\python.exe",
      85          )
      86          ns.add_known_xfile(r"C:\Python\python.exe")
      87          ns.add_known_xfile(r"C:\venv\Scripts\python.exe")
      88          ns.add_known_file(r"C:\Python\Lib\os.py")
      89          ns.add_known_dir(r"C:\Python\DLLs")
      90          ns.add_known_file(r"C:\venv\pyvenv.cfg", [
      91              r"home = C:\Python"
      92          ])
      93          expected = dict(
      94              executable=r"C:\venv\Scripts\python.exe",
      95              prefix=r"C:\Python",
      96              exec_prefix=r"C:\Python",
      97              base_executable=r"C:\Python\python.exe",
      98              base_prefix=r"C:\Python",
      99              base_exec_prefix=r"C:\Python",
     100              module_search_paths_set=1,
     101              module_search_paths=[
     102                  r"C:\Python\python98.zip",
     103                  r"C:\Python\DLLs",
     104                  r"C:\Python\Lib",
     105                  r"C:\Python",
     106              ],
     107          )
     108          actual = getpath(ns, expected)
     109          self.assertEqual(expected, actual)
     110  
     111      def test_registry_win32(self):
     112          """Test registry lookup on Windows.
     113  
     114          On Windows there are registry entries that are intended for other
     115          applications to register search paths.
     116          """
     117          hkey = rf"HKLM\Software\Python\PythonCore\9.8-XY\PythonPath"
     118          winreg = MockWinreg({
     119              hkey: None,
     120              f"{hkey}\\Path1": "path1-dir",
     121              f"{hkey}\\Path1\\Subdir": "not-subdirs",
     122          })
     123          ns = MockNTNamespace(
     124              argv0=r"C:\Python\python.exe",
     125              real_executable=r"C:\Python\python.exe",
     126              winreg=winreg,
     127          )
     128          ns.add_known_xfile(r"C:\Python\python.exe")
     129          ns.add_known_file(r"C:\Python\Lib\os.py")
     130          ns.add_known_dir(r"C:\Python\DLLs")
     131          expected = dict(
     132              module_search_paths_set=1,
     133              module_search_paths=[
     134                  r"C:\Python\python98.zip",
     135                  "path1-dir",
     136                  # should not contain not-subdirs
     137                  r"C:\Python\DLLs",
     138                  r"C:\Python\Lib",
     139                  r"C:\Python",
     140              ],
     141          )
     142          actual = getpath(ns, expected)
     143          self.assertEqual(expected, actual)
     144  
     145          ns["config"]["use_environment"] = 0
     146          ns["config"]["module_search_paths_set"] = 0
     147          ns["config"]["module_search_paths"] = None
     148          expected = dict(
     149              module_search_paths_set=1,
     150              module_search_paths=[
     151                  r"C:\Python\python98.zip",
     152                  r"C:\Python\DLLs",
     153                  r"C:\Python\Lib",
     154                  r"C:\Python",
     155              ],
     156          )
     157          actual = getpath(ns, expected)
     158          self.assertEqual(expected, actual)
     159  
     160      def test_symlink_normal_win32(self):
     161          "Test a 'standard' install layout via symlink on Windows."
     162          ns = MockNTNamespace(
     163              argv0=r"C:\LinkedFrom\python.exe",
     164              real_executable=r"C:\Python\python.exe",
     165          )
     166          ns.add_known_xfile(r"C:\LinkedFrom\python.exe")
     167          ns.add_known_xfile(r"C:\Python\python.exe")
     168          ns.add_known_link(r"C:\LinkedFrom\python.exe", r"C:\Python\python.exe")
     169          ns.add_known_file(r"C:\Python\Lib\os.py")
     170          ns.add_known_dir(r"C:\Python\DLLs")
     171          expected = dict(
     172              executable=r"C:\LinkedFrom\python.exe",
     173              base_executable=r"C:\LinkedFrom\python.exe",
     174              prefix=r"C:\Python",
     175              exec_prefix=r"C:\Python",
     176              module_search_paths_set=1,
     177              module_search_paths=[
     178                  r"C:\Python\python98.zip",
     179                  r"C:\Python\DLLs",
     180                  r"C:\Python\Lib",
     181                  r"C:\Python",
     182              ],
     183          )
     184          actual = getpath(ns, expected)
     185          self.assertEqual(expected, actual)
     186  
     187      def test_symlink_buildtree_win32(self):
     188          "Test an in-build-tree layout via symlink on Windows."
     189          ns = MockNTNamespace(
     190              argv0=r"C:\LinkedFrom\python.exe",
     191              real_executable=r"C:\CPython\PCbuild\amd64\python.exe",
     192          )
     193          ns.add_known_xfile(r"C:\LinkedFrom\python.exe")
     194          ns.add_known_xfile(r"C:\CPython\PCbuild\amd64\python.exe")
     195          ns.add_known_link(r"C:\LinkedFrom\python.exe", r"C:\CPython\PCbuild\amd64\python.exe")
     196          ns.add_known_file(r"C:\CPython\Lib\os.py")
     197          ns.add_known_file(r"C:\CPython\PCbuild\amd64\pybuilddir.txt", [""])
     198          expected = dict(
     199              executable=r"C:\LinkedFrom\python.exe",
     200              base_executable=r"C:\LinkedFrom\python.exe",
     201              prefix=r"C:\CPython",
     202              exec_prefix=r"C:\CPython",
     203              build_prefix=r"C:\CPython",
     204              _is_python_build=1,
     205              module_search_paths_set=1,
     206              module_search_paths=[
     207                  r"C:\CPython\PCbuild\amd64\python98.zip",
     208                  r"C:\CPython\PCbuild\amd64",
     209                  r"C:\CPython\Lib",
     210              ],
     211          )
     212          actual = getpath(ns, expected)
     213          self.assertEqual(expected, actual)
     214  
     215      def test_buildtree_pythonhome_win32(self):
     216          "Test an out-of-build-tree layout on Windows with PYTHONHOME override."
     217          ns = MockNTNamespace(
     218              argv0=r"C:\Out\python.exe",
     219              real_executable=r"C:\Out\python.exe",
     220              ENV_PYTHONHOME=r"C:\CPython",
     221          )
     222          ns.add_known_xfile(r"C:\Out\python.exe")
     223          ns.add_known_file(r"C:\CPython\Lib\os.py")
     224          ns.add_known_file(r"C:\Out\pybuilddir.txt", [""])
     225          expected = dict(
     226              executable=r"C:\Out\python.exe",
     227              base_executable=r"C:\Out\python.exe",
     228              prefix=r"C:\CPython",
     229              exec_prefix=r"C:\CPython",
     230              # This build_prefix is a miscalculation, because we have
     231              # moved the output direction out of the prefix.
     232              # Specify PYTHONHOME to get the correct prefix/exec_prefix
     233              build_prefix="C:\\",
     234              _is_python_build=1,
     235              module_search_paths_set=1,
     236              module_search_paths=[
     237                  r"C:\Out\python98.zip",
     238                  r"C:\Out",
     239                  r"C:\CPython\Lib",
     240              ],
     241          )
     242          actual = getpath(ns, expected)
     243          self.assertEqual(expected, actual)
     244  
     245      def test_no_dlls_win32(self):
     246          "Test a layout on Windows with no DLLs directory."
     247          ns = MockNTNamespace(
     248              argv0=r"C:\Python\python.exe",
     249              real_executable=r"C:\Python\python.exe",
     250          )
     251          ns.add_known_xfile(r"C:\Python\python.exe")
     252          ns.add_known_file(r"C:\Python\Lib\os.py")
     253          expected = dict(
     254              executable=r"C:\Python\python.exe",
     255              base_executable=r"C:\Python\python.exe",
     256              prefix=r"C:\Python",
     257              exec_prefix=r"C:\Python",
     258              module_search_paths_set=1,
     259              module_search_paths=[
     260                  r"C:\Python\python98.zip",
     261                  r"C:\Python",
     262                  r"C:\Python\Lib",
     263              ],
     264          )
     265          actual = getpath(ns, expected)
     266          self.assertEqual(expected, actual)
     267  
     268      def test_normal_posix(self):
     269          "Test a 'standard' install layout on *nix"
     270          ns = MockPosixNamespace(
     271              PREFIX="/usr",
     272              argv0="python",
     273              ENV_PATH="/usr/bin",
     274          )
     275          ns.add_known_xfile("/usr/bin/python")
     276          ns.add_known_file("/usr/lib/python9.8/os.py")
     277          ns.add_known_dir("/usr/lib/python9.8/lib-dynload")
     278          expected = dict(
     279              executable="/usr/bin/python",
     280              base_executable="/usr/bin/python",
     281              prefix="/usr",
     282              exec_prefix="/usr",
     283              module_search_paths_set=1,
     284              module_search_paths=[
     285                  "/usr/lib/python98.zip",
     286                  "/usr/lib/python9.8",
     287                  "/usr/lib/python9.8/lib-dynload",
     288              ],
     289          )
     290          actual = getpath(ns, expected)
     291          self.assertEqual(expected, actual)
     292  
     293      def test_buildpath_posix(self):
     294          """Test an in-build-tree layout on POSIX.
     295  
     296          This layout is discovered from the presence of pybuilddir.txt, which
     297          contains the relative path from the executable's directory to the
     298          platstdlib path.
     299          """
     300          ns = MockPosixNamespace(
     301              argv0=r"/home/cpython/python",
     302              PREFIX="/usr/local",
     303          )
     304          ns.add_known_xfile("/home/cpython/python")
     305          ns.add_known_xfile("/usr/local/bin/python")
     306          ns.add_known_file("/home/cpython/pybuilddir.txt", ["build/lib.linux-x86_64-9.8"])
     307          ns.add_known_file("/home/cpython/Lib/os.py")
     308          ns.add_known_dir("/home/cpython/lib-dynload")
     309          expected = dict(
     310              executable="/home/cpython/python",
     311              prefix="/usr/local",
     312              exec_prefix="/usr/local",
     313              base_executable="/home/cpython/python",
     314              build_prefix="/home/cpython",
     315              _is_python_build=1,
     316              module_search_paths_set=1,
     317              module_search_paths=[
     318                  "/usr/local/lib/python98.zip",
     319                  "/home/cpython/Lib",
     320                  "/home/cpython/build/lib.linux-x86_64-9.8",
     321              ],
     322          )
     323          actual = getpath(ns, expected)
     324          self.assertEqual(expected, actual)
     325  
     326      def test_venv_posix(self):
     327          "Test a venv layout on *nix."
     328          ns = MockPosixNamespace(
     329              argv0="python",
     330              PREFIX="/usr",
     331              ENV_PATH="/venv/bin:/usr/bin",
     332          )
     333          ns.add_known_xfile("/usr/bin/python")
     334          ns.add_known_xfile("/venv/bin/python")
     335          ns.add_known_file("/usr/lib/python9.8/os.py")
     336          ns.add_known_dir("/usr/lib/python9.8/lib-dynload")
     337          ns.add_known_file("/venv/pyvenv.cfg", [
     338              r"home = /usr/bin"
     339          ])
     340          expected = dict(
     341              executable="/venv/bin/python",
     342              prefix="/usr",
     343              exec_prefix="/usr",
     344              base_executable="/usr/bin/python",
     345              base_prefix="/usr",
     346              base_exec_prefix="/usr",
     347              module_search_paths_set=1,
     348              module_search_paths=[
     349                  "/usr/lib/python98.zip",
     350                  "/usr/lib/python9.8",
     351                  "/usr/lib/python9.8/lib-dynload",
     352              ],
     353          )
     354          actual = getpath(ns, expected)
     355          self.assertEqual(expected, actual)
     356  
     357      def test_venv_changed_name_posix(self):
     358          "Test a venv layout on *nix."
     359          ns = MockPosixNamespace(
     360              argv0="python",
     361              PREFIX="/usr",
     362              ENV_PATH="/venv/bin:/usr/bin",
     363          )
     364          ns.add_known_xfile("/usr/bin/python3")
     365          ns.add_known_xfile("/venv/bin/python")
     366          ns.add_known_link("/venv/bin/python", "/usr/bin/python3")
     367          ns.add_known_file("/usr/lib/python9.8/os.py")
     368          ns.add_known_dir("/usr/lib/python9.8/lib-dynload")
     369          ns.add_known_file("/venv/pyvenv.cfg", [
     370              r"home = /usr/bin"
     371          ])
     372          expected = dict(
     373              executable="/venv/bin/python",
     374              prefix="/usr",
     375              exec_prefix="/usr",
     376              base_executable="/usr/bin/python3",
     377              base_prefix="/usr",
     378              base_exec_prefix="/usr",
     379              module_search_paths_set=1,
     380              module_search_paths=[
     381                  "/usr/lib/python98.zip",
     382                  "/usr/lib/python9.8",
     383                  "/usr/lib/python9.8/lib-dynload",
     384              ],
     385          )
     386          actual = getpath(ns, expected)
     387          self.assertEqual(expected, actual)
     388  
     389      def test_venv_non_installed_zip_path_posix(self):
     390          "Test a venv created from non-installed python has correct zip path."""
     391          ns = MockPosixNamespace(
     392              argv0="/venv/bin/python",
     393              PREFIX="/usr",
     394              ENV_PATH="/venv/bin:/usr/bin",
     395          )
     396          ns.add_known_xfile("/path/to/non-installed/bin/python")
     397          ns.add_known_xfile("/venv/bin/python")
     398          ns.add_known_link("/venv/bin/python",
     399                            "/path/to/non-installed/bin/python")
     400          ns.add_known_file("/path/to/non-installed/lib/python9.8/os.py")
     401          ns.add_known_dir("/path/to/non-installed/lib/python9.8/lib-dynload")
     402          ns.add_known_file("/venv/pyvenv.cfg", [
     403              r"home = /path/to/non-installed"
     404          ])
     405          expected = dict(
     406              executable="/venv/bin/python",
     407              prefix="/path/to/non-installed",
     408              exec_prefix="/path/to/non-installed",
     409              base_executable="/path/to/non-installed/bin/python",
     410              base_prefix="/path/to/non-installed",
     411              base_exec_prefix="/path/to/non-installed",
     412              module_search_paths_set=1,
     413              module_search_paths=[
     414                  "/path/to/non-installed/lib/python98.zip",
     415                  "/path/to/non-installed/lib/python9.8",
     416                  "/path/to/non-installed/lib/python9.8/lib-dynload",
     417              ],
     418          )
     419          actual = getpath(ns, expected)
     420          self.assertEqual(expected, actual)
     421  
     422      def test_venv_changed_name_copy_posix(self):
     423          "Test a venv --copies layout on *nix that lacks a distributed 'python'"
     424          ns = MockPosixNamespace(
     425              argv0="python",
     426              PREFIX="/usr",
     427              ENV_PATH="/venv/bin:/usr/bin",
     428          )
     429          ns.add_known_xfile("/usr/bin/python9")
     430          ns.add_known_xfile("/venv/bin/python")
     431          ns.add_known_file("/usr/lib/python9.8/os.py")
     432          ns.add_known_dir("/usr/lib/python9.8/lib-dynload")
     433          ns.add_known_file("/venv/pyvenv.cfg", [
     434              r"home = /usr/bin"
     435          ])
     436          expected = dict(
     437              executable="/venv/bin/python",
     438              prefix="/usr",
     439              exec_prefix="/usr",
     440              base_executable="/usr/bin/python9",
     441              base_prefix="/usr",
     442              base_exec_prefix="/usr",
     443              module_search_paths_set=1,
     444              module_search_paths=[
     445                  "/usr/lib/python98.zip",
     446                  "/usr/lib/python9.8",
     447                  "/usr/lib/python9.8/lib-dynload",
     448              ],
     449          )
     450          actual = getpath(ns, expected)
     451          self.assertEqual(expected, actual)
     452  
     453      def test_symlink_normal_posix(self):
     454          "Test a 'standard' install layout via symlink on *nix"
     455          ns = MockPosixNamespace(
     456              PREFIX="/usr",
     457              argv0="/linkfrom/python",
     458          )
     459          ns.add_known_xfile("/linkfrom/python")
     460          ns.add_known_xfile("/usr/bin/python")
     461          ns.add_known_link("/linkfrom/python", "/usr/bin/python")
     462          ns.add_known_file("/usr/lib/python9.8/os.py")
     463          ns.add_known_dir("/usr/lib/python9.8/lib-dynload")
     464          expected = dict(
     465              executable="/linkfrom/python",
     466              base_executable="/linkfrom/python",
     467              prefix="/usr",
     468              exec_prefix="/usr",
     469              module_search_paths_set=1,
     470              module_search_paths=[
     471                  "/usr/lib/python98.zip",
     472                  "/usr/lib/python9.8",
     473                  "/usr/lib/python9.8/lib-dynload",
     474              ],
     475          )
     476          actual = getpath(ns, expected)
     477          self.assertEqual(expected, actual)
     478  
     479      def test_symlink_buildpath_posix(self):
     480          """Test an in-build-tree layout on POSIX.
     481  
     482          This layout is discovered from the presence of pybuilddir.txt, which
     483          contains the relative path from the executable's directory to the
     484          platstdlib path.
     485          """
     486          ns = MockPosixNamespace(
     487              argv0=r"/linkfrom/python",
     488              PREFIX="/usr/local",
     489          )
     490          ns.add_known_xfile("/linkfrom/python")
     491          ns.add_known_xfile("/home/cpython/python")
     492          ns.add_known_link("/linkfrom/python", "/home/cpython/python")
     493          ns.add_known_xfile("/usr/local/bin/python")
     494          ns.add_known_file("/home/cpython/pybuilddir.txt", ["build/lib.linux-x86_64-9.8"])
     495          ns.add_known_file("/home/cpython/Lib/os.py")
     496          ns.add_known_dir("/home/cpython/lib-dynload")
     497          expected = dict(
     498              executable="/linkfrom/python",
     499              prefix="/usr/local",
     500              exec_prefix="/usr/local",
     501              base_executable="/linkfrom/python",
     502              build_prefix="/home/cpython",
     503              _is_python_build=1,
     504              module_search_paths_set=1,
     505              module_search_paths=[
     506                  "/usr/local/lib/python98.zip",
     507                  "/home/cpython/Lib",
     508                  "/home/cpython/build/lib.linux-x86_64-9.8",
     509              ],
     510          )
     511          actual = getpath(ns, expected)
     512          self.assertEqual(expected, actual)
     513  
     514      def test_custom_platlibdir_posix(self):
     515          "Test an install with custom platlibdir on *nix"
     516          ns = MockPosixNamespace(
     517              PREFIX="/usr",
     518              argv0="/linkfrom/python",
     519              PLATLIBDIR="lib64",
     520          )
     521          ns.add_known_xfile("/usr/bin/python")
     522          ns.add_known_file("/usr/lib64/python9.8/os.py")
     523          ns.add_known_dir("/usr/lib64/python9.8/lib-dynload")
     524          expected = dict(
     525              executable="/linkfrom/python",
     526              base_executable="/linkfrom/python",
     527              prefix="/usr",
     528              exec_prefix="/usr",
     529              module_search_paths_set=1,
     530              module_search_paths=[
     531                  "/usr/lib64/python98.zip",
     532                  "/usr/lib64/python9.8",
     533                  "/usr/lib64/python9.8/lib-dynload",
     534              ],
     535          )
     536          actual = getpath(ns, expected)
     537          self.assertEqual(expected, actual)
     538  
     539      def test_framework_macos(self):
     540          """ Test framework layout on macOS
     541  
     542          This layout is primarily detected using a compile-time option
     543          (WITH_NEXT_FRAMEWORK).
     544          """
     545          ns = MockPosixNamespace(
     546              os_name="darwin",
     547              argv0="/Library/Frameworks/Python.framework/Versions/9.8/Resources/Python.app/Contents/MacOS/Python",
     548              WITH_NEXT_FRAMEWORK=1,
     549              PREFIX="/Library/Frameworks/Python.framework/Versions/9.8",
     550              EXEC_PREFIX="/Library/Frameworks/Python.framework/Versions/9.8",
     551              ENV___PYVENV_LAUNCHER__="/Library/Frameworks/Python.framework/Versions/9.8/bin/python9.8",
     552              real_executable="/Library/Frameworks/Python.framework/Versions/9.8/Resources/Python.app/Contents/MacOS/Python",
     553              library="/Library/Frameworks/Python.framework/Versions/9.8/Python",
     554          )
     555          ns.add_known_xfile("/Library/Frameworks/Python.framework/Versions/9.8/Resources/Python.app/Contents/MacOS/Python")
     556          ns.add_known_xfile("/Library/Frameworks/Python.framework/Versions/9.8/bin/python9.8")
     557          ns.add_known_dir("/Library/Frameworks/Python.framework/Versions/9.8/lib/python9.8/lib-dynload")
     558          ns.add_known_file("/Library/Frameworks/Python.framework/Versions/9.8/lib/python9.8/os.py")
     559  
     560          # This is definitely not the stdlib (see discusion in bpo-46890)
     561          #ns.add_known_file("/Library/Frameworks/lib/python98.zip")
     562  
     563          expected = dict(
     564              executable="/Library/Frameworks/Python.framework/Versions/9.8/bin/python9.8",
     565              prefix="/Library/Frameworks/Python.framework/Versions/9.8",
     566              exec_prefix="/Library/Frameworks/Python.framework/Versions/9.8",
     567              base_executable="/Library/Frameworks/Python.framework/Versions/9.8/bin/python9.8",
     568              base_prefix="/Library/Frameworks/Python.framework/Versions/9.8",
     569              base_exec_prefix="/Library/Frameworks/Python.framework/Versions/9.8",
     570              module_search_paths_set=1,
     571              module_search_paths=[
     572                  "/Library/Frameworks/Python.framework/Versions/9.8/lib/python98.zip",
     573                  "/Library/Frameworks/Python.framework/Versions/9.8/lib/python9.8",
     574                  "/Library/Frameworks/Python.framework/Versions/9.8/lib/python9.8/lib-dynload",
     575              ],
     576          )
     577          actual = getpath(ns, expected)
     578          self.assertEqual(expected, actual)
     579  
     580      def test_alt_framework_macos(self):
     581          """ Test framework layout on macOS with alternate framework name
     582  
     583          ``--with-framework-name=DebugPython``
     584  
     585          This layout is primarily detected using a compile-time option
     586          (WITH_NEXT_FRAMEWORK).
     587          """
     588          ns = MockPosixNamespace(
     589              argv0="/Library/Frameworks/DebugPython.framework/Versions/9.8/Resources/Python.app/Contents/MacOS/DebugPython",
     590              os_name="darwin",
     591              WITH_NEXT_FRAMEWORK=1,
     592              PREFIX="/Library/Frameworks/DebugPython.framework/Versions/9.8",
     593              EXEC_PREFIX="/Library/Frameworks/DebugPython.framework/Versions/9.8",
     594              ENV___PYVENV_LAUNCHER__="/Library/Frameworks/DebugPython.framework/Versions/9.8/bin/python9.8",
     595              real_executable="/Library/Frameworks/DebugPython.framework/Versions/9.8/Resources/Python.app/Contents/MacOS/DebugPython",
     596              library="/Library/Frameworks/DebugPython.framework/Versions/9.8/DebugPython",
     597              PYTHONPATH=None,
     598              ENV_PYTHONHOME=None,
     599              ENV_PYTHONEXECUTABLE=None,
     600              executable_dir=None,
     601              py_setpath=None,
     602          )
     603          ns.add_known_xfile("/Library/Frameworks/DebugPython.framework/Versions/9.8/Resources/Python.app/Contents/MacOS/DebugPython")
     604          ns.add_known_xfile("/Library/Frameworks/DebugPython.framework/Versions/9.8/bin/python9.8")
     605          ns.add_known_dir("/Library/Frameworks/DebugPython.framework/Versions/9.8/lib/python9.8/lib-dynload")
     606          ns.add_known_xfile("/Library/Frameworks/DebugPython.framework/Versions/9.8/lib/python9.8/os.py")
     607  
     608          # This is definitely not the stdlib (see discusion in bpo-46890)
     609          #ns.add_known_xfile("/Library/lib/python98.zip")
     610          expected = dict(
     611              executable="/Library/Frameworks/DebugPython.framework/Versions/9.8/bin/python9.8",
     612              prefix="/Library/Frameworks/DebugPython.framework/Versions/9.8",
     613              exec_prefix="/Library/Frameworks/DebugPython.framework/Versions/9.8",
     614              base_executable="/Library/Frameworks/DebugPython.framework/Versions/9.8/bin/python9.8",
     615              base_prefix="/Library/Frameworks/DebugPython.framework/Versions/9.8",
     616              base_exec_prefix="/Library/Frameworks/DebugPython.framework/Versions/9.8",
     617              module_search_paths_set=1,
     618              module_search_paths=[
     619                  "/Library/Frameworks/DebugPython.framework/Versions/9.8/lib/python98.zip",
     620                  "/Library/Frameworks/DebugPython.framework/Versions/9.8/lib/python9.8",
     621                  "/Library/Frameworks/DebugPython.framework/Versions/9.8/lib/python9.8/lib-dynload",
     622              ],
     623          )
     624          actual = getpath(ns, expected)
     625          self.assertEqual(expected, actual)
     626  
     627      def test_venv_framework_macos(self):
     628          """Test a venv layout on macOS using a framework build
     629          """
     630          venv_path = "/tmp/workdir/venv"
     631          ns = MockPosixNamespace(
     632              os_name="darwin",
     633              argv0="/Library/Frameworks/Python.framework/Versions/9.8/Resources/Python.app/Contents/MacOS/Python",
     634              WITH_NEXT_FRAMEWORK=1,
     635              PREFIX="/Library/Frameworks/Python.framework/Versions/9.8",
     636              EXEC_PREFIX="/Library/Frameworks/Python.framework/Versions/9.8",
     637              ENV___PYVENV_LAUNCHER__=f"{venv_path}/bin/python",
     638              real_executable="/Library/Frameworks/Python.framework/Versions/9.8/Resources/Python.app/Contents/MacOS/Python",
     639              library="/Library/Frameworks/Python.framework/Versions/9.8/Python",
     640          )
     641          ns.add_known_dir(venv_path)
     642          ns.add_known_dir(f"{venv_path}/bin")
     643          ns.add_known_dir(f"{venv_path}/lib")
     644          ns.add_known_dir(f"{venv_path}/lib/python9.8")
     645          ns.add_known_xfile(f"{venv_path}/bin/python")
     646          ns.add_known_xfile("/Library/Frameworks/Python.framework/Versions/9.8/Resources/Python.app/Contents/MacOS/Python")
     647          ns.add_known_xfile("/Library/Frameworks/Python.framework/Versions/9.8/bin/python9.8")
     648          ns.add_known_dir("/Library/Frameworks/Python.framework/Versions/9.8/lib/python9.8/lib-dynload")
     649          ns.add_known_xfile("/Library/Frameworks/Python.framework/Versions/9.8/lib/python9.8/os.py")
     650          ns.add_known_file(f"{venv_path}/pyvenv.cfg", [
     651              "home = /Library/Frameworks/Python.framework/Versions/9.8/bin"
     652          ])
     653          expected = dict(
     654              executable=f"{venv_path}/bin/python",
     655              prefix="/Library/Frameworks/Python.framework/Versions/9.8",
     656              exec_prefix="/Library/Frameworks/Python.framework/Versions/9.8",
     657              base_executable="/Library/Frameworks/Python.framework/Versions/9.8/bin/python9.8",
     658              base_prefix="/Library/Frameworks/Python.framework/Versions/9.8",
     659              base_exec_prefix="/Library/Frameworks/Python.framework/Versions/9.8",
     660              module_search_paths_set=1,
     661              module_search_paths=[
     662                  "/Library/Frameworks/Python.framework/Versions/9.8/lib/python98.zip",
     663                  "/Library/Frameworks/Python.framework/Versions/9.8/lib/python9.8",
     664                  "/Library/Frameworks/Python.framework/Versions/9.8/lib/python9.8/lib-dynload",
     665              ],
     666          )
     667          actual = getpath(ns, expected)
     668          self.assertEqual(expected, actual)
     669  
     670      def test_venv_alt_framework_macos(self):
     671          """Test a venv layout on macOS using a framework build
     672  
     673          ``--with-framework-name=DebugPython``
     674          """
     675          venv_path = "/tmp/workdir/venv"
     676          ns = MockPosixNamespace(
     677              os_name="darwin",
     678              argv0="/Library/Frameworks/DebugPython.framework/Versions/9.8/Resources/Python.app/Contents/MacOS/DebugPython",
     679              WITH_NEXT_FRAMEWORK=1,
     680              PREFIX="/Library/Frameworks/DebugPython.framework/Versions/9.8",
     681              EXEC_PREFIX="/Library/Frameworks/DebugPython.framework/Versions/9.8",
     682              ENV___PYVENV_LAUNCHER__=f"{venv_path}/bin/python",
     683              real_executable="/Library/Frameworks/DebugPython.framework/Versions/9.8/Resources/Python.app/Contents/MacOS/DebugPython",
     684              library="/Library/Frameworks/DebugPython.framework/Versions/9.8/DebugPython",
     685          )
     686          ns.add_known_dir(venv_path)
     687          ns.add_known_dir(f"{venv_path}/bin")
     688          ns.add_known_dir(f"{venv_path}/lib")
     689          ns.add_known_dir(f"{venv_path}/lib/python9.8")
     690          ns.add_known_xfile(f"{venv_path}/bin/python")
     691          ns.add_known_xfile("/Library/Frameworks/DebugPython.framework/Versions/9.8/Resources/Python.app/Contents/MacOS/DebugPython")
     692          ns.add_known_xfile("/Library/Frameworks/DebugPython.framework/Versions/9.8/bin/python9.8")
     693          ns.add_known_dir("/Library/Frameworks/DebugPython.framework/Versions/9.8/lib/python9.8/lib-dynload")
     694          ns.add_known_xfile("/Library/Frameworks/DebugPython.framework/Versions/9.8/lib/python9.8/os.py")
     695          ns.add_known_file(f"{venv_path}/pyvenv.cfg", [
     696              "home = /Library/Frameworks/DebugPython.framework/Versions/9.8/bin"
     697          ])
     698          expected = dict(
     699              executable=f"{venv_path}/bin/python",
     700              prefix="/Library/Frameworks/DebugPython.framework/Versions/9.8",
     701              exec_prefix="/Library/Frameworks/DebugPython.framework/Versions/9.8",
     702              base_executable="/Library/Frameworks/DebugPython.framework/Versions/9.8/bin/python9.8",
     703              base_prefix="/Library/Frameworks/DebugPython.framework/Versions/9.8",
     704              base_exec_prefix="/Library/Frameworks/DebugPython.framework/Versions/9.8",
     705              module_search_paths_set=1,
     706              module_search_paths=[
     707                  "/Library/Frameworks/DebugPython.framework/Versions/9.8/lib/python98.zip",
     708                  "/Library/Frameworks/DebugPython.framework/Versions/9.8/lib/python9.8",
     709                  "/Library/Frameworks/DebugPython.framework/Versions/9.8/lib/python9.8/lib-dynload",
     710              ],
     711          )
     712          actual = getpath(ns, expected)
     713          self.assertEqual(expected, actual)
     714  
     715      def test_venv_macos(self):
     716          """Test a venv layout on macOS.
     717  
     718          This layout is discovered when 'executable' and 'real_executable' match,
     719          but $__PYVENV_LAUNCHER__ has been set to the original process.
     720          """
     721          ns = MockPosixNamespace(
     722              os_name="darwin",
     723              argv0="/usr/bin/python",
     724              PREFIX="/usr",
     725              ENV___PYVENV_LAUNCHER__="/framework/Python9.8/python",
     726              real_executable="/usr/bin/python",
     727          )
     728          ns.add_known_xfile("/usr/bin/python")
     729          ns.add_known_xfile("/framework/Python9.8/python")
     730          ns.add_known_file("/usr/lib/python9.8/os.py")
     731          ns.add_known_dir("/usr/lib/python9.8/lib-dynload")
     732          ns.add_known_file("/framework/Python9.8/pyvenv.cfg", [
     733              "home = /usr/bin"
     734          ])
     735          expected = dict(
     736              executable="/framework/Python9.8/python",
     737              prefix="/usr",
     738              exec_prefix="/usr",
     739              base_executable="/usr/bin/python",
     740              base_prefix="/usr",
     741              base_exec_prefix="/usr",
     742              module_search_paths_set=1,
     743              module_search_paths=[
     744                  "/usr/lib/python98.zip",
     745                  "/usr/lib/python9.8",
     746                  "/usr/lib/python9.8/lib-dynload",
     747              ],
     748          )
     749          actual = getpath(ns, expected)
     750          self.assertEqual(expected, actual)
     751  
     752      def test_symlink_normal_macos(self):
     753          "Test a 'standard' install layout via symlink on macOS"
     754          ns = MockPosixNamespace(
     755              os_name="darwin",
     756              PREFIX="/usr",
     757              argv0="python",
     758              ENV_PATH="/linkfrom:/usr/bin",
     759              # real_executable on macOS matches the invocation path
     760              real_executable="/linkfrom/python",
     761          )
     762          ns.add_known_xfile("/linkfrom/python")
     763          ns.add_known_xfile("/usr/bin/python")
     764          ns.add_known_link("/linkfrom/python", "/usr/bin/python")
     765          ns.add_known_file("/usr/lib/python9.8/os.py")
     766          ns.add_known_dir("/usr/lib/python9.8/lib-dynload")
     767          expected = dict(
     768              executable="/linkfrom/python",
     769              base_executable="/linkfrom/python",
     770              prefix="/usr",
     771              exec_prefix="/usr",
     772              module_search_paths_set=1,
     773              module_search_paths=[
     774                  "/usr/lib/python98.zip",
     775                  "/usr/lib/python9.8",
     776                  "/usr/lib/python9.8/lib-dynload",
     777              ],
     778          )
     779          actual = getpath(ns, expected)
     780          self.assertEqual(expected, actual)
     781  
     782      def test_symlink_buildpath_macos(self):
     783          """Test an in-build-tree layout via symlink on macOS.
     784  
     785          This layout is discovered from the presence of pybuilddir.txt, which
     786          contains the relative path from the executable's directory to the
     787          platstdlib path.
     788          """
     789          ns = MockPosixNamespace(
     790              os_name="darwin",
     791              argv0=r"python",
     792              ENV_PATH="/linkfrom:/usr/bin",
     793              PREFIX="/usr/local",
     794              # real_executable on macOS matches the invocation path
     795              real_executable="/linkfrom/python",
     796          )
     797          ns.add_known_xfile("/linkfrom/python")
     798          ns.add_known_xfile("/home/cpython/python")
     799          ns.add_known_link("/linkfrom/python", "/home/cpython/python")
     800          ns.add_known_xfile("/usr/local/bin/python")
     801          ns.add_known_file("/home/cpython/pybuilddir.txt", ["build/lib.macos-9.8"])
     802          ns.add_known_file("/home/cpython/Lib/os.py")
     803          ns.add_known_dir("/home/cpython/lib-dynload")
     804          expected = dict(
     805              executable="/linkfrom/python",
     806              prefix="/usr/local",
     807              exec_prefix="/usr/local",
     808              base_executable="/linkfrom/python",
     809              build_prefix="/home/cpython",
     810              _is_python_build=1,
     811              module_search_paths_set=1,
     812              module_search_paths=[
     813                  "/usr/local/lib/python98.zip",
     814                  "/home/cpython/Lib",
     815                  "/home/cpython/build/lib.macos-9.8",
     816              ],
     817          )
     818          actual = getpath(ns, expected)
     819          self.assertEqual(expected, actual)
     820  
     821  
     822  # ******************************************************************************
     823  
     824  DEFAULT_NAMESPACE = dict(
     825      PREFIX="",
     826      EXEC_PREFIX="",
     827      PYTHONPATH="",
     828      VPATH="",
     829      PLATLIBDIR="",
     830      PYDEBUGEXT="",
     831      VERSION_MAJOR=9,    # fixed version number for ease
     832      VERSION_MINOR=8,    # of testing
     833      PYWINVER=None,
     834      EXE_SUFFIX=None,
     835  
     836      ENV_PATH="",
     837      ENV_PYTHONHOME="",
     838      ENV_PYTHONEXECUTABLE="",
     839      ENV___PYVENV_LAUNCHER__="",
     840      argv0="",
     841      py_setpath="",
     842      real_executable="",
     843      executable_dir="",
     844      library="",
     845      winreg=None,
     846      build_prefix=None,
     847      venv_prefix=None,
     848  )
     849  
     850  DEFAULT_CONFIG = dict(
     851      home=None,
     852      platlibdir=None,
     853      pythonpath=None,
     854      program_name=None,
     855      prefix=None,
     856      exec_prefix=None,
     857      base_prefix=None,
     858      base_exec_prefix=None,
     859      executable=None,
     860      base_executable="",
     861      stdlib_dir=None,
     862      platstdlib_dir=None,
     863      module_search_paths=None,
     864      module_search_paths_set=0,
     865      pythonpath_env=None,
     866      argv=None,
     867      orig_argv=None,
     868  
     869      isolated=0,
     870      use_environment=1,
     871      use_site=1,
     872  )
     873  
     874  class ESC[4;38;5;81mMockNTNamespace(ESC[4;38;5;149mdict):
     875      def __init__(self, *a, argv0=None, config=None, **kw):
     876          self.update(DEFAULT_NAMESPACE)
     877          self["config"] = DEFAULT_CONFIG.copy()
     878          self["os_name"] = "nt"
     879          self["PLATLIBDIR"] = "DLLs"
     880          self["PYWINVER"] = "9.8-XY"
     881          self["VPATH"] = r"..\.."
     882          super().__init__(*a, **kw)
     883          if argv0:
     884              self["config"]["orig_argv"] = [argv0]
     885          if config:
     886              self["config"].update(config)
     887          self._files = {}
     888          self._links = {}
     889          self._dirs = set()
     890          self._warnings = []
     891  
     892      def add_known_file(self, path, lines=None):
     893          self._files[path.casefold()] = list(lines or ())
     894          self.add_known_dir(path.rpartition("\\")[0])
     895  
     896      def add_known_xfile(self, path):
     897          self.add_known_file(path)
     898  
     899      def add_known_link(self, path, target):
     900          self._links[path.casefold()] = target
     901  
     902      def add_known_dir(self, path):
     903          p = path.rstrip("\\").casefold()
     904          while p:
     905              self._dirs.add(p)
     906              p = p.rpartition("\\")[0]
     907  
     908      def __missing__(self, key):
     909          try:
     910              return getattr(self, key)
     911          except AttributeError:
     912              raise KeyError(key) from None
     913  
     914      def abspath(self, path):
     915          if self.isabs(path):
     916              return path
     917          return self.joinpath("C:\\Absolute", path)
     918  
     919      def basename(self, path):
     920          return path.rpartition("\\")[2]
     921  
     922      def dirname(self, path):
     923          name = path.rstrip("\\").rpartition("\\")[0]
     924          if name[1:] == ":":
     925              return name + "\\"
     926          return name
     927  
     928      def hassuffix(self, path, suffix):
     929          return path.casefold().endswith(suffix.casefold())
     930  
     931      def isabs(self, path):
     932          return path[1:3] == ":\\"
     933  
     934      def isdir(self, path):
     935          if verbose:
     936              print("Check if", path, "is a dir")
     937          return path.casefold() in self._dirs
     938  
     939      def isfile(self, path):
     940          if verbose:
     941              print("Check if", path, "is a file")
     942          return path.casefold() in self._files
     943  
     944      def ismodule(self, path):
     945          if verbose:
     946              print("Check if", path, "is a module")
     947          path = path.casefold()
     948          return path in self._files and path.rpartition(".")[2] == "py".casefold()
     949  
     950      def isxfile(self, path):
     951          if verbose:
     952              print("Check if", path, "is a executable")
     953          path = path.casefold()
     954          return path in self._files and path.rpartition(".")[2] == "exe".casefold()
     955  
     956      def joinpath(self, *path):
     957          return ntpath.normpath(ntpath.join(*path))
     958  
     959      def readlines(self, path):
     960          try:
     961              return self._files[path.casefold()]
     962          except KeyError:
     963              raise FileNotFoundError(path) from None
     964  
     965      def realpath(self, path, _trail=None):
     966          if verbose:
     967              print("Read link from", path)
     968          try:
     969              link = self._links[path.casefold()]
     970          except KeyError:
     971              return path
     972          if _trail is None:
     973              _trail = set()
     974          elif link.casefold() in _trail:
     975              raise OSError("circular link")
     976          _trail.add(link.casefold())
     977          return self.realpath(link, _trail)
     978  
     979      def warn(self, message):
     980          self._warnings.append(message)
     981          if verbose:
     982              print(message)
     983  
     984  
     985  class ESC[4;38;5;81mMockWinreg:
     986      HKEY_LOCAL_MACHINE = "HKLM"
     987      HKEY_CURRENT_USER = "HKCU"
     988  
     989      def __init__(self, keys):
     990          self.keys = {k.casefold(): v for k, v in keys.items()}
     991          self.open = {}
     992  
     993      def __repr__(self):
     994          return "<MockWinreg>"
     995  
     996      def __eq__(self, other):
     997          return isinstance(other, type(self))
     998  
     999      def open_keys(self):
    1000          return list(self.open)
    1001  
    1002      def OpenKeyEx(self, hkey, subkey):
    1003          if verbose:
    1004              print(f"OpenKeyEx({hkey}, {subkey})")
    1005          key = f"{hkey}\\{subkey}".casefold()
    1006          if key in self.keys:
    1007              self.open[key] = self.open.get(key, 0) + 1
    1008              return key
    1009          raise FileNotFoundError()
    1010  
    1011      def CloseKey(self, hkey):
    1012          if verbose:
    1013              print(f"CloseKey({hkey})")
    1014          hkey = hkey.casefold()
    1015          if hkey not in self.open:
    1016              raise RuntimeError("key is not open")
    1017          self.open[hkey] -= 1
    1018          if not self.open[hkey]:
    1019              del self.open[hkey]
    1020  
    1021      def EnumKey(self, hkey, i):
    1022          if verbose:
    1023              print(f"EnumKey({hkey}, {i})")
    1024          hkey = hkey.casefold()
    1025          if hkey not in self.open:
    1026              raise RuntimeError("key is not open")
    1027          prefix = f'{hkey}\\'
    1028          subkeys = [k[len(prefix):] for k in sorted(self.keys) if k.startswith(prefix)]
    1029          subkeys[:] = [k for k in subkeys if '\\' not in k]
    1030          for j, n in enumerate(subkeys):
    1031              if j == i:
    1032                  return n.removeprefix(prefix)
    1033          raise OSError("end of enumeration")
    1034  
    1035      def QueryValue(self, hkey, subkey):
    1036          if verbose:
    1037              print(f"QueryValue({hkey}, {subkey})")
    1038          hkey = hkey.casefold()
    1039          if hkey not in self.open:
    1040              raise RuntimeError("key is not open")
    1041          if subkey:
    1042              subkey = subkey.casefold()
    1043              hkey = f'{hkey}\\{subkey}'
    1044          try:
    1045              return self.keys[hkey]
    1046          except KeyError:
    1047              raise OSError()
    1048  
    1049  
    1050  class ESC[4;38;5;81mMockPosixNamespace(ESC[4;38;5;149mdict):
    1051      def __init__(self, *a, argv0=None, config=None, **kw):
    1052          self.update(DEFAULT_NAMESPACE)
    1053          self["config"] = DEFAULT_CONFIG.copy()
    1054          self["os_name"] = "posix"
    1055          self["PLATLIBDIR"] = "lib"
    1056          self["WITH_NEXT_FRAMEWORK"] = 0
    1057          super().__init__(*a, **kw)
    1058          if argv0:
    1059              self["config"]["orig_argv"] = [argv0]
    1060          if config:
    1061              self["config"].update(config)
    1062          self._files = {}
    1063          self._xfiles = set()
    1064          self._links = {}
    1065          self._dirs = set()
    1066          self._warnings = []
    1067  
    1068      def add_known_file(self, path, lines=None):
    1069          self._files[path] = list(lines or ())
    1070          self.add_known_dir(path.rpartition("/")[0])
    1071  
    1072      def add_known_xfile(self, path):
    1073          self.add_known_file(path)
    1074          self._xfiles.add(path)
    1075  
    1076      def add_known_link(self, path, target):
    1077          self._links[path] = target
    1078  
    1079      def add_known_dir(self, path):
    1080          p = path.rstrip("/")
    1081          while p:
    1082              self._dirs.add(p)
    1083              p = p.rpartition("/")[0]
    1084  
    1085      def __missing__(self, key):
    1086          try:
    1087              return getattr(self, key)
    1088          except AttributeError:
    1089              raise KeyError(key) from None
    1090  
    1091      def abspath(self, path):
    1092          if self.isabs(path):
    1093              return path
    1094          return self.joinpath("/Absolute", path)
    1095  
    1096      def basename(self, path):
    1097          return path.rpartition("/")[2]
    1098  
    1099      def dirname(self, path):
    1100          return path.rstrip("/").rpartition("/")[0]
    1101  
    1102      def hassuffix(self, path, suffix):
    1103          return path.endswith(suffix)
    1104  
    1105      def isabs(self, path):
    1106          return path[0:1] == "/"
    1107  
    1108      def isdir(self, path):
    1109          if verbose:
    1110              print("Check if", path, "is a dir")
    1111          return path in self._dirs
    1112  
    1113      def isfile(self, path):
    1114          if verbose:
    1115              print("Check if", path, "is a file")
    1116          return path in self._files
    1117  
    1118      def ismodule(self, path):
    1119          if verbose:
    1120              print("Check if", path, "is a module")
    1121          return path in self._files and path.rpartition(".")[2] == "py"
    1122  
    1123      def isxfile(self, path):
    1124          if verbose:
    1125              print("Check if", path, "is an xfile")
    1126          return path in self._xfiles
    1127  
    1128      def joinpath(self, *path):
    1129          return posixpath.normpath(posixpath.join(*path))
    1130  
    1131      def readlines(self, path):
    1132          try:
    1133              return self._files[path]
    1134          except KeyError:
    1135              raise FileNotFoundError(path) from None
    1136  
    1137      def realpath(self, path, _trail=None):
    1138          if verbose:
    1139              print("Read link from", path)
    1140          try:
    1141              link = self._links[path]
    1142          except KeyError:
    1143              return path
    1144          if _trail is None:
    1145              _trail = set()
    1146          elif link in _trail:
    1147              raise OSError("circular link")
    1148          _trail.add(link)
    1149          return self.realpath(link, _trail)
    1150  
    1151      def warn(self, message):
    1152          self._warnings.append(message)
    1153          if verbose:
    1154              print(message)
    1155  
    1156  
    1157  def diff_dict(before, after, prefix="global"):
    1158      diff = []
    1159      for k in sorted(before):
    1160          if k[:2] == "__":
    1161              continue
    1162          if k == "config":
    1163              diff_dict(before[k], after[k], prefix="config")
    1164              continue
    1165          if k in after and after[k] != before[k]:
    1166              diff.append((k, before[k], after[k]))
    1167      if not diff:
    1168          return
    1169      max_k = max(len(k) for k, _, _ in diff)
    1170      indent = " " * (len(prefix) + 1 + max_k)
    1171      if verbose:
    1172          for k, b, a in diff:
    1173              if b:
    1174                  print("{}.{} -{!r}\n{} +{!r}".format(prefix, k.ljust(max_k), b, indent, a))
    1175              else:
    1176                  print("{}.{} +{!r}".format(prefix, k.ljust(max_k), a))
    1177  
    1178  
    1179  def dump_dict(before, after, prefix="global"):
    1180      if not verbose or not after:
    1181          return
    1182      max_k = max(len(k) for k in after)
    1183      for k, v in sorted(after.items(), key=lambda i: i[0]):
    1184          if k[:2] == "__":
    1185              continue
    1186          if k == "config":
    1187              dump_dict(before[k], after[k], prefix="config")
    1188              continue
    1189          try:
    1190              if v != before[k]:
    1191                  print("{}.{} {!r} (was {!r})".format(prefix, k.ljust(max_k), v, before[k]))
    1192                  continue
    1193          except KeyError:
    1194              pass
    1195          print("{}.{} {!r}".format(prefix, k.ljust(max_k), v))
    1196  
    1197  
    1198  def getpath(ns, keys):
    1199      before = copy.deepcopy(ns)
    1200      failed = True
    1201      try:
    1202          exec(SOURCE, ns)
    1203          failed = False
    1204      finally:
    1205          if failed:
    1206              dump_dict(before, ns)
    1207          else:
    1208              diff_dict(before, ns)
    1209      return {
    1210          k: ns['config'].get(k, ns.get(k, ...))
    1211          for k in keys
    1212      }