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