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