blob: f1b02336f7e3918134cb983051b21248bcfbc2c9 [file] [log] [blame]
Victor Stinnerb907abc2017-08-17 16:40:51 +02001"""
luzpaza5293b42017-11-05 07:37:50 -06002Collect various information about Python to help debugging test failures.
Victor Stinnerb907abc2017-08-17 16:40:51 +02003"""
4from __future__ import print_function
Victor Stinnera92941f2017-09-19 07:37:24 -07005import errno
Victor Stinnerb907abc2017-08-17 16:40:51 +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
Victor Stinnerad7eaed2017-08-18 12:08:47 +020027 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
Victor Stinnerb907abc2017-08-17 16:40:51 +020035 value = value.strip()
36 if not value:
37 return
Victor Stinnerb907abc2017-08-17 16:40:51 +020038
39 self.info[key] = value
40
41 def get_infos(self):
42 """
luzpaza5293b42017-11-05 07:37:50 -060043 Get information as a key:value dictionary where values are strings.
Victor Stinnerb907abc2017-08-17 16:40:51 +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 Stinner5d39e042017-11-29 17:20:38 +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 Stinnerad7eaed2017-08-18 12:08:47 +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)
Victor Stinnerb907abc2017-08-17 16:40:51 +020076
Victor Stinnerad7eaed2017-08-18 12:08:47 +020077
78def collect_sys(info_add):
Victor Stinnerb907abc2017-08-17 16:40:51 +020079 attributes = (
80 '_framework',
Victor Stinnerad7eaed2017-08-18 12:08:47 +020081 'abiflags',
82 'api_version',
83 'builtin_module_names',
Victor Stinnerb907abc2017-08-17 16:40:51 +020084 'byteorder',
Victor Stinnerad7eaed2017-08-18 12:08:47 +020085 'dont_write_bytecode',
Victor Stinnerb907abc2017-08-17 16:40:51 +020086 'executable',
87 'flags',
Victor Stinnerad7eaed2017-08-18 12:08:47 +020088 'float_info',
89 'float_repr_style',
90 'hash_info',
91 'hexversion',
92 'implementation',
93 'int_info',
Victor Stinnerb907abc2017-08-17 16:40:51 +020094 'maxsize',
95 'maxunicode',
Victor Stinnerad7eaed2017-08-18 12:08:47 +020096 'path',
97 'platform',
98 'prefix',
99 'thread_info',
Victor Stinnerb907abc2017-08-17 16:40:51 +0200100 'version',
Victor Stinnerad7eaed2017-08-18 12:08:47 +0200101 'version_info',
102 'winver',
Victor Stinnerb907abc2017-08-17 16:40:51 +0200103 )
Victor Stinnerad7eaed2017-08-18 12:08:47 +0200104 copy_attributes(info_add, sys, 'sys.%s', attributes)
105
106 call_func(info_add, 'sys.androidapilevel', sys, 'getandroidapilevel')
107 call_func(info_add, 'sys.windowsversion', sys, 'getwindowsversion')
Victor Stinnerb907abc2017-08-17 16:40:51 +0200108
109 encoding = sys.getfilesystemencoding()
110 if hasattr(sys, 'getfilesystemencodeerrors'):
111 encoding = '%s/%s' % (encoding, sys.getfilesystemencodeerrors())
112 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 Stinnerafd055a2017-10-31 08:41:10 -0700126 # 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 Stinnerb907abc2017-08-17 16:40:51 +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
151 info_add('locale.encoding', locale.getpreferredencoding(False))
152
153
Victor Stinner98146972017-12-13 17:27:40 +0100154def collect_builtins(info_add):
155 info_add('builtins.float.float_format', float.__getformat__("float"))
156 info_add('builtins.float.double_format', float.__getformat__("double"))
157
158
Victor Stinnerb907abc2017-08-17 16:40:51 +0200159def collect_os(info_add):
160 import os
161
Victor Stinnerad7eaed2017-08-18 12:08:47 +0200162 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
168
169 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)
Victor Stinnerb907abc2017-08-17 16:40:51 +0200177
Victor Stinner98146972017-12-13 17:27:40 +0100178 call_func(info_add, 'os.cwd', os, 'getcwd')
Victor Stinnerb907abc2017-08-17 16:40:51 +0200179
Victor Stinnerad7eaed2017-08-18 12:08:47 +0200180 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')
Victor Stinnerb907abc2017-08-17 16:40:51 +0200183
Victor Stinner5d39e042017-11-29 17:20:38 +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 Stinnerb907abc2017-08-17 16:40:51 +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 Stinner5d39e042017-11-29 17:20:38 +0100199 call_func(info_add, 'os.cpu_count', os, 'cpu_count')
Victor Stinnerad7eaed2017-08-18 12:08:47 +0200200 call_func(info_add, 'os.loadavg', os, 'getloadavg')
Victor Stinnerb907abc2017-08-17 16:40:51 +0200201
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 Stinner5d39e042017-11-29 17:20:38 +0100230 if (uname in ENV_VARS
231 # Copy PYTHON* and LC_* variables
232 or uname.startswith(("PYTHON", "LC_"))
Victor Stinnerb907abc2017-08-17 16:40:51 +0200233 # Visual Studio: VS140COMNTOOLS
234 or (uname.startswith("VS") and uname.endswith("COMNTOOLS"))):
235 info_add('os.environ[%s]' % name, value)
236
Victor Stinnerad7eaed2017-08-18 12:08:47 +0200237 if hasattr(os, 'umask'):
238 mask = os.umask(0)
239 os.umask(mask)
240 info_add("os.umask", '%03o' % mask)
241
242 if hasattr(os, 'getrandom'):
243 # PEP 524: Check if system urandom is initialized
244 try:
Victor Stinnera92941f2017-09-19 07:37:24 -0700245 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 Stinnerad7eaed2017-08-18 12:08:47 +0200256
Victor Stinnerb907abc2017-08-17 16:40:51 +0200257
258def collect_readline(info_add):
259 try:
260 import readline
261 except ImportError:
262 return
263
264 def format_attr(attr, value):
265 if isinstance(value, int):
266 return "%#x" % value
267 else:
268 return value
269
270 attributes = (
271 "_READLINE_VERSION",
272 "_READLINE_RUNTIME_VERSION",
273 "_READLINE_LIBRARY_VERSION",
274 )
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:
306 import tkinter
307 except ImportError:
308 pass
309 else:
310 tcl = tkinter.Tcl()
311 patchlevel = tcl.call('info', 'patchlevel')
312 info_add('tkinter.info_patchlevel', patchlevel)
313
314
315def collect_time(info_add):
316 import time
317
Victor Stinner7d91c022018-01-17 16:35:45 +0100318 info_add('time.time', time.time())
319
Victor Stinnerad7eaed2017-08-18 12:08:47 +0200320 attributes = (
321 'altzone',
322 'daylight',
323 'timezone',
324 'tzname',
325 )
326 copy_attributes(info_add, time, 'time.%s', attributes)
327
Victor Stinner5d39e042017-11-29 17:20:38 +0100328 if hasattr(time, 'get_clock_info'):
329 for clock in ('time', 'perf_counter'):
330 tinfo = time.get_clock_info(clock)
Victor Stinner7d91c022018-01-17 16:35:45 +0100331 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 Stinnerb907abc2017-08-17 16:40:51 +0200341
342
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 Stinnerb907abc2017-08-17 16:40:51 +0200353 'CONFIG_ARGS',
354 'HOST_GNU_TYPE',
355 'MACHDEP',
356 'MULTIARCH',
357 'OPT',
358 'PY_CFLAGS',
359 'PY_CFLAGS_NODIST',
Victor Stinner5d39e042017-11-29 17:20:38 +0100360 'PY_LDFLAGS',
Victor Stinnerb907abc2017-08-17 16:40:51 +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:
Victor Stinnerad7eaed2017-08-18 12:08:47 +0200385 return value
Victor Stinnerb907abc2017-08-17 16:40:51 +0200386
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
Victor Stinnerf6ebd832017-08-17 22:13:11 +0200424def 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
434def collect_decimal(info_add):
435 try:
436 import _decimal
437 except ImportError:
438 return
439
440 attributes = ('__libmpdec_version__',)
441 copy_attributes(info_add, _decimal, '_decimal.%s', attributes)
442
443
Victor Stinner5d39e042017-11-29 17:20:38 +0100444def 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
Victor Stinner98146972017-12-13 17:27:40 +0100454def 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')
490
491
Victor Stinnerb907abc2017-08-17 16:40:51 +0200492def collect_info(info):
493 error = False
494 info_add = info.add
495
496 for collect_func in (
497 # collect_os() should be the first, to check the getrandom() status
498 collect_os,
499
Victor Stinner98146972017-12-13 17:27:40 +0100500 collect_builtins,
Victor Stinnerb907abc2017-08-17 16:40:51 +0200501 collect_gdb,
502 collect_locale,
503 collect_platform,
504 collect_readline,
505 collect_socket,
506 collect_sqlite,
507 collect_ssl,
508 collect_sys,
509 collect_sysconfig,
510 collect_time,
Victor Stinner7d91c022018-01-17 16:35:45 +0100511 collect_datetime,
Victor Stinnerb907abc2017-08-17 16:40:51 +0200512 collect_tkinter,
513 collect_zlib,
Victor Stinnerf6ebd832017-08-17 22:13:11 +0200514 collect_expat,
515 collect_decimal,
Victor Stinner5d39e042017-11-29 17:20:38 +0100516 collect_testcapi,
Victor Stinner98146972017-12-13 17:27:40 +0100517 collect_resource,
518
519 # Collecting from tests should be last as they have side effects.
520 collect_test_socket,
521 collect_test_support,
Victor Stinnerb907abc2017-08-17 16:40:51 +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()