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