• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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