blob: c238ef7b75c5bd6db4de21e5299590b7cb871504 [file] [log] [blame]
Victor Stinnerb907abc2017-08-17 16:40:51 +02001"""
2Collect various informations about Python to help debugging test failures.
3"""
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 """
43 Get informations 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
Victor Stinnerad7eaed2017-08-18 12:08:47 +020059def call_func(info_add, name, mod, func_name, *, formatter=None):
60 try:
61 func = getattr(mod, func_name)
62 except AttributeError:
63 return
64 value = func()
65 if formatter is not None:
66 value = formatter(value)
67 info_add(name, value)
Victor Stinnerb907abc2017-08-17 16:40:51 +020068
Victor Stinnerad7eaed2017-08-18 12:08:47 +020069
70def collect_sys(info_add):
Victor Stinnerb907abc2017-08-17 16:40:51 +020071 attributes = (
72 '_framework',
Victor Stinnerad7eaed2017-08-18 12:08:47 +020073 'abiflags',
74 'api_version',
75 'builtin_module_names',
Victor Stinnerb907abc2017-08-17 16:40:51 +020076 'byteorder',
Victor Stinnerad7eaed2017-08-18 12:08:47 +020077 'dont_write_bytecode',
Victor Stinnerb907abc2017-08-17 16:40:51 +020078 'executable',
79 'flags',
Victor Stinnerad7eaed2017-08-18 12:08:47 +020080 'float_info',
81 'float_repr_style',
82 'hash_info',
83 'hexversion',
84 'implementation',
85 'int_info',
Victor Stinnerb907abc2017-08-17 16:40:51 +020086 'maxsize',
87 'maxunicode',
Victor Stinnerad7eaed2017-08-18 12:08:47 +020088 'path',
89 'platform',
90 'prefix',
91 'thread_info',
Victor Stinnerb907abc2017-08-17 16:40:51 +020092 'version',
Victor Stinnerad7eaed2017-08-18 12:08:47 +020093 'version_info',
94 'winver',
Victor Stinnerb907abc2017-08-17 16:40:51 +020095 )
Victor Stinnerad7eaed2017-08-18 12:08:47 +020096 copy_attributes(info_add, sys, 'sys.%s', attributes)
97
98 call_func(info_add, 'sys.androidapilevel', sys, 'getandroidapilevel')
99 call_func(info_add, 'sys.windowsversion', sys, 'getwindowsversion')
Victor Stinnerb907abc2017-08-17 16:40:51 +0200100
101 encoding = sys.getfilesystemencoding()
102 if hasattr(sys, 'getfilesystemencodeerrors'):
103 encoding = '%s/%s' % (encoding, sys.getfilesystemencodeerrors())
104 info_add('sys.filesystem_encoding', encoding)
105
106 for name in ('stdin', 'stdout', 'stderr'):
107 stream = getattr(sys, name)
108 if stream is None:
109 continue
110 encoding = getattr(stream, 'encoding', None)
111 if not encoding:
112 continue
113 errors = getattr(stream, 'errors', None)
114 if errors:
115 encoding = '%s/%s' % (encoding, errors)
116 info_add('sys.%s.encoding' % name, encoding)
117
Victor Stinnerafd055a2017-10-31 08:41:10 -0700118 # Were we compiled --with-pydebug or with #define Py_DEBUG?
119 Py_DEBUG = hasattr(sys, 'gettotalrefcount')
120 if Py_DEBUG:
121 text = 'Yes (sys.gettotalrefcount() present)'
122 else:
123 text = 'No (sys.gettotalrefcount() missing)'
124 info_add('Py_DEBUG', text)
125
Victor Stinnerb907abc2017-08-17 16:40:51 +0200126
127def collect_platform(info_add):
128 import platform
129
130 arch = platform.architecture()
131 arch = ' '.join(filter(bool, arch))
132 info_add('platform.architecture', arch)
133
134 info_add('platform.python_implementation',
135 platform.python_implementation())
136 info_add('platform.platform',
137 platform.platform(aliased=True))
138
139
140def collect_locale(info_add):
141 import locale
142
143 info_add('locale.encoding', locale.getpreferredencoding(False))
144
145
146def collect_os(info_add):
147 import os
148
Victor Stinnerad7eaed2017-08-18 12:08:47 +0200149 def format_attr(attr, value):
150 if attr in ('supports_follow_symlinks', 'supports_fd',
151 'supports_effective_ids'):
152 return str(sorted(func.__name__ for func in value))
153 else:
154 return value
155
156 attributes = (
157 'name',
158 'supports_bytes_environ',
159 'supports_effective_ids',
160 'supports_fd',
161 'supports_follow_symlinks',
162 )
163 copy_attributes(info_add, os, 'os.%s', attributes, formatter=format_attr)
Victor Stinnerb907abc2017-08-17 16:40:51 +0200164
165 info_add("os.cwd", os.getcwd())
166
Victor Stinnerad7eaed2017-08-18 12:08:47 +0200167 call_func(info_add, 'os.uid', os, 'getuid')
168 call_func(info_add, 'os.gid', os, 'getgid')
169 call_func(info_add, 'os.uname', os, 'uname')
Victor Stinnerb907abc2017-08-17 16:40:51 +0200170
171 if hasattr(os, 'getgroups'):
172 groups = os.getgroups()
173 groups = map(str, groups)
174 groups = ', '.join(groups)
175 info_add("os.groups", groups)
176
177 if hasattr(os, 'getlogin'):
178 try:
179 login = os.getlogin()
180 except OSError:
181 # getlogin() fails with "OSError: [Errno 25] Inappropriate ioctl
182 # for device" on Travis CI
183 pass
184 else:
185 info_add("os.login", login)
186
187 if hasattr(os, 'cpu_count'):
188 cpu_count = os.cpu_count()
189 if cpu_count:
190 info_add('os.cpu_count', cpu_count)
191
Victor Stinnerad7eaed2017-08-18 12:08:47 +0200192 call_func(info_add, 'os.loadavg', os, 'getloadavg')
Victor Stinnerb907abc2017-08-17 16:40:51 +0200193
194 # Get environment variables: filter to list
195 # to not leak sensitive information
196 ENV_VARS = (
197 "CC",
198 "COMSPEC",
199 "DISPLAY",
200 "DISTUTILS_USE_SDK",
201 "DYLD_LIBRARY_PATH",
202 "HOME",
203 "HOMEDRIVE",
204 "HOMEPATH",
205 "LANG",
206 "LD_LIBRARY_PATH",
207 "MACOSX_DEPLOYMENT_TARGET",
208 "MAKEFLAGS",
209 "MSSDK",
210 "PATH",
211 "SDK_TOOLS_BIN",
212 "SHELL",
213 "TEMP",
214 "TERM",
215 "TMP",
216 "TMPDIR",
217 "USERPROFILE",
218 "WAYLAND_DISPLAY",
219 )
220 for name, value in os.environ.items():
221 uname = name.upper()
222 if (uname in ENV_VARS or uname.startswith(("PYTHON", "LC_"))
223 # Visual Studio: VS140COMNTOOLS
224 or (uname.startswith("VS") and uname.endswith("COMNTOOLS"))):
225 info_add('os.environ[%s]' % name, value)
226
Victor Stinnerad7eaed2017-08-18 12:08:47 +0200227 if hasattr(os, 'umask'):
228 mask = os.umask(0)
229 os.umask(mask)
230 info_add("os.umask", '%03o' % mask)
231
232 if hasattr(os, 'getrandom'):
233 # PEP 524: Check if system urandom is initialized
234 try:
Victor Stinnera92941f2017-09-19 07:37:24 -0700235 try:
236 os.getrandom(1, os.GRND_NONBLOCK)
237 state = 'ready (initialized)'
238 except BlockingIOError as exc:
239 state = 'not seeded yet (%s)' % exc
240 info_add('os.getrandom', state)
241 except OSError as exc:
242 # Python was compiled on a more recent Linux version
243 # than the current Linux kernel: ignore OSError(ENOSYS)
244 if exc.errno != errno.ENOSYS:
245 raise
Victor Stinnerad7eaed2017-08-18 12:08:47 +0200246
Victor Stinnerb907abc2017-08-17 16:40:51 +0200247
248def collect_readline(info_add):
249 try:
250 import readline
251 except ImportError:
252 return
253
254 def format_attr(attr, value):
255 if isinstance(value, int):
256 return "%#x" % value
257 else:
258 return value
259
260 attributes = (
261 "_READLINE_VERSION",
262 "_READLINE_RUNTIME_VERSION",
263 "_READLINE_LIBRARY_VERSION",
264 )
265 copy_attributes(info_add, readline, 'readline.%s', attributes,
266 formatter=format_attr)
267
268
269def collect_gdb(info_add):
270 import subprocess
271
272 try:
273 proc = subprocess.Popen(["gdb", "-nx", "--version"],
274 stdout=subprocess.PIPE,
275 stderr=subprocess.PIPE,
276 universal_newlines=True)
277 version = proc.communicate()[0]
278 except OSError:
279 return
280
281 # Only keep the first line
282 version = version.splitlines()[0]
283 info_add('gdb_version', version)
284
285
286def collect_tkinter(info_add):
287 try:
288 import _tkinter
289 except ImportError:
290 pass
291 else:
292 attributes = ('TK_VERSION', 'TCL_VERSION')
293 copy_attributes(info_add, _tkinter, 'tkinter.%s', attributes)
294
295 try:
296 import tkinter
297 except ImportError:
298 pass
299 else:
300 tcl = tkinter.Tcl()
301 patchlevel = tcl.call('info', 'patchlevel')
302 info_add('tkinter.info_patchlevel', patchlevel)
303
304
305def collect_time(info_add):
306 import time
307
Victor Stinnerad7eaed2017-08-18 12:08:47 +0200308 attributes = (
309 'altzone',
310 'daylight',
311 'timezone',
312 'tzname',
313 )
314 copy_attributes(info_add, time, 'time.%s', attributes)
315
Victor Stinnerb907abc2017-08-17 16:40:51 +0200316 if not hasattr(time, 'get_clock_info'):
317 return
318
319 for clock in ('time', 'perf_counter'):
320 tinfo = time.get_clock_info(clock)
Victor Stinnerad7eaed2017-08-18 12:08:47 +0200321 info_add('time.%s' % clock, tinfo)
Victor Stinnerb907abc2017-08-17 16:40:51 +0200322
323
324def collect_sysconfig(info_add):
325 import sysconfig
326
327 for name in (
328 'ABIFLAGS',
329 'ANDROID_API_LEVEL',
330 'CC',
331 'CCSHARED',
332 'CFLAGS',
333 'CFLAGSFORSHARED',
334 'PY_LDFLAGS',
335 'CONFIG_ARGS',
336 'HOST_GNU_TYPE',
337 'MACHDEP',
338 'MULTIARCH',
339 'OPT',
340 'PY_CFLAGS',
341 'PY_CFLAGS_NODIST',
342 'Py_DEBUG',
343 'Py_ENABLE_SHARED',
344 'SHELL',
345 'SOABI',
346 'prefix',
347 ):
348 value = sysconfig.get_config_var(name)
349 if name == 'ANDROID_API_LEVEL' and not value:
350 # skip ANDROID_API_LEVEL=0
351 continue
352 value = normalize_text(value)
353 info_add('sysconfig[%s]' % name, value)
354
355
356def collect_ssl(info_add):
357 try:
358 import ssl
359 except ImportError:
360 return
361
362 def format_attr(attr, value):
363 if attr.startswith('OP_'):
364 return '%#8x' % value
365 else:
Victor Stinnerad7eaed2017-08-18 12:08:47 +0200366 return value
Victor Stinnerb907abc2017-08-17 16:40:51 +0200367
368 attributes = (
369 'OPENSSL_VERSION',
370 'OPENSSL_VERSION_INFO',
371 'HAS_SNI',
372 'OP_ALL',
373 'OP_NO_TLSv1_1',
374 )
375 copy_attributes(info_add, ssl, 'ssl.%s', attributes, formatter=format_attr)
376
377
378def collect_socket(info_add):
379 import socket
380
381 hostname = socket.gethostname()
382 info_add('socket.hostname', hostname)
383
384
385def collect_sqlite(info_add):
386 try:
387 import sqlite3
388 except ImportError:
389 return
390
391 attributes = ('version', 'sqlite_version')
392 copy_attributes(info_add, sqlite3, 'sqlite3.%s', attributes)
393
394
395def collect_zlib(info_add):
396 try:
397 import zlib
398 except ImportError:
399 return
400
401 attributes = ('ZLIB_VERSION', 'ZLIB_RUNTIME_VERSION')
402 copy_attributes(info_add, zlib, 'zlib.%s', attributes)
403
404
Victor Stinnerf6ebd832017-08-17 22:13:11 +0200405def collect_expat(info_add):
406 try:
407 from xml.parsers import expat
408 except ImportError:
409 return
410
411 attributes = ('EXPAT_VERSION',)
412 copy_attributes(info_add, expat, 'expat.%s', attributes)
413
414
415def collect_decimal(info_add):
416 try:
417 import _decimal
418 except ImportError:
419 return
420
421 attributes = ('__libmpdec_version__',)
422 copy_attributes(info_add, _decimal, '_decimal.%s', attributes)
423
424
Victor Stinnerb907abc2017-08-17 16:40:51 +0200425def collect_info(info):
426 error = False
427 info_add = info.add
428
429 for collect_func in (
430 # collect_os() should be the first, to check the getrandom() status
431 collect_os,
432
433 collect_gdb,
434 collect_locale,
435 collect_platform,
436 collect_readline,
437 collect_socket,
438 collect_sqlite,
439 collect_ssl,
440 collect_sys,
441 collect_sysconfig,
442 collect_time,
443 collect_tkinter,
444 collect_zlib,
Victor Stinnerf6ebd832017-08-17 22:13:11 +0200445 collect_expat,
446 collect_decimal,
Victor Stinnerb907abc2017-08-17 16:40:51 +0200447 ):
448 try:
449 collect_func(info_add)
450 except Exception as exc:
451 error = True
452 print("ERROR: %s() failed" % (collect_func.__name__),
453 file=sys.stderr)
454 traceback.print_exc(file=sys.stderr)
455 print(file=sys.stderr)
456 sys.stderr.flush()
457
458 return error
459
460
461def dump_info(info, file=None):
462 title = "Python debug information"
463 print(title)
464 print("=" * len(title))
465 print()
466
467 infos = info.get_infos()
468 infos = sorted(infos.items())
469 for key, value in infos:
470 value = value.replace("\n", " ")
471 print("%s: %s" % (key, value))
472 print()
473
474
475def main():
476 info = PythonInfo()
477 error = collect_info(info)
478 dump_info(info)
479
480 if error:
481 print("Collection failed: exit with error", file=sys.stderr)
482 sys.exit(1)
483
484
485if __name__ == "__main__":
486 main()