blob: f40ff8d0eba1fe1e43c2d5822416ae5b12db7e14 [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
Miss Islington (bot)016f59a2018-01-17 08:58:16 -0800318 info_add('time.time', time.time())
319
Victor Stinnercce1cb92017-08-22 03:40:26 +0200320 attributes = (
321 'altzone',
322 'daylight',
323 'timezone',
324 'tzname',
325 )
326 copy_attributes(info_add, time, 'time.%s', attributes)
327
Victor Stinner2316c682017-12-13 23:23:40 +0100328 if hasattr(time, 'get_clock_info'):
329 for clock in ('time', 'perf_counter'):
330 tinfo = time.get_clock_info(clock)
Miss Islington (bot)016f59a2018-01-17 08:58:16 -0800331 info_add('time.get_clock_info(%s)' % clock, tinfo)
332
333
334def collect_datetime(info_add):
335 try:
336 import datetime
337 except ImportError:
338 return
339
340 info_add('datetime.datetime.now', datetime.datetime.now())
Victor Stinner2316c682017-12-13 23:23:40 +0100341
Victor Stinnercce1cb92017-08-22 03:40:26 +0200342
343def collect_sysconfig(info_add):
344 import sysconfig
345
346 for name in (
347 'ABIFLAGS',
348 'ANDROID_API_LEVEL',
349 'CC',
350 'CCSHARED',
351 'CFLAGS',
352 'CFLAGSFORSHARED',
Victor Stinnercce1cb92017-08-22 03:40:26 +0200353 'CONFIG_ARGS',
354 'HOST_GNU_TYPE',
355 'MACHDEP',
356 'MULTIARCH',
357 'OPT',
358 'PY_CFLAGS',
359 'PY_CFLAGS_NODIST',
Victor Stinner2316c682017-12-13 23:23:40 +0100360 'PY_LDFLAGS',
Victor Stinnercce1cb92017-08-22 03:40:26 +0200361 'Py_DEBUG',
362 'Py_ENABLE_SHARED',
363 'SHELL',
364 'SOABI',
365 'prefix',
366 ):
367 value = sysconfig.get_config_var(name)
368 if name == 'ANDROID_API_LEVEL' and not value:
369 # skip ANDROID_API_LEVEL=0
370 continue
371 value = normalize_text(value)
372 info_add('sysconfig[%s]' % name, value)
373
374
375def collect_ssl(info_add):
376 try:
377 import ssl
378 except ImportError:
379 return
380
381 def format_attr(attr, value):
382 if attr.startswith('OP_'):
383 return '%#8x' % value
384 else:
385 return value
386
387 attributes = (
388 'OPENSSL_VERSION',
389 'OPENSSL_VERSION_INFO',
390 'HAS_SNI',
391 'OP_ALL',
392 'OP_NO_TLSv1_1',
393 )
394 copy_attributes(info_add, ssl, 'ssl.%s', attributes, formatter=format_attr)
395
396
397def collect_socket(info_add):
398 import socket
399
400 hostname = socket.gethostname()
401 info_add('socket.hostname', hostname)
402
403
404def collect_sqlite(info_add):
405 try:
406 import sqlite3
407 except ImportError:
408 return
409
410 attributes = ('version', 'sqlite_version')
411 copy_attributes(info_add, sqlite3, 'sqlite3.%s', attributes)
412
413
414def collect_zlib(info_add):
415 try:
416 import zlib
417 except ImportError:
418 return
419
420 attributes = ('ZLIB_VERSION', 'ZLIB_RUNTIME_VERSION')
421 copy_attributes(info_add, zlib, 'zlib.%s', attributes)
422
423
424def collect_expat(info_add):
425 try:
426 from xml.parsers import expat
427 except ImportError:
428 return
429
430 attributes = ('EXPAT_VERSION',)
431 copy_attributes(info_add, expat, 'expat.%s', attributes)
432
433
Victor Stinner2316c682017-12-13 23:23:40 +0100434def collect_decimal(info_add):
Victor Stinnercce1cb92017-08-22 03:40:26 +0200435 try:
Victor Stinner2316c682017-12-13 23:23:40 +0100436 import _decimal
Victor Stinnercce1cb92017-08-22 03:40:26 +0200437 except ImportError:
438 return
439
Victor Stinner2316c682017-12-13 23:23:40 +0100440 attributes = ('__libmpdec_version__',)
441 copy_attributes(info_add, _decimal, '_decimal.%s', attributes)
442
443
444def collect_testcapi(info_add):
445 try:
446 import _testcapi
447 except ImportError:
448 return
449
450 call_func(info_add, 'pymem.allocator', _testcapi, 'pymem_getallocatorsname')
451 copy_attr(info_add, 'pymem.with_pymalloc', _testcapi, 'WITH_PYMALLOC')
452
453
454def collect_resource(info_add):
455 try:
456 import resource
457 except ImportError:
458 return
459
460 limits = [attr for attr in dir(resource) if attr.startswith('RLIMIT_')]
461 for name in limits:
462 key = getattr(resource, name)
463 value = resource.getrlimit(key)
464 info_add('resource.%s' % name, value)
465
466
467def collect_test_socket(info_add):
468 try:
469 from test import test_socket
470 except ImportError:
471 return
472
473 # all check attributes like HAVE_SOCKET_CAN
474 attributes = [name for name in dir(test_socket)
475 if name.startswith('HAVE_')]
476 copy_attributes(info_add, test_socket, 'test_socket.%s', attributes)
477
478
479def collect_test_support(info_add):
480 try:
481 from test import support
482 except ImportError:
483 return
484
485 attributes = ('IPV6_ENABLED',)
486 copy_attributes(info_add, support, 'test_support.%s', attributes)
487
488 call_func(info_add, 'test_support._is_gui_available', support, '_is_gui_available')
489 call_func(info_add, 'test_support.python_is_optimized', support, 'python_is_optimized')
Victor Stinnercce1cb92017-08-22 03:40:26 +0200490
491
492def collect_info(info):
493 error = False
494 info_add = info.add
495
496 for collect_func in (
Victor Stinner2316c682017-12-13 23:23:40 +0100497 # collect_os() should be the first, to check the getrandom() status
498 collect_os,
499
500 collect_builtins,
Victor Stinnercce1cb92017-08-22 03:40:26 +0200501 collect_gdb,
502 collect_locale,
Victor Stinnercce1cb92017-08-22 03:40:26 +0200503 collect_platform,
504 collect_readline,
505 collect_socket,
506 collect_sqlite,
507 collect_ssl,
508 collect_sys,
509 collect_sysconfig,
510 collect_time,
Miss Islington (bot)016f59a2018-01-17 08:58:16 -0800511 collect_datetime,
Victor Stinnercce1cb92017-08-22 03:40:26 +0200512 collect_tkinter,
513 collect_zlib,
Victor Stinner2316c682017-12-13 23:23:40 +0100514 collect_expat,
515 collect_decimal,
516 collect_testcapi,
517 collect_resource,
518
519 # Collecting from tests should be last as they have side effects.
520 collect_test_socket,
521 collect_test_support,
Victor Stinnercce1cb92017-08-22 03:40:26 +0200522 ):
523 try:
524 collect_func(info_add)
525 except Exception as exc:
526 error = True
527 print("ERROR: %s() failed" % (collect_func.__name__),
528 file=sys.stderr)
529 traceback.print_exc(file=sys.stderr)
530 print(file=sys.stderr)
531 sys.stderr.flush()
532
533 return error
534
535
536def dump_info(info, file=None):
537 title = "Python debug information"
538 print(title)
539 print("=" * len(title))
540 print()
541
542 infos = info.get_infos()
543 infos = sorted(infos.items())
544 for key, value in infos:
545 value = value.replace("\n", " ")
546 print("%s: %s" % (key, value))
547 print()
548
549
550def main():
551 info = PythonInfo()
552 error = collect_info(info)
553 dump_info(info)
554
555 if error:
556 print("Collection failed: exit with error", file=sys.stderr)
557 sys.exit(1)
558
559
560if __name__ == "__main__":
561 main()