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\Lib", 42 r"C:\Python\DLLs", 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\Lib", 68 r"C:\CPython\PCbuild\amd64", 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\Lib", 138 r"C:\Python\DLLs", 139 ], 140 ) 141 actual = getpath(ns, expected) 142 self.assertEqual(expected, actual) 143 144 ns["config"]["use_environment"] = 0 145 ns["config"]["module_search_paths_set"] = 0 146 ns["config"]["module_search_paths"] = None 147 expected = dict( 148 module_search_paths_set=1, 149 module_search_paths=[ 150 r"C:\Python\python98.zip", 151 r"C:\Python\Lib", 152 r"C:\Python\DLLs", 153 ], 154 ) 155 actual = getpath(ns, expected) 156 self.assertEqual(expected, actual) 157 158 def test_symlink_normal_win32(self): 159 "Test a 'standard' install layout via symlink on Windows." 160 ns = MockNTNamespace( 161 argv0=r"C:\LinkedFrom\python.exe", 162 real_executable=r"C:\Python\python.exe", 163 ) 164 ns.add_known_xfile(r"C:\LinkedFrom\python.exe") 165 ns.add_known_xfile(r"C:\Python\python.exe") 166 ns.add_known_link(r"C:\LinkedFrom\python.exe", r"C:\Python\python.exe") 167 ns.add_known_file(r"C:\Python\Lib\os.py") 168 ns.add_known_dir(r"C:\Python\DLLs") 169 expected = dict( 170 executable=r"C:\LinkedFrom\python.exe", 171 base_executable=r"C:\LinkedFrom\python.exe", 172 prefix=r"C:\Python", 173 exec_prefix=r"C:\Python", 174 module_search_paths_set=1, 175 module_search_paths=[ 176 r"C:\Python\python98.zip", 177 r"C:\Python\Lib", 178 r"C:\Python\DLLs", 179 ], 180 ) 181 actual = getpath(ns, expected) 182 self.assertEqual(expected, actual) 183 184 def test_symlink_buildtree_win32(self): 185 "Test an in-build-tree layout via symlink on Windows." 186 ns = MockNTNamespace( 187 argv0=r"C:\LinkedFrom\python.exe", 188 real_executable=r"C:\CPython\PCbuild\amd64\python.exe", 189 ) 190 ns.add_known_xfile(r"C:\LinkedFrom\python.exe") 191 ns.add_known_xfile(r"C:\CPython\PCbuild\amd64\python.exe") 192 ns.add_known_link(r"C:\LinkedFrom\python.exe", r"C:\CPython\PCbuild\amd64\python.exe") 193 ns.add_known_file(r"C:\CPython\Lib\os.py") 194 ns.add_known_file(r"C:\CPython\PCbuild\amd64\pybuilddir.txt", [""]) 195 expected = dict( 196 executable=r"C:\LinkedFrom\python.exe", 197 base_executable=r"C:\LinkedFrom\python.exe", 198 prefix=r"C:\CPython", 199 exec_prefix=r"C:\CPython", 200 build_prefix=r"C:\CPython", 201 _is_python_build=1, 202 module_search_paths_set=1, 203 module_search_paths=[ 204 r"C:\CPython\PCbuild\amd64\python98.zip", 205 r"C:\CPython\Lib", 206 r"C:\CPython\PCbuild\amd64", 207 ], 208 ) 209 actual = getpath(ns, expected) 210 self.assertEqual(expected, actual) 211 212 def test_buildtree_pythonhome_win32(self): 213 "Test an out-of-build-tree layout on Windows with PYTHONHOME override." 214 ns = MockNTNamespace( 215 argv0=r"C:\Out\python.exe", 216 real_executable=r"C:\Out\python.exe", 217 ENV_PYTHONHOME=r"C:\CPython", 218 ) 219 ns.add_known_xfile(r"C:\Out\python.exe") 220 ns.add_known_file(r"C:\CPython\Lib\os.py") 221 ns.add_known_file(r"C:\Out\pybuilddir.txt", [""]) 222 expected = dict( 223 executable=r"C:\Out\python.exe", 224 base_executable=r"C:\Out\python.exe", 225 prefix=r"C:\CPython", 226 exec_prefix=r"C:\CPython", 227 # This build_prefix is a miscalculation, because we have 228 # moved the output direction out of the prefix. 229 # Specify PYTHONHOME to get the correct prefix/exec_prefix 230 build_prefix="C:\\", 231 _is_python_build=1, 232 module_search_paths_set=1, 233 module_search_paths=[ 234 r"C:\Out\python98.zip", 235 r"C:\CPython\Lib", 236 r"C:\Out", 237 ], 238 ) 239 actual = getpath(ns, expected) 240 self.assertEqual(expected, actual) 241 242 def test_normal_posix(self): 243 "Test a 'standard' install layout on *nix" 244 ns = MockPosixNamespace( 245 PREFIX="/usr", 246 argv0="python", 247 ENV_PATH="/usr/bin", 248 ) 249 ns.add_known_xfile("/usr/bin/python") 250 ns.add_known_file("/usr/lib/python9.8/os.py") 251 ns.add_known_dir("/usr/lib/python9.8/lib-dynload") 252 expected = dict( 253 executable="/usr/bin/python", 254 base_executable="/usr/bin/python", 255 prefix="/usr", 256 exec_prefix="/usr", 257 module_search_paths_set=1, 258 module_search_paths=[ 259 "/usr/lib/python98.zip", 260 "/usr/lib/python9.8", 261 "/usr/lib/python9.8/lib-dynload", 262 ], 263 ) 264 actual = getpath(ns, expected) 265 self.assertEqual(expected, actual) 266 267 def test_buildpath_posix(self): 268 """Test an in-build-tree layout on POSIX. 269 270 This layout is discovered from the presence of pybuilddir.txt, which 271 contains the relative path from the executable's directory to the 272 platstdlib path. 273 """ 274 ns = MockPosixNamespace( 275 argv0=r"/home/cpython/python", 276 PREFIX="/usr/local", 277 ) 278 ns.add_known_xfile("/home/cpython/python") 279 ns.add_known_xfile("/usr/local/bin/python") 280 ns.add_known_file("/home/cpython/pybuilddir.txt", ["build/lib.linux-x86_64-9.8"]) 281 ns.add_known_file("/home/cpython/Lib/os.py") 282 ns.add_known_dir("/home/cpython/lib-dynload") 283 expected = dict( 284 executable="/home/cpython/python", 285 prefix="/usr/local", 286 exec_prefix="/usr/local", 287 base_executable="/home/cpython/python", 288 build_prefix="/home/cpython", 289 _is_python_build=1, 290 module_search_paths_set=1, 291 module_search_paths=[ 292 "/usr/local/lib/python98.zip", 293 "/home/cpython/Lib", 294 "/home/cpython/build/lib.linux-x86_64-9.8", 295 ], 296 ) 297 actual = getpath(ns, expected) 298 self.assertEqual(expected, actual) 299 300 def test_venv_posix(self): 301 "Test a venv layout on *nix." 302 ns = MockPosixNamespace( 303 argv0="python", 304 PREFIX="/usr", 305 ENV_PATH="/venv/bin:/usr/bin", 306 ) 307 ns.add_known_xfile("/usr/bin/python") 308 ns.add_known_xfile("/venv/bin/python") 309 ns.add_known_file("/usr/lib/python9.8/os.py") 310 ns.add_known_dir("/usr/lib/python9.8/lib-dynload") 311 ns.add_known_file("/venv/pyvenv.cfg", [ 312 r"home = /usr/bin" 313 ]) 314 expected = dict( 315 executable="/venv/bin/python", 316 prefix="/usr", 317 exec_prefix="/usr", 318 base_executable="/usr/bin/python", 319 base_prefix="/usr", 320 base_exec_prefix="/usr", 321 module_search_paths_set=1, 322 module_search_paths=[ 323 "/usr/lib/python98.zip", 324 "/usr/lib/python9.8", 325 "/usr/lib/python9.8/lib-dynload", 326 ], 327 ) 328 actual = getpath(ns, expected) 329 self.assertEqual(expected, actual) 330 331 def test_symlink_normal_posix(self): 332 "Test a 'standard' install layout via symlink on *nix" 333 ns = MockPosixNamespace( 334 PREFIX="/usr", 335 argv0="/linkfrom/python", 336 ) 337 ns.add_known_xfile("/linkfrom/python") 338 ns.add_known_xfile("/usr/bin/python") 339 ns.add_known_link("/linkfrom/python", "/usr/bin/python") 340 ns.add_known_file("/usr/lib/python9.8/os.py") 341 ns.add_known_dir("/usr/lib/python9.8/lib-dynload") 342 expected = dict( 343 executable="/linkfrom/python", 344 base_executable="/linkfrom/python", 345 prefix="/usr", 346 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_symlink_buildpath_posix(self): 358 """Test an in-build-tree layout on POSIX. 359 360 This layout is discovered from the presence of pybuilddir.txt, which 361 contains the relative path from the executable's directory to the 362 platstdlib path. 363 """ 364 ns = MockPosixNamespace( 365 argv0=r"/linkfrom/python", 366 PREFIX="/usr/local", 367 ) 368 ns.add_known_xfile("/linkfrom/python") 369 ns.add_known_xfile("/home/cpython/python") 370 ns.add_known_link("/linkfrom/python", "/home/cpython/python") 371 ns.add_known_xfile("/usr/local/bin/python") 372 ns.add_known_file("/home/cpython/pybuilddir.txt", ["build/lib.linux-x86_64-9.8"]) 373 ns.add_known_file("/home/cpython/Lib/os.py") 374 ns.add_known_dir("/home/cpython/lib-dynload") 375 expected = dict( 376 executable="/linkfrom/python", 377 prefix="/usr/local", 378 exec_prefix="/usr/local", 379 base_executable="/linkfrom/python", 380 build_prefix="/home/cpython", 381 _is_python_build=1, 382 module_search_paths_set=1, 383 module_search_paths=[ 384 "/usr/local/lib/python98.zip", 385 "/home/cpython/Lib", 386 "/home/cpython/build/lib.linux-x86_64-9.8", 387 ], 388 ) 389 actual = getpath(ns, expected) 390 self.assertEqual(expected, actual) 391 392 def test_custom_platlibdir_posix(self): 393 "Test an install with custom platlibdir on *nix" 394 ns = MockPosixNamespace( 395 PREFIX="/usr", 396 argv0="/linkfrom/python", 397 PLATLIBDIR="lib64", 398 ) 399 ns.add_known_xfile("/usr/bin/python") 400 ns.add_known_file("/usr/lib64/python9.8/os.py") 401 ns.add_known_dir("/usr/lib64/python9.8/lib-dynload") 402 expected = dict( 403 executable="/linkfrom/python", 404 base_executable="/linkfrom/python", 405 prefix="/usr", 406 exec_prefix="/usr", 407 module_search_paths_set=1, 408 module_search_paths=[ 409 "/usr/lib64/python98.zip", 410 "/usr/lib64/python9.8", 411 "/usr/lib64/python9.8/lib-dynload", 412 ], 413 ) 414 actual = getpath(ns, expected) 415 self.assertEqual(expected, actual) 416 417 def test_venv_macos(self): 418 """Test a venv layout on macOS. 419 420 This layout is discovered when 'executable' and 'real_executable' match, 421 but $__PYVENV_LAUNCHER__ has been set to the original process. 422 """ 423 ns = MockPosixNamespace( 424 os_name="darwin", 425 argv0="/usr/bin/python", 426 PREFIX="/usr", 427 ENV___PYVENV_LAUNCHER__="/framework/Python9.8/python", 428 real_executable="/usr/bin/python", 429 ) 430 ns.add_known_xfile("/usr/bin/python") 431 ns.add_known_xfile("/framework/Python9.8/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("/framework/Python9.8/pyvenv.cfg", [ 435 "home = /usr/bin" 436 ]) 437 expected = dict( 438 executable="/framework/Python9.8/python", 439 prefix="/usr", 440 exec_prefix="/usr", 441 base_executable="/usr/bin/python", 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_macos(self): 455 "Test a 'standard' install layout via symlink on macOS" 456 ns = MockPosixNamespace( 457 os_name="darwin", 458 PREFIX="/usr", 459 argv0="python", 460 ENV_PATH="/linkfrom:/usr/bin", 461 # real_executable on macOS matches the invocation path 462 real_executable="/linkfrom/python", 463 ) 464 ns.add_known_xfile("/linkfrom/python") 465 ns.add_known_xfile("/usr/bin/python") 466 ns.add_known_link("/linkfrom/python", "/usr/bin/python") 467 ns.add_known_file("/usr/lib/python9.8/os.py") 468 ns.add_known_dir("/usr/lib/python9.8/lib-dynload") 469 expected = dict( 470 executable="/linkfrom/python", 471 base_executable="/linkfrom/python", 472 prefix="/usr", 473 exec_prefix="/usr", 474 module_search_paths_set=1, 475 module_search_paths=[ 476 "/usr/lib/python98.zip", 477 "/usr/lib/python9.8", 478 "/usr/lib/python9.8/lib-dynload", 479 ], 480 ) 481 actual = getpath(ns, expected) 482 self.assertEqual(expected, actual) 483 484 def test_symlink_buildpath_macos(self): 485 """Test an in-build-tree layout via symlink on macOS. 486 487 This layout is discovered from the presence of pybuilddir.txt, which 488 contains the relative path from the executable's directory to the 489 platstdlib path. 490 """ 491 ns = MockPosixNamespace( 492 os_name="darwin", 493 argv0=r"python", 494 ENV_PATH="/linkfrom:/usr/bin", 495 PREFIX="/usr/local", 496 # real_executable on macOS matches the invocation path 497 real_executable="/linkfrom/python", 498 ) 499 ns.add_known_xfile("/linkfrom/python") 500 ns.add_known_xfile("/home/cpython/python") 501 ns.add_known_link("/linkfrom/python", "/home/cpython/python") 502 ns.add_known_xfile("/usr/local/bin/python") 503 ns.add_known_file("/home/cpython/pybuilddir.txt", ["build/lib.macos-9.8"]) 504 ns.add_known_file("/home/cpython/Lib/os.py") 505 ns.add_known_dir("/home/cpython/lib-dynload") 506 expected = dict( 507 executable="/linkfrom/python", 508 prefix="/usr/local", 509 exec_prefix="/usr/local", 510 base_executable="/linkfrom/python", 511 build_prefix="/home/cpython", 512 _is_python_build=1, 513 module_search_paths_set=1, 514 module_search_paths=[ 515 "/usr/local/lib/python98.zip", 516 "/home/cpython/Lib", 517 "/home/cpython/build/lib.macos-9.8", 518 ], 519 ) 520 actual = getpath(ns, expected) 521 self.assertEqual(expected, actual) 522 523 524# ****************************************************************************** 525 526DEFAULT_NAMESPACE = dict( 527 PREFIX="", 528 EXEC_PREFIX="", 529 PYTHONPATH="", 530 VPATH="", 531 PLATLIBDIR="", 532 PYDEBUGEXT="", 533 VERSION_MAJOR=9, # fixed version number for ease 534 VERSION_MINOR=8, # of testing 535 PYWINVER=None, 536 EXE_SUFFIX=None, 537 538 ENV_PATH="", 539 ENV_PYTHONHOME="", 540 ENV_PYTHONEXECUTABLE="", 541 ENV___PYVENV_LAUNCHER__="", 542 argv0="", 543 py_setpath="", 544 real_executable="", 545 executable_dir="", 546 library="", 547 winreg=None, 548 build_prefix=None, 549 venv_prefix=None, 550) 551 552DEFAULT_CONFIG = dict( 553 home=None, 554 platlibdir=None, 555 pythonpath=None, 556 program_name=None, 557 prefix=None, 558 exec_prefix=None, 559 base_prefix=None, 560 base_exec_prefix=None, 561 executable=None, 562 base_executable="", 563 stdlib_dir=None, 564 platstdlib_dir=None, 565 module_search_paths=None, 566 module_search_paths_set=0, 567 pythonpath_env=None, 568 argv=None, 569 orig_argv=None, 570 571 isolated=0, 572 use_environment=1, 573 use_site=1, 574) 575 576class MockNTNamespace(dict): 577 def __init__(self, *a, argv0=None, config=None, **kw): 578 self.update(DEFAULT_NAMESPACE) 579 self["config"] = DEFAULT_CONFIG.copy() 580 self["os_name"] = "nt" 581 self["PLATLIBDIR"] = "DLLs" 582 self["PYWINVER"] = "9.8-XY" 583 self["VPATH"] = r"..\.." 584 super().__init__(*a, **kw) 585 if argv0: 586 self["config"]["orig_argv"] = [argv0] 587 if config: 588 self["config"].update(config) 589 self._files = {} 590 self._links = {} 591 self._dirs = set() 592 self._warnings = [] 593 594 def add_known_file(self, path, lines=None): 595 self._files[path.casefold()] = list(lines or ()) 596 self.add_known_dir(path.rpartition("\\")[0]) 597 598 def add_known_xfile(self, path): 599 self.add_known_file(path) 600 601 def add_known_link(self, path, target): 602 self._links[path.casefold()] = target 603 604 def add_known_dir(self, path): 605 p = path.rstrip("\\").casefold() 606 while p: 607 self._dirs.add(p) 608 p = p.rpartition("\\")[0] 609 610 def __missing__(self, key): 611 try: 612 return getattr(self, key) 613 except AttributeError: 614 raise KeyError(key) from None 615 616 def abspath(self, path): 617 if self.isabs(path): 618 return path 619 return self.joinpath("C:\\Absolute", path) 620 621 def basename(self, path): 622 return path.rpartition("\\")[2] 623 624 def dirname(self, path): 625 name = path.rstrip("\\").rpartition("\\")[0] 626 if name[1:] == ":": 627 return name + "\\" 628 return name 629 630 def hassuffix(self, path, suffix): 631 return path.casefold().endswith(suffix.casefold()) 632 633 def isabs(self, path): 634 return path[1:3] == ":\\" 635 636 def isdir(self, path): 637 if verbose: 638 print("Check if", path, "is a dir") 639 return path.casefold() in self._dirs 640 641 def isfile(self, path): 642 if verbose: 643 print("Check if", path, "is a file") 644 return path.casefold() in self._files 645 646 def ismodule(self, path): 647 if verbose: 648 print("Check if", path, "is a module") 649 path = path.casefold() 650 return path in self._files and path.rpartition(".")[2] == "py".casefold() 651 652 def isxfile(self, path): 653 if verbose: 654 print("Check if", path, "is a executable") 655 path = path.casefold() 656 return path in self._files and path.rpartition(".")[2] == "exe".casefold() 657 658 def joinpath(self, *path): 659 return ntpath.normpath(ntpath.join(*path)) 660 661 def readlines(self, path): 662 try: 663 return self._files[path.casefold()] 664 except KeyError: 665 raise FileNotFoundError(path) from None 666 667 def realpath(self, path, _trail=None): 668 if verbose: 669 print("Read link from", path) 670 try: 671 link = self._links[path.casefold()] 672 except KeyError: 673 return path 674 if _trail is None: 675 _trail = set() 676 elif link.casefold() in _trail: 677 raise OSError("circular link") 678 _trail.add(link.casefold()) 679 return self.realpath(link, _trail) 680 681 def warn(self, message): 682 self._warnings.append(message) 683 if verbose: 684 print(message) 685 686 687class MockWinreg: 688 HKEY_LOCAL_MACHINE = "HKLM" 689 HKEY_CURRENT_USER = "HKCU" 690 691 def __init__(self, keys): 692 self.keys = {k.casefold(): v for k, v in keys.items()} 693 self.open = {} 694 695 def __repr__(self): 696 return "<MockWinreg>" 697 698 def __eq__(self, other): 699 return isinstance(other, type(self)) 700 701 def open_keys(self): 702 return list(self.open) 703 704 def OpenKeyEx(self, hkey, subkey): 705 if verbose: 706 print(f"OpenKeyEx({hkey}, {subkey})") 707 key = f"{hkey}\\{subkey}".casefold() 708 if key in self.keys: 709 self.open[key] = self.open.get(key, 0) + 1 710 return key 711 raise FileNotFoundError() 712 713 def CloseKey(self, hkey): 714 if verbose: 715 print(f"CloseKey({hkey})") 716 hkey = hkey.casefold() 717 if hkey not in self.open: 718 raise RuntimeError("key is not open") 719 self.open[hkey] -= 1 720 if not self.open[hkey]: 721 del self.open[hkey] 722 723 def EnumKey(self, hkey, i): 724 if verbose: 725 print(f"EnumKey({hkey}, {i})") 726 hkey = hkey.casefold() 727 if hkey not in self.open: 728 raise RuntimeError("key is not open") 729 prefix = f'{hkey}\\' 730 subkeys = [k[len(prefix):] for k in sorted(self.keys) if k.startswith(prefix)] 731 subkeys[:] = [k for k in subkeys if '\\' not in k] 732 for j, n in enumerate(subkeys): 733 if j == i: 734 return n.removeprefix(prefix) 735 raise OSError("end of enumeration") 736 737 def QueryValue(self, hkey): 738 if verbose: 739 print(f"QueryValue({hkey})") 740 hkey = hkey.casefold() 741 if hkey not in self.open: 742 raise RuntimeError("key is not open") 743 try: 744 return self.keys[hkey] 745 except KeyError: 746 raise OSError() 747 748 749class MockPosixNamespace(dict): 750 def __init__(self, *a, argv0=None, config=None, **kw): 751 self.update(DEFAULT_NAMESPACE) 752 self["config"] = DEFAULT_CONFIG.copy() 753 self["os_name"] = "posix" 754 self["PLATLIBDIR"] = "lib" 755 super().__init__(*a, **kw) 756 if argv0: 757 self["config"]["orig_argv"] = [argv0] 758 if config: 759 self["config"].update(config) 760 self._files = {} 761 self._xfiles = set() 762 self._links = {} 763 self._dirs = set() 764 self._warnings = [] 765 766 def add_known_file(self, path, lines=None): 767 self._files[path] = list(lines or ()) 768 self.add_known_dir(path.rpartition("/")[0]) 769 770 def add_known_xfile(self, path): 771 self.add_known_file(path) 772 self._xfiles.add(path) 773 774 def add_known_link(self, path, target): 775 self._links[path] = target 776 777 def add_known_dir(self, path): 778 p = path.rstrip("/") 779 while p: 780 self._dirs.add(p) 781 p = p.rpartition("/")[0] 782 783 def __missing__(self, key): 784 try: 785 return getattr(self, key) 786 except AttributeError: 787 raise KeyError(key) from None 788 789 def abspath(self, path): 790 if self.isabs(path): 791 return path 792 return self.joinpath("/Absolute", path) 793 794 def basename(self, path): 795 return path.rpartition("/")[2] 796 797 def dirname(self, path): 798 return path.rstrip("/").rpartition("/")[0] 799 800 def hassuffix(self, path, suffix): 801 return path.endswith(suffix) 802 803 def isabs(self, path): 804 return path[0:1] == "/" 805 806 def isdir(self, path): 807 if verbose: 808 print("Check if", path, "is a dir") 809 return path in self._dirs 810 811 def isfile(self, path): 812 if verbose: 813 print("Check if", path, "is a file") 814 return path in self._files 815 816 def ismodule(self, path): 817 if verbose: 818 print("Check if", path, "is a module") 819 return path in self._files and path.rpartition(".")[2] == "py" 820 821 def isxfile(self, path): 822 if verbose: 823 print("Check if", path, "is an xfile") 824 return path in self._xfiles 825 826 def joinpath(self, *path): 827 return posixpath.normpath(posixpath.join(*path)) 828 829 def readlines(self, path): 830 try: 831 return self._files[path] 832 except KeyError: 833 raise FileNotFoundError(path) from None 834 835 def realpath(self, path, _trail=None): 836 if verbose: 837 print("Read link from", path) 838 try: 839 link = self._links[path] 840 except KeyError: 841 return path 842 if _trail is None: 843 _trail = set() 844 elif link in _trail: 845 raise OSError("circular link") 846 _trail.add(link) 847 return self.realpath(link, _trail) 848 849 def warn(self, message): 850 self._warnings.append(message) 851 if verbose: 852 print(message) 853 854 855def diff_dict(before, after, prefix="global"): 856 diff = [] 857 for k in sorted(before): 858 if k[:2] == "__": 859 continue 860 if k == "config": 861 diff_dict(before[k], after[k], prefix="config") 862 continue 863 if k in after and after[k] != before[k]: 864 diff.append((k, before[k], after[k])) 865 if not diff: 866 return 867 max_k = max(len(k) for k, _, _ in diff) 868 indent = " " * (len(prefix) + 1 + max_k) 869 if verbose: 870 for k, b, a in diff: 871 if b: 872 print("{}.{} -{!r}\n{} +{!r}".format(prefix, k.ljust(max_k), b, indent, a)) 873 else: 874 print("{}.{} +{!r}".format(prefix, k.ljust(max_k), a)) 875 876 877def dump_dict(before, after, prefix="global"): 878 if not verbose or not after: 879 return 880 max_k = max(len(k) for k in after) 881 for k, v in sorted(after.items(), key=lambda i: i[0]): 882 if k[:2] == "__": 883 continue 884 if k == "config": 885 dump_dict(before[k], after[k], prefix="config") 886 continue 887 try: 888 if v != before[k]: 889 print("{}.{} {!r} (was {!r})".format(prefix, k.ljust(max_k), v, before[k])) 890 continue 891 except KeyError: 892 pass 893 print("{}.{} {!r}".format(prefix, k.ljust(max_k), v)) 894 895 896def getpath(ns, keys): 897 before = copy.deepcopy(ns) 898 failed = True 899 try: 900 exec(SOURCE, ns) 901 failed = False 902 finally: 903 if failed: 904 dump_dict(before, ns) 905 else: 906 diff_dict(before, ns) 907 return { 908 k: ns['config'].get(k, ns.get(k, ...)) 909 for k in keys 910 } 911