1""" 2Collect various information about Python to help debugging test failures. 3""" 4import errno 5import re 6import sys 7import traceback 8import warnings 9 10 11def normalize_text(text): 12 if text is None: 13 return None 14 text = str(text) 15 text = re.sub(r'\s+', ' ', text) 16 return text.strip() 17 18 19class PythonInfo: 20 def __init__(self): 21 self.info = {} 22 23 def add(self, key, value): 24 if key in self.info: 25 raise ValueError("duplicate key: %r" % key) 26 27 if value is None: 28 return 29 30 if not isinstance(value, int): 31 if not isinstance(value, str): 32 # convert other objects like sys.flags to string 33 value = str(value) 34 35 value = value.strip() 36 if not value: 37 return 38 39 self.info[key] = value 40 41 def get_infos(self): 42 """ 43 Get information as a key:value dictionary where values are strings. 44 """ 45 return {key: str(value) for key, value in self.info.items()} 46 47 48def copy_attributes(info_add, obj, name_fmt, attributes, *, formatter=None): 49 for attr in attributes: 50 value = getattr(obj, attr, None) 51 if value is None: 52 continue 53 name = name_fmt % attr 54 if formatter is not None: 55 value = formatter(attr, value) 56 info_add(name, value) 57 58 59def copy_attr(info_add, name, mod, attr_name): 60 try: 61 value = getattr(mod, attr_name) 62 except AttributeError: 63 return 64 info_add(name, value) 65 66 67def call_func(info_add, name, mod, func_name, *, formatter=None): 68 try: 69 func = getattr(mod, func_name) 70 except AttributeError: 71 return 72 value = func() 73 if formatter is not None: 74 value = formatter(value) 75 info_add(name, value) 76 77 78def collect_sys(info_add): 79 attributes = ( 80 '_emscripten_info', 81 '_framework', 82 'abiflags', 83 'api_version', 84 'builtin_module_names', 85 'byteorder', 86 'dont_write_bytecode', 87 'executable', 88 'flags', 89 'float_info', 90 'float_repr_style', 91 'hash_info', 92 'hexversion', 93 'implementation', 94 'int_info', 95 'maxsize', 96 'maxunicode', 97 'path', 98 'platform', 99 'platlibdir', 100 'prefix', 101 'thread_info', 102 'version', 103 'version_info', 104 'winver', 105 ) 106 copy_attributes(info_add, sys, 'sys.%s', attributes) 107 108 for func in ( 109 '_is_gil_enabled', 110 'getandroidapilevel', 111 'getrecursionlimit', 112 'getwindowsversion', 113 ): 114 call_func(info_add, f'sys.{func}', sys, func) 115 116 encoding = sys.getfilesystemencoding() 117 if hasattr(sys, 'getfilesystemencodeerrors'): 118 encoding = '%s/%s' % (encoding, sys.getfilesystemencodeerrors()) 119 info_add('sys.filesystem_encoding', encoding) 120 121 for name in ('stdin', 'stdout', 'stderr'): 122 stream = getattr(sys, name) 123 if stream is None: 124 continue 125 encoding = getattr(stream, 'encoding', None) 126 if not encoding: 127 continue 128 errors = getattr(stream, 'errors', None) 129 if errors: 130 encoding = '%s/%s' % (encoding, errors) 131 info_add('sys.%s.encoding' % name, encoding) 132 133 # Were we compiled --with-pydebug? 134 Py_DEBUG = hasattr(sys, 'gettotalrefcount') 135 if Py_DEBUG: 136 text = 'Yes (sys.gettotalrefcount() present)' 137 else: 138 text = 'No (sys.gettotalrefcount() missing)' 139 info_add('build.Py_DEBUG', text) 140 141 # Were we compiled --with-trace-refs? 142 Py_TRACE_REFS = hasattr(sys, 'getobjects') 143 if Py_TRACE_REFS: 144 text = 'Yes (sys.getobjects() present)' 145 else: 146 text = 'No (sys.getobjects() missing)' 147 info_add('build.Py_TRACE_REFS', text) 148 149 150def collect_platform(info_add): 151 import platform 152 153 arch = platform.architecture() 154 arch = ' '.join(filter(bool, arch)) 155 info_add('platform.architecture', arch) 156 157 info_add('platform.python_implementation', 158 platform.python_implementation()) 159 info_add('platform.platform', 160 platform.platform(aliased=True)) 161 162 libc_ver = ('%s %s' % platform.libc_ver()).strip() 163 if libc_ver: 164 info_add('platform.libc_ver', libc_ver) 165 166 try: 167 os_release = platform.freedesktop_os_release() 168 except OSError: 169 pass 170 else: 171 for key in ( 172 'ID', 173 'NAME', 174 'PRETTY_NAME' 175 'VARIANT', 176 'VARIANT_ID', 177 'VERSION', 178 'VERSION_CODENAME', 179 'VERSION_ID', 180 ): 181 if key not in os_release: 182 continue 183 info_add(f'platform.freedesktop_os_release[{key}]', 184 os_release[key]) 185 186 if sys.platform == 'android': 187 call_func(info_add, 'platform.android_ver', platform, 'android_ver') 188 189 190def collect_locale(info_add): 191 import locale 192 193 info_add('locale.getencoding', locale.getencoding()) 194 195 196def collect_builtins(info_add): 197 info_add('builtins.float.float_format', float.__getformat__("float")) 198 info_add('builtins.float.double_format', float.__getformat__("double")) 199 200 201def collect_urandom(info_add): 202 import os 203 204 if hasattr(os, 'getrandom'): 205 # PEP 524: Check if system urandom is initialized 206 try: 207 try: 208 os.getrandom(1, os.GRND_NONBLOCK) 209 state = 'ready (initialized)' 210 except BlockingIOError as exc: 211 state = 'not seeded yet (%s)' % exc 212 info_add('os.getrandom', state) 213 except OSError as exc: 214 # Python was compiled on a more recent Linux version 215 # than the current Linux kernel: ignore OSError(ENOSYS) 216 if exc.errno != errno.ENOSYS: 217 raise 218 219 220def collect_os(info_add): 221 import os 222 223 def format_attr(attr, value): 224 if attr in ('supports_follow_symlinks', 'supports_fd', 225 'supports_effective_ids'): 226 return str(sorted(func.__name__ for func in value)) 227 else: 228 return value 229 230 attributes = ( 231 'name', 232 'supports_bytes_environ', 233 'supports_effective_ids', 234 'supports_fd', 235 'supports_follow_symlinks', 236 ) 237 copy_attributes(info_add, os, 'os.%s', attributes, formatter=format_attr) 238 239 for func in ( 240 'cpu_count', 241 'getcwd', 242 'getegid', 243 'geteuid', 244 'getgid', 245 'getloadavg', 246 'getresgid', 247 'getresuid', 248 'getuid', 249 'process_cpu_count', 250 'uname', 251 ): 252 call_func(info_add, 'os.%s' % func, os, func) 253 254 def format_groups(groups): 255 return ', '.join(map(str, groups)) 256 257 call_func(info_add, 'os.getgroups', os, 'getgroups', formatter=format_groups) 258 259 if hasattr(os, 'getlogin'): 260 try: 261 login = os.getlogin() 262 except OSError: 263 # getlogin() fails with "OSError: [Errno 25] Inappropriate ioctl 264 # for device" on Travis CI 265 pass 266 else: 267 info_add("os.login", login) 268 269 # Environment variables used by the stdlib and tests. Don't log the full 270 # environment: filter to list to not leak sensitive information. 271 # 272 # HTTP_PROXY is not logged because it can contain a password. 273 ENV_VARS = frozenset(( 274 "APPDATA", 275 "AR", 276 "ARCHFLAGS", 277 "ARFLAGS", 278 "AUDIODEV", 279 "BUILDPYTHON", 280 "CC", 281 "CFLAGS", 282 "COLUMNS", 283 "COMPUTERNAME", 284 "COMSPEC", 285 "CPP", 286 "CPPFLAGS", 287 "DISPLAY", 288 "DISTUTILS_DEBUG", 289 "DISTUTILS_USE_SDK", 290 "DYLD_LIBRARY_PATH", 291 "ENSUREPIP_OPTIONS", 292 "HISTORY_FILE", 293 "HOME", 294 "HOMEDRIVE", 295 "HOMEPATH", 296 "IDLESTARTUP", 297 "IPHONEOS_DEPLOYMENT_TARGET", 298 "LANG", 299 "LDFLAGS", 300 "LDSHARED", 301 "LD_LIBRARY_PATH", 302 "LINES", 303 "MACOSX_DEPLOYMENT_TARGET", 304 "MAILCAPS", 305 "MAKEFLAGS", 306 "MIXERDEV", 307 "MSSDK", 308 "PATH", 309 "PATHEXT", 310 "PIP_CONFIG_FILE", 311 "PLAT", 312 "POSIXLY_CORRECT", 313 "PY_SAX_PARSER", 314 "ProgramFiles", 315 "ProgramFiles(x86)", 316 "RUNNING_ON_VALGRIND", 317 "SDK_TOOLS_BIN", 318 "SERVER_SOFTWARE", 319 "SHELL", 320 "SOURCE_DATE_EPOCH", 321 "SYSTEMROOT", 322 "TEMP", 323 "TERM", 324 "TILE_LIBRARY", 325 "TMP", 326 "TMPDIR", 327 "TRAVIS", 328 "TZ", 329 "USERPROFILE", 330 "VIRTUAL_ENV", 331 "WAYLAND_DISPLAY", 332 "WINDIR", 333 "_PYTHON_HOSTRUNNER", 334 "_PYTHON_HOST_PLATFORM", 335 "_PYTHON_PROJECT_BASE", 336 "_PYTHON_SYSCONFIGDATA_NAME", 337 "_PYTHON_SYSCONFIGDATA_PATH", 338 "__PYVENV_LAUNCHER__", 339 340 # Sanitizer options 341 "ASAN_OPTIONS", 342 "LSAN_OPTIONS", 343 "MSAN_OPTIONS", 344 "TSAN_OPTIONS", 345 "UBSAN_OPTIONS", 346 )) 347 for name, value in os.environ.items(): 348 uname = name.upper() 349 if (uname in ENV_VARS 350 # Copy PYTHON* variables like PYTHONPATH 351 # Copy LC_* variables like LC_ALL 352 or uname.startswith(("PYTHON", "LC_")) 353 # Visual Studio: VS140COMNTOOLS 354 or (uname.startswith("VS") and uname.endswith("COMNTOOLS"))): 355 info_add('os.environ[%s]' % name, value) 356 357 if hasattr(os, 'umask'): 358 mask = os.umask(0) 359 os.umask(mask) 360 info_add("os.umask", '0o%03o' % mask) 361 362 363def collect_pwd(info_add): 364 try: 365 import pwd 366 except ImportError: 367 return 368 import os 369 370 uid = os.getuid() 371 try: 372 entry = pwd.getpwuid(uid) 373 except KeyError: 374 entry = None 375 376 info_add('pwd.getpwuid(%s)'% uid, 377 entry if entry is not None else '<KeyError>') 378 379 if entry is None: 380 # there is nothing interesting to read if the current user identifier 381 # is not the password database 382 return 383 384 if hasattr(os, 'getgrouplist'): 385 groups = os.getgrouplist(entry.pw_name, entry.pw_gid) 386 groups = ', '.join(map(str, groups)) 387 info_add('os.getgrouplist', groups) 388 389 390def collect_readline(info_add): 391 try: 392 import readline 393 except ImportError: 394 return 395 396 def format_attr(attr, value): 397 if isinstance(value, int): 398 return "%#x" % value 399 else: 400 return value 401 402 attributes = ( 403 "_READLINE_VERSION", 404 "_READLINE_RUNTIME_VERSION", 405 "_READLINE_LIBRARY_VERSION", 406 ) 407 copy_attributes(info_add, readline, 'readline.%s', attributes, 408 formatter=format_attr) 409 410 if not hasattr(readline, "_READLINE_LIBRARY_VERSION"): 411 # _READLINE_LIBRARY_VERSION has been added to CPython 3.7 412 doc = getattr(readline, '__doc__', '') 413 if 'libedit readline' in doc: 414 info_add('readline.library', 'libedit readline') 415 elif 'GNU readline' in doc: 416 info_add('readline.library', 'GNU readline') 417 418 419def collect_gdb(info_add): 420 import subprocess 421 422 try: 423 proc = subprocess.Popen(["gdb", "-nx", "--version"], 424 stdout=subprocess.PIPE, 425 stderr=subprocess.PIPE, 426 universal_newlines=True) 427 version = proc.communicate()[0] 428 if proc.returncode: 429 # ignore gdb failure: test_gdb will log the error 430 return 431 except OSError: 432 return 433 434 # Only keep the first line 435 version = version.splitlines()[0] 436 info_add('gdb_version', version) 437 438 439def collect_tkinter(info_add): 440 try: 441 import _tkinter 442 except ImportError: 443 pass 444 else: 445 attributes = ('TK_VERSION', 'TCL_VERSION') 446 copy_attributes(info_add, _tkinter, 'tkinter.%s', attributes) 447 448 try: 449 import tkinter 450 except ImportError: 451 pass 452 else: 453 tcl = tkinter.Tcl() 454 patchlevel = tcl.call('info', 'patchlevel') 455 info_add('tkinter.info_patchlevel', patchlevel) 456 457 458def collect_time(info_add): 459 import time 460 461 info_add('time.time', time.time()) 462 463 attributes = ( 464 'altzone', 465 'daylight', 466 'timezone', 467 'tzname', 468 ) 469 copy_attributes(info_add, time, 'time.%s', attributes) 470 471 if hasattr(time, 'get_clock_info'): 472 for clock in ('clock', 'monotonic', 'perf_counter', 473 'process_time', 'thread_time', 'time'): 474 try: 475 # prevent DeprecatingWarning on get_clock_info('clock') 476 with warnings.catch_warnings(record=True): 477 clock_info = time.get_clock_info(clock) 478 except ValueError: 479 # missing clock like time.thread_time() 480 pass 481 else: 482 info_add('time.get_clock_info(%s)' % clock, clock_info) 483 484 485def collect_curses(info_add): 486 try: 487 import curses 488 except ImportError: 489 return 490 491 copy_attr(info_add, 'curses.ncurses_version', curses, 'ncurses_version') 492 493 494def collect_datetime(info_add): 495 try: 496 import datetime 497 except ImportError: 498 return 499 500 info_add('datetime.datetime.now', datetime.datetime.now()) 501 502 503def collect_sysconfig(info_add): 504 import sysconfig 505 506 info_add('sysconfig.is_python_build', sysconfig.is_python_build()) 507 508 for name in ( 509 'ABIFLAGS', 510 'ANDROID_API_LEVEL', 511 'CC', 512 'CCSHARED', 513 'CFLAGS', 514 'CFLAGSFORSHARED', 515 'CONFIG_ARGS', 516 'HOSTRUNNER', 517 'HOST_GNU_TYPE', 518 'MACHDEP', 519 'MULTIARCH', 520 'OPT', 521 'PGO_PROF_USE_FLAG', 522 'PY_CFLAGS', 523 'PY_CFLAGS_NODIST', 524 'PY_CORE_LDFLAGS', 525 'PY_LDFLAGS', 526 'PY_LDFLAGS_NODIST', 527 'PY_STDMODULE_CFLAGS', 528 'Py_DEBUG', 529 'Py_ENABLE_SHARED', 530 'Py_GIL_DISABLED', 531 'SHELL', 532 'SOABI', 533 'TEST_MODULES', 534 'abs_builddir', 535 'abs_srcdir', 536 'prefix', 537 'srcdir', 538 ): 539 value = sysconfig.get_config_var(name) 540 if name == 'ANDROID_API_LEVEL' and not value: 541 # skip ANDROID_API_LEVEL=0 542 continue 543 value = normalize_text(value) 544 info_add('sysconfig[%s]' % name, value) 545 546 PY_CFLAGS = sysconfig.get_config_var('PY_CFLAGS') 547 NDEBUG = (PY_CFLAGS and '-DNDEBUG' in PY_CFLAGS) 548 if NDEBUG: 549 text = 'ignore assertions (macro defined)' 550 else: 551 text= 'build assertions (macro not defined)' 552 info_add('build.NDEBUG',text) 553 554 for name in ( 555 'WITH_DOC_STRINGS', 556 'WITH_DTRACE', 557 'WITH_FREELISTS', 558 'WITH_MIMALLOC', 559 'WITH_PYMALLOC', 560 'WITH_VALGRIND', 561 ): 562 value = sysconfig.get_config_var(name) 563 if value: 564 text = 'Yes' 565 else: 566 text = 'No' 567 info_add(f'build.{name}', text) 568 569 570def collect_ssl(info_add): 571 import os 572 try: 573 import ssl 574 except ImportError: 575 return 576 try: 577 import _ssl 578 except ImportError: 579 _ssl = None 580 581 def format_attr(attr, value): 582 if attr.startswith('OP_'): 583 return '%#8x' % value 584 else: 585 return value 586 587 attributes = ( 588 'OPENSSL_VERSION', 589 'OPENSSL_VERSION_INFO', 590 'HAS_SNI', 591 'OP_ALL', 592 'OP_NO_TLSv1_1', 593 ) 594 copy_attributes(info_add, ssl, 'ssl.%s', attributes, formatter=format_attr) 595 596 for name, ctx in ( 597 ('SSLContext', ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)), 598 ('default_https_context', ssl._create_default_https_context()), 599 ('stdlib_context', ssl._create_stdlib_context()), 600 ): 601 attributes = ( 602 'minimum_version', 603 'maximum_version', 604 'protocol', 605 'options', 606 'verify_mode', 607 ) 608 copy_attributes(info_add, ctx, f'ssl.{name}.%s', attributes) 609 610 env_names = ["OPENSSL_CONF", "SSLKEYLOGFILE"] 611 if _ssl is not None and hasattr(_ssl, 'get_default_verify_paths'): 612 parts = _ssl.get_default_verify_paths() 613 env_names.extend((parts[0], parts[2])) 614 615 for name in env_names: 616 try: 617 value = os.environ[name] 618 except KeyError: 619 continue 620 info_add('ssl.environ[%s]' % name, value) 621 622 623def collect_socket(info_add): 624 try: 625 import socket 626 except ImportError: 627 return 628 629 try: 630 hostname = socket.gethostname() 631 except (OSError, AttributeError): 632 # WASI SDK 16.0 does not have gethostname(2). 633 if sys.platform != "wasi": 634 raise 635 else: 636 info_add('socket.hostname', hostname) 637 638 639def collect_sqlite(info_add): 640 try: 641 import sqlite3 642 except ImportError: 643 return 644 645 attributes = ('sqlite_version',) 646 copy_attributes(info_add, sqlite3, 'sqlite3.%s', attributes) 647 648 649def collect_zlib(info_add): 650 try: 651 import zlib 652 except ImportError: 653 return 654 655 attributes = ('ZLIB_VERSION', 'ZLIB_RUNTIME_VERSION') 656 copy_attributes(info_add, zlib, 'zlib.%s', attributes) 657 658 659def collect_expat(info_add): 660 try: 661 from xml.parsers import expat 662 except ImportError: 663 return 664 665 attributes = ('EXPAT_VERSION',) 666 copy_attributes(info_add, expat, 'expat.%s', attributes) 667 668 669def collect_decimal(info_add): 670 try: 671 import _decimal 672 except ImportError: 673 return 674 675 attributes = ('__libmpdec_version__',) 676 copy_attributes(info_add, _decimal, '_decimal.%s', attributes) 677 678 679def collect_testcapi(info_add): 680 try: 681 import _testcapi 682 except ImportError: 683 return 684 685 for name in ( 686 'LONG_MAX', # always 32-bit on Windows, 64-bit on 64-bit Unix 687 'PY_SSIZE_T_MAX', 688 'Py_C_RECURSION_LIMIT', 689 'SIZEOF_TIME_T', # 32-bit or 64-bit depending on the platform 690 'SIZEOF_WCHAR_T', # 16-bit or 32-bit depending on the platform 691 ): 692 copy_attr(info_add, f'_testcapi.{name}', _testcapi, name) 693 694 695def collect_testinternalcapi(info_add): 696 try: 697 import _testinternalcapi 698 except ImportError: 699 return 700 701 call_func(info_add, 'pymem.allocator', _testinternalcapi, 'pymem_getallocatorsname') 702 703 for name in ( 704 'SIZEOF_PYGC_HEAD', 705 'SIZEOF_PYOBJECT', 706 ): 707 copy_attr(info_add, f'_testinternalcapi.{name}', _testinternalcapi, name) 708 709 710def collect_resource(info_add): 711 try: 712 import resource 713 except ImportError: 714 return 715 716 limits = [attr for attr in dir(resource) if attr.startswith('RLIMIT_')] 717 for name in limits: 718 key = getattr(resource, name) 719 value = resource.getrlimit(key) 720 info_add('resource.%s' % name, value) 721 722 call_func(info_add, 'resource.pagesize', resource, 'getpagesize') 723 724 725def collect_test_socket(info_add): 726 import unittest 727 try: 728 from test import test_socket 729 except (ImportError, unittest.SkipTest): 730 return 731 732 # all check attributes like HAVE_SOCKET_CAN 733 attributes = [name for name in dir(test_socket) 734 if name.startswith('HAVE_')] 735 copy_attributes(info_add, test_socket, 'test_socket.%s', attributes) 736 737 738def collect_support(info_add): 739 try: 740 from test import support 741 except ImportError: 742 return 743 744 attributes = ( 745 'MS_WINDOWS', 746 'has_fork_support', 747 'has_socket_support', 748 'has_strftime_extensions', 749 'has_subprocess_support', 750 'is_android', 751 'is_emscripten', 752 'is_jython', 753 'is_wasi', 754 ) 755 copy_attributes(info_add, support, 'support.%s', attributes) 756 757 call_func(info_add, 'support._is_gui_available', support, '_is_gui_available') 758 call_func(info_add, 'support.python_is_optimized', support, 'python_is_optimized') 759 760 info_add('support.check_sanitizer(address=True)', 761 support.check_sanitizer(address=True)) 762 info_add('support.check_sanitizer(memory=True)', 763 support.check_sanitizer(memory=True)) 764 info_add('support.check_sanitizer(ub=True)', 765 support.check_sanitizer(ub=True)) 766 767 768def collect_support_os_helper(info_add): 769 try: 770 from test.support import os_helper 771 except ImportError: 772 return 773 774 for name in ( 775 'can_symlink', 776 'can_xattr', 777 'can_chmod', 778 'can_dac_override', 779 ): 780 func = getattr(os_helper, name) 781 info_add(f'support_os_helper.{name}', func()) 782 783 784def collect_support_socket_helper(info_add): 785 try: 786 from test.support import socket_helper 787 except ImportError: 788 return 789 790 attributes = ( 791 'IPV6_ENABLED', 792 'has_gethostname', 793 ) 794 copy_attributes(info_add, socket_helper, 'support_socket_helper.%s', attributes) 795 796 for name in ( 797 'tcp_blackhole', 798 ): 799 func = getattr(socket_helper, name) 800 info_add(f'support_socket_helper.{name}', func()) 801 802 803def collect_support_threading_helper(info_add): 804 try: 805 from test.support import threading_helper 806 except ImportError: 807 return 808 809 attributes = ( 810 'can_start_thread', 811 ) 812 copy_attributes(info_add, threading_helper, 'support_threading_helper.%s', attributes) 813 814 815def collect_cc(info_add): 816 import subprocess 817 import sysconfig 818 819 CC = sysconfig.get_config_var('CC') 820 if not CC: 821 return 822 823 try: 824 import shlex 825 args = shlex.split(CC) 826 except ImportError: 827 args = CC.split() 828 args.append('--version') 829 try: 830 proc = subprocess.Popen(args, 831 stdout=subprocess.PIPE, 832 stderr=subprocess.STDOUT, 833 universal_newlines=True) 834 except OSError: 835 # Cannot run the compiler, for example when Python has been 836 # cross-compiled and installed on the target platform where the 837 # compiler is missing. 838 return 839 840 stdout = proc.communicate()[0] 841 if proc.returncode: 842 # CC --version failed: ignore error 843 return 844 845 text = stdout.splitlines()[0] 846 text = normalize_text(text) 847 info_add('CC.version', text) 848 849 850def collect_gdbm(info_add): 851 try: 852 from _gdbm import _GDBM_VERSION 853 except ImportError: 854 return 855 856 info_add('gdbm.GDBM_VERSION', '.'.join(map(str, _GDBM_VERSION))) 857 858 859def collect_get_config(info_add): 860 # Get global configuration variables, _PyPreConfig and _PyCoreConfig 861 try: 862 from _testinternalcapi import get_configs 863 except ImportError: 864 return 865 866 all_configs = get_configs() 867 for config_type in sorted(all_configs): 868 config = all_configs[config_type] 869 for key in sorted(config): 870 info_add('%s[%s]' % (config_type, key), repr(config[key])) 871 872 873def collect_subprocess(info_add): 874 import subprocess 875 copy_attributes(info_add, subprocess, 'subprocess.%s', ('_USE_POSIX_SPAWN',)) 876 877 878def collect_windows(info_add): 879 if sys.platform != "win32": 880 # Code specific to Windows 881 return 882 883 # windows.RtlAreLongPathsEnabled: RtlAreLongPathsEnabled() 884 # windows.is_admin: IsUserAnAdmin() 885 try: 886 import ctypes 887 if not hasattr(ctypes, 'WinDLL'): 888 raise ImportError 889 except ImportError: 890 pass 891 else: 892 ntdll = ctypes.WinDLL('ntdll') 893 BOOLEAN = ctypes.c_ubyte 894 try: 895 RtlAreLongPathsEnabled = ntdll.RtlAreLongPathsEnabled 896 except AttributeError: 897 res = '<function not available>' 898 else: 899 RtlAreLongPathsEnabled.restype = BOOLEAN 900 RtlAreLongPathsEnabled.argtypes = () 901 res = bool(RtlAreLongPathsEnabled()) 902 info_add('windows.RtlAreLongPathsEnabled', res) 903 904 shell32 = ctypes.windll.shell32 905 IsUserAnAdmin = shell32.IsUserAnAdmin 906 IsUserAnAdmin.restype = BOOLEAN 907 IsUserAnAdmin.argtypes = () 908 info_add('windows.is_admin', IsUserAnAdmin()) 909 910 try: 911 import _winapi 912 dll_path = _winapi.GetModuleFileName(sys.dllhandle) 913 info_add('windows.dll_path', dll_path) 914 except (ImportError, AttributeError): 915 pass 916 917 # windows.version_caption: "wmic os get Caption,Version /value" command 918 import subprocess 919 try: 920 # When wmic.exe output is redirected to a pipe, 921 # it uses the OEM code page 922 proc = subprocess.Popen(["wmic", "os", "get", "Caption,Version", "/value"], 923 stdout=subprocess.PIPE, 924 stderr=subprocess.PIPE, 925 encoding="oem", 926 text=True) 927 output, stderr = proc.communicate() 928 if proc.returncode: 929 output = "" 930 except OSError: 931 pass 932 else: 933 for line in output.splitlines(): 934 line = line.strip() 935 if line.startswith('Caption='): 936 line = line.removeprefix('Caption=').strip() 937 if line: 938 info_add('windows.version_caption', line) 939 elif line.startswith('Version='): 940 line = line.removeprefix('Version=').strip() 941 if line: 942 info_add('windows.version', line) 943 944 # windows.ver: "ver" command 945 try: 946 proc = subprocess.Popen(["ver"], shell=True, 947 stdout=subprocess.PIPE, 948 stderr=subprocess.PIPE, 949 text=True) 950 output = proc.communicate()[0] 951 if proc.returncode == 0xc0000142: 952 return 953 if proc.returncode: 954 output = "" 955 except OSError: 956 return 957 else: 958 output = output.strip() 959 line = output.splitlines()[0] 960 if line: 961 info_add('windows.ver', line) 962 963 # windows.developer_mode: get AllowDevelopmentWithoutDevLicense registry 964 import winreg 965 try: 966 key = winreg.OpenKey( 967 winreg.HKEY_LOCAL_MACHINE, 968 r"SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock") 969 subkey = "AllowDevelopmentWithoutDevLicense" 970 try: 971 value, value_type = winreg.QueryValueEx(key, subkey) 972 finally: 973 winreg.CloseKey(key) 974 except OSError: 975 pass 976 else: 977 info_add('windows.developer_mode', "enabled" if value else "disabled") 978 979 980def collect_fips(info_add): 981 try: 982 import _hashlib 983 except ImportError: 984 _hashlib = None 985 986 if _hashlib is not None: 987 call_func(info_add, 'fips.openssl_fips_mode', _hashlib, 'get_fips_mode') 988 989 try: 990 with open("/proc/sys/crypto/fips_enabled", encoding="utf-8") as fp: 991 line = fp.readline().rstrip() 992 993 if line: 994 info_add('fips.linux_crypto_fips_enabled', line) 995 except OSError: 996 pass 997 998 999def collect_tempfile(info_add): 1000 import tempfile 1001 1002 info_add('tempfile.gettempdir', tempfile.gettempdir()) 1003 1004 1005def collect_libregrtest_utils(info_add): 1006 try: 1007 from test.libregrtest import utils 1008 except ImportError: 1009 return 1010 1011 info_add('libregrtests.build_info', ' '.join(utils.get_build_info())) 1012 1013 1014def collect_info(info): 1015 error = False 1016 info_add = info.add 1017 1018 for collect_func in ( 1019 # collect_urandom() must be the first, to check the getrandom() status. 1020 # Other functions may block on os.urandom() indirectly and so change 1021 # its state. 1022 collect_urandom, 1023 1024 collect_builtins, 1025 collect_cc, 1026 collect_curses, 1027 collect_datetime, 1028 collect_decimal, 1029 collect_expat, 1030 collect_fips, 1031 collect_gdb, 1032 collect_gdbm, 1033 collect_get_config, 1034 collect_locale, 1035 collect_os, 1036 collect_platform, 1037 collect_pwd, 1038 collect_readline, 1039 collect_resource, 1040 collect_socket, 1041 collect_sqlite, 1042 collect_ssl, 1043 collect_subprocess, 1044 collect_sys, 1045 collect_sysconfig, 1046 collect_testcapi, 1047 collect_testinternalcapi, 1048 collect_tempfile, 1049 collect_time, 1050 collect_tkinter, 1051 collect_windows, 1052 collect_zlib, 1053 collect_libregrtest_utils, 1054 1055 # Collecting from tests should be last as they have side effects. 1056 collect_test_socket, 1057 collect_support, 1058 collect_support_os_helper, 1059 collect_support_socket_helper, 1060 collect_support_threading_helper, 1061 ): 1062 try: 1063 collect_func(info_add) 1064 except Exception: 1065 error = True 1066 print("ERROR: %s() failed" % (collect_func.__name__), 1067 file=sys.stderr) 1068 traceback.print_exc(file=sys.stderr) 1069 print(file=sys.stderr) 1070 sys.stderr.flush() 1071 1072 return error 1073 1074 1075def dump_info(info, file=None): 1076 title = "Python debug information" 1077 print(title) 1078 print("=" * len(title)) 1079 print() 1080 1081 infos = info.get_infos() 1082 infos = sorted(infos.items()) 1083 for key, value in infos: 1084 value = value.replace("\n", " ") 1085 print("%s: %s" % (key, value)) 1086 1087 1088def main(): 1089 info = PythonInfo() 1090 error = collect_info(info) 1091 dump_info(info) 1092 1093 if error: 1094 print() 1095 print("Collection failed: exit with error", file=sys.stderr) 1096 sys.exit(1) 1097 1098 1099if __name__ == "__main__": 1100 main() 1101