blob: 4ce06e00eb98f5a86201e87718926a9afe46f299 [file] [log] [blame]
Victor Stinnercce1cb92017-08-22 03:40:26 +02001"""
Victor Stinner2316c682017-12-13 23:23:40 +01002Collect various information about Python to help debugging test failures.
Victor Stinnercce1cb92017-08-22 03:40:26 +02003"""
4from __future__ import print_function
Victor Stinner2316c682017-12-13 23:23:40 +01005import errno
Victor Stinnercce1cb92017-08-22 03:40:26 +02006import re
7import sys
8import traceback
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
Victor Stinner2316c682017-12-13 23:23:40 +010030 if not isinstance(value, int):
31 if not isinstance(value, str):
Victor Stinnercce1cb92017-08-22 03:40:26 +020032 # 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 """
Victor Stinner2316c682017-12-13 23:23:40 +010043 Get information as a key:value dictionary where values are strings.
Victor Stinnercce1cb92017-08-22 03:40:26 +020044 """
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
Victor Stinner2316c682017-12-13 23:23:40 +010059def 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
Victor Stinnercce1cb92017-08-22 03:40:26 +020067def 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 = (
Victor Stinner2316c682017-12-13 23:23:40 +010080 '_framework',
81 'abiflags',
Victor Stinnercce1cb92017-08-22 03:40:26 +020082 'api_version',
83 'builtin_module_names',
84 'byteorder',
85 'dont_write_bytecode',
86 'executable',
87 'flags',
88 'float_info',
89 'float_repr_style',
Victor Stinner2316c682017-12-13 23:23:40 +010090 'hash_info',
Victor Stinnercce1cb92017-08-22 03:40:26 +020091 'hexversion',
Victor Stinner2316c682017-12-13 23:23:40 +010092 'implementation',
93 'int_info',
Victor Stinnercce1cb92017-08-22 03:40:26 +020094 'maxsize',
95 'maxunicode',
96 'path',
97 'platform',
98 'prefix',
Victor Stinner2316c682017-12-13 23:23:40 +010099 'thread_info',
Victor Stinnercce1cb92017-08-22 03:40:26 +0200100 'version',
101 'version_info',
102 'winver',
103 )
104 copy_attributes(info_add, sys, 'sys.%s', attributes)
105
Victor Stinner2316c682017-12-13 23:23:40 +0100106 call_func(info_add, 'sys.androidapilevel', sys, 'getandroidapilevel')
107 call_func(info_add, 'sys.windowsversion', sys, 'getwindowsversion')
108
Victor Stinnercce1cb92017-08-22 03:40:26 +0200109 encoding = sys.getfilesystemencoding()
Victor Stinner2316c682017-12-13 23:23:40 +0100110 if hasattr(sys, 'getfilesystemencodeerrors'):
111 encoding = '%s/%s' % (encoding, sys.getfilesystemencodeerrors())
Victor Stinnercce1cb92017-08-22 03:40:26 +0200112 info_add('sys.filesystem_encoding', encoding)
113
114 for name in ('stdin', 'stdout', 'stderr'):
115 stream = getattr(sys, name)
116 if stream is None:
117 continue
118 encoding = getattr(stream, 'encoding', None)
119 if not encoding:
120 continue
121 errors = getattr(stream, 'errors', None)
122 if errors:
123 encoding = '%s/%s' % (encoding, errors)
124 info_add('sys.%s.encoding' % name, encoding)
125
Victor Stinner180372c2017-11-27 10:44:50 +0100126 # Were we compiled --with-pydebug or with #define Py_DEBUG?
127 Py_DEBUG = hasattr(sys, 'gettotalrefcount')
128 if Py_DEBUG:
129 text = 'Yes (sys.gettotalrefcount() present)'
130 else:
131 text = 'No (sys.gettotalrefcount() missing)'
132 info_add('Py_DEBUG', text)
133
Victor Stinnercce1cb92017-08-22 03:40:26 +0200134
135def collect_platform(info_add):
136 import platform
137
138 arch = platform.architecture()
139 arch = ' '.join(filter(bool, arch))
140 info_add('platform.architecture', arch)
141
142 info_add('platform.python_implementation',
143 platform.python_implementation())
144 info_add('platform.platform',
145 platform.platform(aliased=True))
146
147
148def collect_locale(info_add):
149 import locale
150
Victor Stinner2316c682017-12-13 23:23:40 +0100151 info_add('locale.encoding', locale.getpreferredencoding(False))
152
153
154def collect_builtins(info_add):
155 info_add('builtins.float.float_format', float.__getformat__("float"))
156 info_add('builtins.float.double_format', float.__getformat__("double"))
Victor Stinnercce1cb92017-08-22 03:40:26 +0200157
158
159def collect_os(info_add):
160 import os
161
Victor Stinner2316c682017-12-13 23:23:40 +0100162 def format_attr(attr, value):
163 if attr in ('supports_follow_symlinks', 'supports_fd',
164 'supports_effective_ids'):
165 return str(sorted(func.__name__ for func in value))
166 else:
167 return value
Victor Stinnercce1cb92017-08-22 03:40:26 +0200168
Victor Stinner2316c682017-12-13 23:23:40 +0100169 attributes = (
170 'name',
171 'supports_bytes_environ',
172 'supports_effective_ids',
173 'supports_fd',
174 'supports_follow_symlinks',
175 )
176 copy_attributes(info_add, os, 'os.%s', attributes, formatter=format_attr)
177
178 call_func(info_add, 'os.cwd', os, 'getcwd')
Victor Stinnercce1cb92017-08-22 03:40:26 +0200179
180 call_func(info_add, 'os.uid', os, 'getuid')
181 call_func(info_add, 'os.gid', os, 'getgid')
182 call_func(info_add, 'os.uname', os, 'uname')
183
Victor Stinner2316c682017-12-13 23:23:40 +0100184 def format_groups(groups):
185 return ', '.join(map(str, groups))
186
187 call_func(info_add, 'os.groups', os, 'getgroups', formatter=format_groups)
Victor Stinnercce1cb92017-08-22 03:40:26 +0200188
189 if hasattr(os, 'getlogin'):
190 try:
191 login = os.getlogin()
192 except OSError:
193 # getlogin() fails with "OSError: [Errno 25] Inappropriate ioctl
194 # for device" on Travis CI
195 pass
196 else:
197 info_add("os.login", login)
198
Victor Stinner2316c682017-12-13 23:23:40 +0100199 call_func(info_add, 'os.cpu_count', os, 'cpu_count')
Victor Stinnercce1cb92017-08-22 03:40:26 +0200200 call_func(info_add, 'os.loadavg', os, 'getloadavg')
201
202 # Get environment variables: filter to list
203 # to not leak sensitive information
204 ENV_VARS = (
205 "CC",
206 "COMSPEC",
207 "DISPLAY",
208 "DISTUTILS_USE_SDK",
209 "DYLD_LIBRARY_PATH",
210 "HOME",
211 "HOMEDRIVE",
212 "HOMEPATH",
213 "LANG",
214 "LD_LIBRARY_PATH",
215 "MACOSX_DEPLOYMENT_TARGET",
216 "MAKEFLAGS",
217 "MSSDK",
218 "PATH",
219 "SDK_TOOLS_BIN",
220 "SHELL",
221 "TEMP",
222 "TERM",
223 "TMP",
224 "TMPDIR",
225 "USERPROFILE",
226 "WAYLAND_DISPLAY",
227 )
228 for name, value in os.environ.items():
229 uname = name.upper()
Victor Stinner2316c682017-12-13 23:23:40 +0100230 if (uname in ENV_VARS
231 # Copy PYTHON* and LC_* variables
232 or uname.startswith(("PYTHON", "LC_"))
Victor Stinnercce1cb92017-08-22 03:40:26 +0200233 # Visual Studio: VS140COMNTOOLS
234 or (uname.startswith("VS") and uname.endswith("COMNTOOLS"))):
235 info_add('os.environ[%s]' % name, value)
236
237 if hasattr(os, 'umask'):
238 mask = os.umask(0)
239 os.umask(mask)
240 info_add("os.umask", '%03o' % mask)
241
Victor Stinner2316c682017-12-13 23:23:40 +0100242 if hasattr(os, 'getrandom'):
243 # PEP 524: Check if system urandom is initialized
244 try:
245 try:
246 os.getrandom(1, os.GRND_NONBLOCK)
247 state = 'ready (initialized)'
248 except BlockingIOError as exc:
249 state = 'not seeded yet (%s)' % exc
250 info_add('os.getrandom', state)
251 except OSError as exc:
252 # Python was compiled on a more recent Linux version
253 # than the current Linux kernel: ignore OSError(ENOSYS)
254 if exc.errno != errno.ENOSYS:
255 raise
Victor Stinnercce1cb92017-08-22 03:40:26 +0200256
257
258def collect_readline(info_add):
259 try:
260 import readline
261 except ImportError:
262 return
263
264 def format_attr(attr, value):
Victor Stinner2316c682017-12-13 23:23:40 +0100265 if isinstance(value, int):
Victor Stinnercce1cb92017-08-22 03:40:26 +0200266 return "%#x" % value
267 else:
268 return value
269
270 attributes = (
271 "_READLINE_VERSION",
272 "_READLINE_RUNTIME_VERSION",
Victor Stinner2316c682017-12-13 23:23:40 +0100273 "_READLINE_LIBRARY_VERSION",
Victor Stinnercce1cb92017-08-22 03:40:26 +0200274 )
275 copy_attributes(info_add, readline, 'readline.%s', attributes,
276 formatter=format_attr)
277
278
279def collect_gdb(info_add):
280 import subprocess
281
282 try:
283 proc = subprocess.Popen(["gdb", "-nx", "--version"],
284 stdout=subprocess.PIPE,
285 stderr=subprocess.PIPE,
286 universal_newlines=True)
287 version = proc.communicate()[0]
288 except OSError:
289 return
290
291 # Only keep the first line
292 version = version.splitlines()[0]
293 info_add('gdb_version', version)
294
295
296def collect_tkinter(info_add):
297 try:
298 import _tkinter
299 except ImportError:
300 pass
301 else:
302 attributes = ('TK_VERSION', 'TCL_VERSION')
303 copy_attributes(info_add, _tkinter, 'tkinter.%s', attributes)
304
305 try:
Victor Stinner2316c682017-12-13 23:23:40 +0100306 import tkinter
Victor Stinnercce1cb92017-08-22 03:40:26 +0200307 except ImportError:
308 pass
309 else:
Victor Stinner2316c682017-12-13 23:23:40 +0100310 tcl = tkinter.Tcl()
Victor Stinnercce1cb92017-08-22 03:40:26 +0200311 patchlevel = tcl.call('info', 'patchlevel')
312 info_add('tkinter.info_patchlevel', patchlevel)
313
314
315def collect_time(info_add):
316 import time
317
318 attributes = (
319 'altzone',
320 'daylight',
321 'timezone',
322 'tzname',
323 )
324 copy_attributes(info_add, time, 'time.%s', attributes)
325
Victor Stinner2316c682017-12-13 23:23:40 +0100326 if hasattr(time, 'get_clock_info'):
327 for clock in ('time', 'perf_counter'):
328 tinfo = time.get_clock_info(clock)
329 info_add('time.%s' % clock, tinfo)
330
Victor Stinnercce1cb92017-08-22 03:40:26 +0200331
332def collect_sysconfig(info_add):
333 import sysconfig
334
335 for name in (
336 'ABIFLAGS',
337 'ANDROID_API_LEVEL',
338 'CC',
339 'CCSHARED',
340 'CFLAGS',
341 'CFLAGSFORSHARED',
Victor Stinnercce1cb92017-08-22 03:40:26 +0200342 'CONFIG_ARGS',
343 'HOST_GNU_TYPE',
344 'MACHDEP',
345 'MULTIARCH',
346 'OPT',
347 'PY_CFLAGS',
348 'PY_CFLAGS_NODIST',
Victor Stinner2316c682017-12-13 23:23:40 +0100349 'PY_LDFLAGS',
Victor Stinnercce1cb92017-08-22 03:40:26 +0200350 'Py_DEBUG',
351 'Py_ENABLE_SHARED',
352 'SHELL',
353 'SOABI',
354 'prefix',
355 ):
356 value = sysconfig.get_config_var(name)
357 if name == 'ANDROID_API_LEVEL' and not value:
358 # skip ANDROID_API_LEVEL=0
359 continue
360 value = normalize_text(value)
361 info_add('sysconfig[%s]' % name, value)
362
363
364def collect_ssl(info_add):
365 try:
366 import ssl
367 except ImportError:
368 return
369
370 def format_attr(attr, value):
371 if attr.startswith('OP_'):
372 return '%#8x' % value
373 else:
374 return value
375
376 attributes = (
377 'OPENSSL_VERSION',
378 'OPENSSL_VERSION_INFO',
379 'HAS_SNI',
380 'OP_ALL',
381 'OP_NO_TLSv1_1',
382 )
383 copy_attributes(info_add, ssl, 'ssl.%s', attributes, formatter=format_attr)
384
385
386def collect_socket(info_add):
387 import socket
388
389 hostname = socket.gethostname()
390 info_add('socket.hostname', hostname)
391
392
393def collect_sqlite(info_add):
394 try:
395 import sqlite3
396 except ImportError:
397 return
398
399 attributes = ('version', 'sqlite_version')
400 copy_attributes(info_add, sqlite3, 'sqlite3.%s', attributes)
401
402
403def collect_zlib(info_add):
404 try:
405 import zlib
406 except ImportError:
407 return
408
409 attributes = ('ZLIB_VERSION', 'ZLIB_RUNTIME_VERSION')
410 copy_attributes(info_add, zlib, 'zlib.%s', attributes)
411
412
413def collect_expat(info_add):
414 try:
415 from xml.parsers import expat
416 except ImportError:
417 return
418
419 attributes = ('EXPAT_VERSION',)
420 copy_attributes(info_add, expat, 'expat.%s', attributes)
421
422
Victor Stinner2316c682017-12-13 23:23:40 +0100423def collect_decimal(info_add):
Victor Stinnercce1cb92017-08-22 03:40:26 +0200424 try:
Victor Stinner2316c682017-12-13 23:23:40 +0100425 import _decimal
Victor Stinnercce1cb92017-08-22 03:40:26 +0200426 except ImportError:
427 return
428
Victor Stinner2316c682017-12-13 23:23:40 +0100429 attributes = ('__libmpdec_version__',)
430 copy_attributes(info_add, _decimal, '_decimal.%s', attributes)
431
432
433def collect_testcapi(info_add):
434 try:
435 import _testcapi
436 except ImportError:
437 return
438
439 call_func(info_add, 'pymem.allocator', _testcapi, 'pymem_getallocatorsname')
440 copy_attr(info_add, 'pymem.with_pymalloc', _testcapi, 'WITH_PYMALLOC')
441
442
443def collect_resource(info_add):
444 try:
445 import resource
446 except ImportError:
447 return
448
449 limits = [attr for attr in dir(resource) if attr.startswith('RLIMIT_')]
450 for name in limits:
451 key = getattr(resource, name)
452 value = resource.getrlimit(key)
453 info_add('resource.%s' % name, value)
454
455
456def collect_test_socket(info_add):
457 try:
458 from test import test_socket
459 except ImportError:
460 return
461
462 # all check attributes like HAVE_SOCKET_CAN
463 attributes = [name for name in dir(test_socket)
464 if name.startswith('HAVE_')]
465 copy_attributes(info_add, test_socket, 'test_socket.%s', attributes)
466
467
468def collect_test_support(info_add):
469 try:
470 from test import support
471 except ImportError:
472 return
473
474 attributes = ('IPV6_ENABLED',)
475 copy_attributes(info_add, support, 'test_support.%s', attributes)
476
477 call_func(info_add, 'test_support._is_gui_available', support, '_is_gui_available')
478 call_func(info_add, 'test_support.python_is_optimized', support, 'python_is_optimized')
Victor Stinnercce1cb92017-08-22 03:40:26 +0200479
480
481def collect_info(info):
482 error = False
483 info_add = info.add
484
485 for collect_func in (
Victor Stinner2316c682017-12-13 23:23:40 +0100486 # collect_os() should be the first, to check the getrandom() status
487 collect_os,
488
489 collect_builtins,
Victor Stinnercce1cb92017-08-22 03:40:26 +0200490 collect_gdb,
491 collect_locale,
Victor Stinnercce1cb92017-08-22 03:40:26 +0200492 collect_platform,
493 collect_readline,
494 collect_socket,
495 collect_sqlite,
496 collect_ssl,
497 collect_sys,
498 collect_sysconfig,
499 collect_time,
500 collect_tkinter,
501 collect_zlib,
Victor Stinner2316c682017-12-13 23:23:40 +0100502 collect_expat,
503 collect_decimal,
504 collect_testcapi,
505 collect_resource,
506
507 # Collecting from tests should be last as they have side effects.
508 collect_test_socket,
509 collect_test_support,
Victor Stinnercce1cb92017-08-22 03:40:26 +0200510 ):
511 try:
512 collect_func(info_add)
513 except Exception as exc:
514 error = True
515 print("ERROR: %s() failed" % (collect_func.__name__),
516 file=sys.stderr)
517 traceback.print_exc(file=sys.stderr)
518 print(file=sys.stderr)
519 sys.stderr.flush()
520
521 return error
522
523
524def dump_info(info, file=None):
525 title = "Python debug information"
526 print(title)
527 print("=" * len(title))
528 print()
529
530 infos = info.get_infos()
531 infos = sorted(infos.items())
532 for key, value in infos:
533 value = value.replace("\n", " ")
534 print("%s: %s" % (key, value))
535 print()
536
537
538def main():
539 info = PythonInfo()
540 error = collect_info(info)
541 dump_info(info)
542
543 if error:
544 print("Collection failed: exit with error", file=sys.stderr)
545 sys.exit(1)
546
547
548if __name__ == "__main__":
549 main()