blob: 5447ab8274b4e7d95eb4d04bf2c8a8350073d574 [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 Stinnerb907abc2017-08-17 16:40:51 +0200118
119def collect_platform(info_add):
120 import platform
121
122 arch = platform.architecture()
123 arch = ' '.join(filter(bool, arch))
124 info_add('platform.architecture', arch)
125
126 info_add('platform.python_implementation',
127 platform.python_implementation())
128 info_add('platform.platform',
129 platform.platform(aliased=True))
130
131
132def collect_locale(info_add):
133 import locale
134
135 info_add('locale.encoding', locale.getpreferredencoding(False))
136
137
138def collect_os(info_add):
139 import os
140
Victor Stinnerad7eaed2017-08-18 12:08:47 +0200141 def format_attr(attr, value):
142 if attr in ('supports_follow_symlinks', 'supports_fd',
143 'supports_effective_ids'):
144 return str(sorted(func.__name__ for func in value))
145 else:
146 return value
147
148 attributes = (
149 'name',
150 'supports_bytes_environ',
151 'supports_effective_ids',
152 'supports_fd',
153 'supports_follow_symlinks',
154 )
155 copy_attributes(info_add, os, 'os.%s', attributes, formatter=format_attr)
Victor Stinnerb907abc2017-08-17 16:40:51 +0200156
157 info_add("os.cwd", os.getcwd())
158
Victor Stinnerad7eaed2017-08-18 12:08:47 +0200159 call_func(info_add, 'os.uid', os, 'getuid')
160 call_func(info_add, 'os.gid', os, 'getgid')
161 call_func(info_add, 'os.uname', os, 'uname')
Victor Stinnerb907abc2017-08-17 16:40:51 +0200162
163 if hasattr(os, 'getgroups'):
164 groups = os.getgroups()
165 groups = map(str, groups)
166 groups = ', '.join(groups)
167 info_add("os.groups", groups)
168
169 if hasattr(os, 'getlogin'):
170 try:
171 login = os.getlogin()
172 except OSError:
173 # getlogin() fails with "OSError: [Errno 25] Inappropriate ioctl
174 # for device" on Travis CI
175 pass
176 else:
177 info_add("os.login", login)
178
179 if hasattr(os, 'cpu_count'):
180 cpu_count = os.cpu_count()
181 if cpu_count:
182 info_add('os.cpu_count', cpu_count)
183
Victor Stinnerad7eaed2017-08-18 12:08:47 +0200184 call_func(info_add, 'os.loadavg', os, 'getloadavg')
Victor Stinnerb907abc2017-08-17 16:40:51 +0200185
186 # Get environment variables: filter to list
187 # to not leak sensitive information
188 ENV_VARS = (
189 "CC",
190 "COMSPEC",
191 "DISPLAY",
192 "DISTUTILS_USE_SDK",
193 "DYLD_LIBRARY_PATH",
194 "HOME",
195 "HOMEDRIVE",
196 "HOMEPATH",
197 "LANG",
198 "LD_LIBRARY_PATH",
199 "MACOSX_DEPLOYMENT_TARGET",
200 "MAKEFLAGS",
201 "MSSDK",
202 "PATH",
203 "SDK_TOOLS_BIN",
204 "SHELL",
205 "TEMP",
206 "TERM",
207 "TMP",
208 "TMPDIR",
209 "USERPROFILE",
210 "WAYLAND_DISPLAY",
211 )
212 for name, value in os.environ.items():
213 uname = name.upper()
214 if (uname in ENV_VARS or uname.startswith(("PYTHON", "LC_"))
215 # Visual Studio: VS140COMNTOOLS
216 or (uname.startswith("VS") and uname.endswith("COMNTOOLS"))):
217 info_add('os.environ[%s]' % name, value)
218
Victor Stinnerad7eaed2017-08-18 12:08:47 +0200219 if hasattr(os, 'umask'):
220 mask = os.umask(0)
221 os.umask(mask)
222 info_add("os.umask", '%03o' % mask)
223
224 if hasattr(os, 'getrandom'):
225 # PEP 524: Check if system urandom is initialized
226 try:
Victor Stinnera92941f2017-09-19 07:37:24 -0700227 try:
228 os.getrandom(1, os.GRND_NONBLOCK)
229 state = 'ready (initialized)'
230 except BlockingIOError as exc:
231 state = 'not seeded yet (%s)' % exc
232 info_add('os.getrandom', state)
233 except OSError as exc:
234 # Python was compiled on a more recent Linux version
235 # than the current Linux kernel: ignore OSError(ENOSYS)
236 if exc.errno != errno.ENOSYS:
237 raise
Victor Stinnerad7eaed2017-08-18 12:08:47 +0200238
Victor Stinnerb907abc2017-08-17 16:40:51 +0200239
240def collect_readline(info_add):
241 try:
242 import readline
243 except ImportError:
244 return
245
246 def format_attr(attr, value):
247 if isinstance(value, int):
248 return "%#x" % value
249 else:
250 return value
251
252 attributes = (
253 "_READLINE_VERSION",
254 "_READLINE_RUNTIME_VERSION",
255 "_READLINE_LIBRARY_VERSION",
256 )
257 copy_attributes(info_add, readline, 'readline.%s', attributes,
258 formatter=format_attr)
259
260
261def collect_gdb(info_add):
262 import subprocess
263
264 try:
265 proc = subprocess.Popen(["gdb", "-nx", "--version"],
266 stdout=subprocess.PIPE,
267 stderr=subprocess.PIPE,
268 universal_newlines=True)
269 version = proc.communicate()[0]
270 except OSError:
271 return
272
273 # Only keep the first line
274 version = version.splitlines()[0]
275 info_add('gdb_version', version)
276
277
278def collect_tkinter(info_add):
279 try:
280 import _tkinter
281 except ImportError:
282 pass
283 else:
284 attributes = ('TK_VERSION', 'TCL_VERSION')
285 copy_attributes(info_add, _tkinter, 'tkinter.%s', attributes)
286
287 try:
288 import tkinter
289 except ImportError:
290 pass
291 else:
292 tcl = tkinter.Tcl()
293 patchlevel = tcl.call('info', 'patchlevel')
294 info_add('tkinter.info_patchlevel', patchlevel)
295
296
297def collect_time(info_add):
298 import time
299
Victor Stinnerad7eaed2017-08-18 12:08:47 +0200300 attributes = (
301 'altzone',
302 'daylight',
303 'timezone',
304 'tzname',
305 )
306 copy_attributes(info_add, time, 'time.%s', attributes)
307
Victor Stinnerb907abc2017-08-17 16:40:51 +0200308 if not hasattr(time, 'get_clock_info'):
309 return
310
311 for clock in ('time', 'perf_counter'):
312 tinfo = time.get_clock_info(clock)
Victor Stinnerad7eaed2017-08-18 12:08:47 +0200313 info_add('time.%s' % clock, tinfo)
Victor Stinnerb907abc2017-08-17 16:40:51 +0200314
315
316def collect_sysconfig(info_add):
317 import sysconfig
318
319 for name in (
320 'ABIFLAGS',
321 'ANDROID_API_LEVEL',
322 'CC',
323 'CCSHARED',
324 'CFLAGS',
325 'CFLAGSFORSHARED',
326 'PY_LDFLAGS',
327 'CONFIG_ARGS',
328 'HOST_GNU_TYPE',
329 'MACHDEP',
330 'MULTIARCH',
331 'OPT',
332 'PY_CFLAGS',
333 'PY_CFLAGS_NODIST',
334 'Py_DEBUG',
335 'Py_ENABLE_SHARED',
336 'SHELL',
337 'SOABI',
338 'prefix',
339 ):
340 value = sysconfig.get_config_var(name)
341 if name == 'ANDROID_API_LEVEL' and not value:
342 # skip ANDROID_API_LEVEL=0
343 continue
344 value = normalize_text(value)
345 info_add('sysconfig[%s]' % name, value)
346
347
348def collect_ssl(info_add):
349 try:
350 import ssl
351 except ImportError:
352 return
353
354 def format_attr(attr, value):
355 if attr.startswith('OP_'):
356 return '%#8x' % value
357 else:
Victor Stinnerad7eaed2017-08-18 12:08:47 +0200358 return value
Victor Stinnerb907abc2017-08-17 16:40:51 +0200359
360 attributes = (
361 'OPENSSL_VERSION',
362 'OPENSSL_VERSION_INFO',
363 'HAS_SNI',
364 'OP_ALL',
365 'OP_NO_TLSv1_1',
366 )
367 copy_attributes(info_add, ssl, 'ssl.%s', attributes, formatter=format_attr)
368
369
370def collect_socket(info_add):
371 import socket
372
373 hostname = socket.gethostname()
374 info_add('socket.hostname', hostname)
375
376
377def collect_sqlite(info_add):
378 try:
379 import sqlite3
380 except ImportError:
381 return
382
383 attributes = ('version', 'sqlite_version')
384 copy_attributes(info_add, sqlite3, 'sqlite3.%s', attributes)
385
386
387def collect_zlib(info_add):
388 try:
389 import zlib
390 except ImportError:
391 return
392
393 attributes = ('ZLIB_VERSION', 'ZLIB_RUNTIME_VERSION')
394 copy_attributes(info_add, zlib, 'zlib.%s', attributes)
395
396
Victor Stinnerf6ebd832017-08-17 22:13:11 +0200397def collect_expat(info_add):
398 try:
399 from xml.parsers import expat
400 except ImportError:
401 return
402
403 attributes = ('EXPAT_VERSION',)
404 copy_attributes(info_add, expat, 'expat.%s', attributes)
405
406
407def collect_decimal(info_add):
408 try:
409 import _decimal
410 except ImportError:
411 return
412
413 attributes = ('__libmpdec_version__',)
414 copy_attributes(info_add, _decimal, '_decimal.%s', attributes)
415
416
Victor Stinnerb907abc2017-08-17 16:40:51 +0200417def collect_info(info):
418 error = False
419 info_add = info.add
420
421 for collect_func in (
422 # collect_os() should be the first, to check the getrandom() status
423 collect_os,
424
425 collect_gdb,
426 collect_locale,
427 collect_platform,
428 collect_readline,
429 collect_socket,
430 collect_sqlite,
431 collect_ssl,
432 collect_sys,
433 collect_sysconfig,
434 collect_time,
435 collect_tkinter,
436 collect_zlib,
Victor Stinnerf6ebd832017-08-17 22:13:11 +0200437 collect_expat,
438 collect_decimal,
Victor Stinnerb907abc2017-08-17 16:40:51 +0200439 ):
440 try:
441 collect_func(info_add)
442 except Exception as exc:
443 error = True
444 print("ERROR: %s() failed" % (collect_func.__name__),
445 file=sys.stderr)
446 traceback.print_exc(file=sys.stderr)
447 print(file=sys.stderr)
448 sys.stderr.flush()
449
450 return error
451
452
453def dump_info(info, file=None):
454 title = "Python debug information"
455 print(title)
456 print("=" * len(title))
457 print()
458
459 infos = info.get_infos()
460 infos = sorted(infos.items())
461 for key, value in infos:
462 value = value.replace("\n", " ")
463 print("%s: %s" % (key, value))
464 print()
465
466
467def main():
468 info = PythonInfo()
469 error = collect_info(info)
470 dump_info(info)
471
472 if error:
473 print("Collection failed: exit with error", file=sys.stderr)
474 sys.exit(1)
475
476
477if __name__ == "__main__":
478 main()