1import copy 2import ntpath 3import pathlib 4import posixpath 5import unittest 6 7from test.support import verbose 8 9try: 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() 12except FileNotFoundError: 13 # Try from _testcapimodule instead 14 from _testinternalcapi import get_getpath_codeobject 15 SOURCE = get_getpath_codeobject() 16 17 18class MockGetPathTests(unittest.TestCase): 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 discussion 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 discussion 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 def test_explicitly_set_stdlib_dir(self): 822 """Test the explicitly set stdlib_dir in the config is respected.""" 823 ns = MockPosixNamespace( 824 PREFIX="/usr", 825 argv0="python", 826 ENV_PATH="/usr/bin", 827 ) 828 ns["config"]["stdlib_dir"] = "/custom_stdlib_dir" 829 expected = dict( 830 stdlib_dir="/custom_stdlib_dir", 831 ) 832 actual = getpath(ns, expected) 833 self.assertEqual(expected, actual) 834 835 836# ****************************************************************************** 837 838DEFAULT_NAMESPACE = dict( 839 PREFIX="", 840 EXEC_PREFIX="", 841 PYTHONPATH="", 842 VPATH="", 843 PLATLIBDIR="", 844 PYDEBUGEXT="", 845 VERSION_MAJOR=9, # fixed version number for ease 846 VERSION_MINOR=8, # of testing 847 ABI_THREAD="", 848 PYWINVER=None, 849 EXE_SUFFIX=None, 850 851 ENV_PATH="", 852 ENV_PYTHONHOME="", 853 ENV_PYTHONEXECUTABLE="", 854 ENV___PYVENV_LAUNCHER__="", 855 argv0="", 856 py_setpath="", 857 real_executable="", 858 executable_dir="", 859 library="", 860 winreg=None, 861 build_prefix=None, 862 venv_prefix=None, 863) 864 865DEFAULT_CONFIG = dict( 866 home=None, 867 platlibdir=None, 868 pythonpath=None, 869 program_name=None, 870 prefix=None, 871 exec_prefix=None, 872 base_prefix=None, 873 base_exec_prefix=None, 874 executable=None, 875 base_executable="", 876 stdlib_dir=None, 877 platstdlib_dir=None, 878 module_search_paths=None, 879 module_search_paths_set=0, 880 pythonpath_env=None, 881 argv=None, 882 orig_argv=None, 883 884 isolated=0, 885 use_environment=1, 886 use_site=1, 887) 888 889class MockNTNamespace(dict): 890 def __init__(self, *a, argv0=None, config=None, **kw): 891 self.update(DEFAULT_NAMESPACE) 892 self["config"] = DEFAULT_CONFIG.copy() 893 self["os_name"] = "nt" 894 self["PLATLIBDIR"] = "DLLs" 895 self["PYWINVER"] = "9.8-XY" 896 self["VPATH"] = r"..\.." 897 super().__init__(*a, **kw) 898 if argv0: 899 self["config"]["orig_argv"] = [argv0] 900 if config: 901 self["config"].update(config) 902 self._files = {} 903 self._links = {} 904 self._dirs = set() 905 self._warnings = [] 906 907 def add_known_file(self, path, lines=None): 908 self._files[path.casefold()] = list(lines or ()) 909 self.add_known_dir(path.rpartition("\\")[0]) 910 911 def add_known_xfile(self, path): 912 self.add_known_file(path) 913 914 def add_known_link(self, path, target): 915 self._links[path.casefold()] = target 916 917 def add_known_dir(self, path): 918 p = path.rstrip("\\").casefold() 919 while p: 920 self._dirs.add(p) 921 p = p.rpartition("\\")[0] 922 923 def __missing__(self, key): 924 try: 925 return getattr(self, key) 926 except AttributeError: 927 raise KeyError(key) from None 928 929 def abspath(self, path): 930 if self.isabs(path): 931 return path 932 return self.joinpath("C:\\Absolute", path) 933 934 def basename(self, path): 935 return path.rpartition("\\")[2] 936 937 def dirname(self, path): 938 name = path.rstrip("\\").rpartition("\\")[0] 939 if name[1:] == ":": 940 return name + "\\" 941 return name 942 943 def hassuffix(self, path, suffix): 944 return path.casefold().endswith(suffix.casefold()) 945 946 def isabs(self, path): 947 return path[1:3] == ":\\" 948 949 def isdir(self, path): 950 if verbose: 951 print("Check if", path, "is a dir") 952 return path.casefold() in self._dirs 953 954 def isfile(self, path): 955 if verbose: 956 print("Check if", path, "is a file") 957 return path.casefold() in self._files 958 959 def ismodule(self, path): 960 if verbose: 961 print("Check if", path, "is a module") 962 path = path.casefold() 963 return path in self._files and path.rpartition(".")[2] == "py".casefold() 964 965 def isxfile(self, path): 966 if verbose: 967 print("Check if", path, "is a executable") 968 path = path.casefold() 969 return path in self._files and path.rpartition(".")[2] == "exe".casefold() 970 971 def joinpath(self, *path): 972 return ntpath.normpath(ntpath.join(*path)) 973 974 def readlines(self, path): 975 try: 976 return self._files[path.casefold()] 977 except KeyError: 978 raise FileNotFoundError(path) from None 979 980 def realpath(self, path, _trail=None): 981 if verbose: 982 print("Read link from", path) 983 try: 984 link = self._links[path.casefold()] 985 except KeyError: 986 return path 987 if _trail is None: 988 _trail = set() 989 elif link.casefold() in _trail: 990 raise OSError("circular link") 991 _trail.add(link.casefold()) 992 return self.realpath(link, _trail) 993 994 def warn(self, message): 995 self._warnings.append(message) 996 if verbose: 997 print(message) 998 999 1000class MockWinreg: 1001 HKEY_LOCAL_MACHINE = "HKLM" 1002 HKEY_CURRENT_USER = "HKCU" 1003 1004 def __init__(self, keys): 1005 self.keys = {k.casefold(): v for k, v in keys.items()} 1006 self.open = {} 1007 1008 def __repr__(self): 1009 return "<MockWinreg>" 1010 1011 def __eq__(self, other): 1012 return isinstance(other, type(self)) 1013 1014 def open_keys(self): 1015 return list(self.open) 1016 1017 def OpenKeyEx(self, hkey, subkey): 1018 if verbose: 1019 print(f"OpenKeyEx({hkey}, {subkey})") 1020 key = f"{hkey}\\{subkey}".casefold() 1021 if key in self.keys: 1022 self.open[key] = self.open.get(key, 0) + 1 1023 return key 1024 raise FileNotFoundError() 1025 1026 def CloseKey(self, hkey): 1027 if verbose: 1028 print(f"CloseKey({hkey})") 1029 hkey = hkey.casefold() 1030 if hkey not in self.open: 1031 raise RuntimeError("key is not open") 1032 self.open[hkey] -= 1 1033 if not self.open[hkey]: 1034 del self.open[hkey] 1035 1036 def EnumKey(self, hkey, i): 1037 if verbose: 1038 print(f"EnumKey({hkey}, {i})") 1039 hkey = hkey.casefold() 1040 if hkey not in self.open: 1041 raise RuntimeError("key is not open") 1042 prefix = f'{hkey}\\' 1043 subkeys = [k[len(prefix):] for k in sorted(self.keys) if k.startswith(prefix)] 1044 subkeys[:] = [k for k in subkeys if '\\' not in k] 1045 for j, n in enumerate(subkeys): 1046 if j == i: 1047 return n.removeprefix(prefix) 1048 raise OSError("end of enumeration") 1049 1050 def QueryValue(self, hkey, subkey): 1051 if verbose: 1052 print(f"QueryValue({hkey}, {subkey})") 1053 hkey = hkey.casefold() 1054 if hkey not in self.open: 1055 raise RuntimeError("key is not open") 1056 if subkey: 1057 subkey = subkey.casefold() 1058 hkey = f'{hkey}\\{subkey}' 1059 try: 1060 return self.keys[hkey] 1061 except KeyError: 1062 raise OSError() 1063 1064 1065class MockPosixNamespace(dict): 1066 def __init__(self, *a, argv0=None, config=None, **kw): 1067 self.update(DEFAULT_NAMESPACE) 1068 self["config"] = DEFAULT_CONFIG.copy() 1069 self["os_name"] = "posix" 1070 self["PLATLIBDIR"] = "lib" 1071 self["WITH_NEXT_FRAMEWORK"] = 0 1072 super().__init__(*a, **kw) 1073 if argv0: 1074 self["config"]["orig_argv"] = [argv0] 1075 if config: 1076 self["config"].update(config) 1077 self._files = {} 1078 self._xfiles = set() 1079 self._links = {} 1080 self._dirs = set() 1081 self._warnings = [] 1082 1083 def add_known_file(self, path, lines=None): 1084 self._files[path] = list(lines or ()) 1085 self.add_known_dir(path.rpartition("/")[0]) 1086 1087 def add_known_xfile(self, path): 1088 self.add_known_file(path) 1089 self._xfiles.add(path) 1090 1091 def add_known_link(self, path, target): 1092 self._links[path] = target 1093 1094 def add_known_dir(self, path): 1095 p = path.rstrip("/") 1096 while p: 1097 self._dirs.add(p) 1098 p = p.rpartition("/")[0] 1099 1100 def __missing__(self, key): 1101 try: 1102 return getattr(self, key) 1103 except AttributeError: 1104 raise KeyError(key) from None 1105 1106 def abspath(self, path): 1107 if self.isabs(path): 1108 return path 1109 return self.joinpath("/Absolute", path) 1110 1111 def basename(self, path): 1112 return path.rpartition("/")[2] 1113 1114 def dirname(self, path): 1115 return path.rstrip("/").rpartition("/")[0] 1116 1117 def hassuffix(self, path, suffix): 1118 return path.endswith(suffix) 1119 1120 def isabs(self, path): 1121 return path[0:1] == "/" 1122 1123 def isdir(self, path): 1124 if verbose: 1125 print("Check if", path, "is a dir") 1126 return path in self._dirs 1127 1128 def isfile(self, path): 1129 if verbose: 1130 print("Check if", path, "is a file") 1131 return path in self._files 1132 1133 def ismodule(self, path): 1134 if verbose: 1135 print("Check if", path, "is a module") 1136 return path in self._files and path.rpartition(".")[2] == "py" 1137 1138 def isxfile(self, path): 1139 if verbose: 1140 print("Check if", path, "is an xfile") 1141 return path in self._xfiles 1142 1143 def joinpath(self, *path): 1144 return posixpath.normpath(posixpath.join(*path)) 1145 1146 def readlines(self, path): 1147 try: 1148 return self._files[path] 1149 except KeyError: 1150 raise FileNotFoundError(path) from None 1151 1152 def realpath(self, path, _trail=None): 1153 if verbose: 1154 print("Read link from", path) 1155 try: 1156 link = self._links[path] 1157 except KeyError: 1158 return path 1159 if _trail is None: 1160 _trail = set() 1161 elif link in _trail: 1162 raise OSError("circular link") 1163 _trail.add(link) 1164 return self.realpath(link, _trail) 1165 1166 def warn(self, message): 1167 self._warnings.append(message) 1168 if verbose: 1169 print(message) 1170 1171 1172def diff_dict(before, after, prefix="global"): 1173 diff = [] 1174 for k in sorted(before): 1175 if k[:2] == "__": 1176 continue 1177 if k == "config": 1178 diff_dict(before[k], after[k], prefix="config") 1179 continue 1180 if k in after and after[k] != before[k]: 1181 diff.append((k, before[k], after[k])) 1182 if not diff: 1183 return 1184 max_k = max(len(k) for k, _, _ in diff) 1185 indent = " " * (len(prefix) + 1 + max_k) 1186 if verbose: 1187 for k, b, a in diff: 1188 if b: 1189 print("{}.{} -{!r}\n{} +{!r}".format(prefix, k.ljust(max_k), b, indent, a)) 1190 else: 1191 print("{}.{} +{!r}".format(prefix, k.ljust(max_k), a)) 1192 1193 1194def dump_dict(before, after, prefix="global"): 1195 if not verbose or not after: 1196 return 1197 max_k = max(len(k) for k in after) 1198 for k, v in sorted(after.items(), key=lambda i: i[0]): 1199 if k[:2] == "__": 1200 continue 1201 if k == "config": 1202 dump_dict(before[k], after[k], prefix="config") 1203 continue 1204 try: 1205 if v != before[k]: 1206 print("{}.{} {!r} (was {!r})".format(prefix, k.ljust(max_k), v, before[k])) 1207 continue 1208 except KeyError: 1209 pass 1210 print("{}.{} {!r}".format(prefix, k.ljust(max_k), v)) 1211 1212 1213def getpath(ns, keys): 1214 before = copy.deepcopy(ns) 1215 failed = True 1216 try: 1217 exec(SOURCE, ns) 1218 failed = False 1219 finally: 1220 if failed: 1221 dump_dict(before, ns) 1222 else: 1223 diff_dict(before, ns) 1224 return { 1225 k: ns['config'].get(k, ns.get(k, ...)) 1226 for k in keys 1227 } 1228