blob: 83b1ec726e4a1031c7bb849fcfac38fa83ace5c9 [file] [log] [blame]
Victor Stinnercce1cb92017-08-22 03:40:26 +02001"""
2Collect various informations about Python to help debugging test failures.
3"""
4from __future__ import print_function
5import re
6import sys
7import traceback
8
9
10def normalize_text(text):
11 if text is None:
12 return None
13 text = str(text)
14 text = re.sub(r'\s+', ' ', text)
15 return text.strip()
16
17
18class PythonInfo:
19 def __init__(self):
20 self.info = {}
21
22 def add(self, key, value):
23 if key in self.info:
24 raise ValueError("duplicate key: %r" % key)
25
26 if value is None:
27 return
28
29 if not isinstance(value, (int, long)):
30 if not isinstance(value, basestring):
31 # convert other objects like sys.flags to string
32 value = str(value)
33
34 value = value.strip()
35 if not value:
36 return
37
38 self.info[key] = value
39
40 def get_infos(self):
41 """
42 Get informations as a key:value dictionary where values are strings.
43 """
44 return {key: str(value) for key, value in self.info.items()}
45
46
47def copy_attributes(info_add, obj, name_fmt, attributes, formatter=None):
48 for attr in attributes:
49 value = getattr(obj, attr, None)
50 if value is None:
51 continue
52 name = name_fmt % attr
53 if formatter is not None:
54 value = formatter(attr, value)
55 info_add(name, value)
56
57
58def call_func(info_add, name, mod, func_name, formatter=None):
59 try:
60 func = getattr(mod, func_name)
61 except AttributeError:
62 return
63 value = func()
64 if formatter is not None:
65 value = formatter(value)
66 info_add(name, value)
67
68
69def collect_sys(info_add):
70 attributes = (
71 'api_version',
72 'builtin_module_names',
73 'byteorder',
74 'dont_write_bytecode',
75 'executable',
76 'flags',
77 'float_info',
78 'float_repr_style',
79 'hexversion',
80 'maxint',
81 'maxsize',
82 'maxunicode',
83 'path',
84 'platform',
85 'prefix',
86 'version',
87 'version_info',
88 'winver',
89 )
90 copy_attributes(info_add, sys, 'sys.%s', attributes)
91
92 encoding = sys.getfilesystemencoding()
93 info_add('sys.filesystem_encoding', encoding)
94
95 for name in ('stdin', 'stdout', 'stderr'):
96 stream = getattr(sys, name)
97 if stream is None:
98 continue
99 encoding = getattr(stream, 'encoding', None)
100 if not encoding:
101 continue
102 errors = getattr(stream, 'errors', None)
103 if errors:
104 encoding = '%s/%s' % (encoding, errors)
105 info_add('sys.%s.encoding' % name, encoding)
106
107
108def collect_platform(info_add):
109 import platform
110
111 arch = platform.architecture()
112 arch = ' '.join(filter(bool, arch))
113 info_add('platform.architecture', arch)
114
115 info_add('platform.python_implementation',
116 platform.python_implementation())
117 info_add('platform.platform',
118 platform.platform(aliased=True))
119
120
121def collect_locale(info_add):
122 import locale
123
124 info_add('locale.encoding', locale.getpreferredencoding(True))
125
126
127def collect_os(info_add):
128 import os
129
130 attributes = ('name',)
131 copy_attributes(info_add, os, 'os.%s', attributes)
132
133 info_add("os.cwd", os.getcwd())
134
135 call_func(info_add, 'os.uid', os, 'getuid')
136 call_func(info_add, 'os.gid', os, 'getgid')
137 call_func(info_add, 'os.uname', os, 'uname')
138
139 if hasattr(os, 'getgroups'):
140 groups = os.getgroups()
141 groups = map(str, groups)
142 groups = ', '.join(groups)
143 info_add("os.groups", groups)
144
145 if hasattr(os, 'getlogin'):
146 try:
147 login = os.getlogin()
148 except OSError:
149 # getlogin() fails with "OSError: [Errno 25] Inappropriate ioctl
150 # for device" on Travis CI
151 pass
152 else:
153 info_add("os.login", login)
154
155 call_func(info_add, 'os.loadavg', os, 'getloadavg')
156
157 # Get environment variables: filter to list
158 # to not leak sensitive information
159 ENV_VARS = (
160 "CC",
161 "COMSPEC",
162 "DISPLAY",
163 "DISTUTILS_USE_SDK",
164 "DYLD_LIBRARY_PATH",
165 "HOME",
166 "HOMEDRIVE",
167 "HOMEPATH",
168 "LANG",
169 "LD_LIBRARY_PATH",
170 "MACOSX_DEPLOYMENT_TARGET",
171 "MAKEFLAGS",
172 "MSSDK",
173 "PATH",
174 "SDK_TOOLS_BIN",
175 "SHELL",
176 "TEMP",
177 "TERM",
178 "TMP",
179 "TMPDIR",
180 "USERPROFILE",
181 "WAYLAND_DISPLAY",
182 )
183 for name, value in os.environ.items():
184 uname = name.upper()
185 if (uname in ENV_VARS or uname.startswith(("PYTHON", "LC_"))
186 # Visual Studio: VS140COMNTOOLS
187 or (uname.startswith("VS") and uname.endswith("COMNTOOLS"))):
188 info_add('os.environ[%s]' % name, value)
189
190 if hasattr(os, 'umask'):
191 mask = os.umask(0)
192 os.umask(mask)
193 info_add("os.umask", '%03o' % mask)
194
195 try:
196 cpu_count = os.sysconf('SC_NPROCESSORS_ONLN')
197 except (AttributeError, ValueError):
198 pass
199 else:
200 if cpu_count:
201 info_add('os.sysconf(SC_NPROCESSORS_ONLN)', cpu_count)
202
203
204def collect_readline(info_add):
205 try:
206 import readline
207 except ImportError:
208 return
209
210 def format_attr(attr, value):
211 if isinstance(value, (int, long)):
212 return "%#x" % value
213 else:
214 return value
215
216 attributes = (
217 "_READLINE_VERSION",
218 "_READLINE_RUNTIME_VERSION",
219 )
220 copy_attributes(info_add, readline, 'readline.%s', attributes,
221 formatter=format_attr)
222
223
224def collect_gdb(info_add):
225 import subprocess
226
227 try:
228 proc = subprocess.Popen(["gdb", "-nx", "--version"],
229 stdout=subprocess.PIPE,
230 stderr=subprocess.PIPE,
231 universal_newlines=True)
232 version = proc.communicate()[0]
233 except OSError:
234 return
235
236 # Only keep the first line
237 version = version.splitlines()[0]
238 info_add('gdb_version', version)
239
240
241def collect_tkinter(info_add):
242 try:
243 import _tkinter
244 except ImportError:
245 pass
246 else:
247 attributes = ('TK_VERSION', 'TCL_VERSION')
248 copy_attributes(info_add, _tkinter, 'tkinter.%s', attributes)
249
250 try:
251 import Tkinter
252 except ImportError:
253 pass
254 else:
255 tcl = Tkinter.Tcl()
256 patchlevel = tcl.call('info', 'patchlevel')
257 info_add('tkinter.info_patchlevel', patchlevel)
258
259
260def collect_time(info_add):
261 import time
262
263 attributes = (
264 'altzone',
265 'daylight',
266 'timezone',
267 'tzname',
268 )
269 copy_attributes(info_add, time, 'time.%s', attributes)
270
271
272def collect_sysconfig(info_add):
273 import sysconfig
274
275 for name in (
276 'ABIFLAGS',
277 'ANDROID_API_LEVEL',
278 'CC',
279 'CCSHARED',
280 'CFLAGS',
281 'CFLAGSFORSHARED',
282 'PY_LDFLAGS',
283 'CONFIG_ARGS',
284 'HOST_GNU_TYPE',
285 'MACHDEP',
286 'MULTIARCH',
287 'OPT',
288 'PY_CFLAGS',
289 'PY_CFLAGS_NODIST',
290 'Py_DEBUG',
291 'Py_ENABLE_SHARED',
292 'SHELL',
293 'SOABI',
294 'prefix',
295 ):
296 value = sysconfig.get_config_var(name)
297 if name == 'ANDROID_API_LEVEL' and not value:
298 # skip ANDROID_API_LEVEL=0
299 continue
300 value = normalize_text(value)
301 info_add('sysconfig[%s]' % name, value)
302
303
304def collect_ssl(info_add):
305 try:
306 import ssl
307 except ImportError:
308 return
309
310 def format_attr(attr, value):
311 if attr.startswith('OP_'):
312 return '%#8x' % value
313 else:
314 return value
315
316 attributes = (
317 'OPENSSL_VERSION',
318 'OPENSSL_VERSION_INFO',
319 'HAS_SNI',
320 'OP_ALL',
321 'OP_NO_TLSv1_1',
322 )
323 copy_attributes(info_add, ssl, 'ssl.%s', attributes, formatter=format_attr)
324
325
326def collect_socket(info_add):
327 import socket
328
329 hostname = socket.gethostname()
330 info_add('socket.hostname', hostname)
331
332
333def collect_sqlite(info_add):
334 try:
335 import sqlite3
336 except ImportError:
337 return
338
339 attributes = ('version', 'sqlite_version')
340 copy_attributes(info_add, sqlite3, 'sqlite3.%s', attributes)
341
342
343def collect_zlib(info_add):
344 try:
345 import zlib
346 except ImportError:
347 return
348
349 attributes = ('ZLIB_VERSION', 'ZLIB_RUNTIME_VERSION')
350 copy_attributes(info_add, zlib, 'zlib.%s', attributes)
351
352
353def collect_expat(info_add):
354 try:
355 from xml.parsers import expat
356 except ImportError:
357 return
358
359 attributes = ('EXPAT_VERSION',)
360 copy_attributes(info_add, expat, 'expat.%s', attributes)
361
362
363def collect_multiprocessing(info_add):
364 try:
365 import multiprocessing
366 except ImportError:
367 return
368
369 cpu_count = multiprocessing.cpu_count()
370 if cpu_count:
371 info_add('multiprocessing.cpu_count', cpu_count)
372
373
374def collect_info(info):
375 error = False
376 info_add = info.add
377
378 for collect_func in (
379 collect_expat,
380 collect_gdb,
381 collect_locale,
382 collect_os,
383 collect_platform,
384 collect_readline,
385 collect_socket,
386 collect_sqlite,
387 collect_ssl,
388 collect_sys,
389 collect_sysconfig,
390 collect_time,
391 collect_tkinter,
392 collect_zlib,
393 collect_multiprocessing,
394 ):
395 try:
396 collect_func(info_add)
397 except Exception as exc:
398 error = True
399 print("ERROR: %s() failed" % (collect_func.__name__),
400 file=sys.stderr)
401 traceback.print_exc(file=sys.stderr)
402 print(file=sys.stderr)
403 sys.stderr.flush()
404
405 return error
406
407
408def dump_info(info, file=None):
409 title = "Python debug information"
410 print(title)
411 print("=" * len(title))
412 print()
413
414 infos = info.get_infos()
415 infos = sorted(infos.items())
416 for key, value in infos:
417 value = value.replace("\n", " ")
418 print("%s: %s" % (key, value))
419 print()
420
421
422def main():
423 info = PythonInfo()
424 error = collect_info(info)
425 dump_info(info)
426
427 if error:
428 print("Collection failed: exit with error", file=sys.stderr)
429 sys.exit(1)
430
431
432if __name__ == "__main__":
433 main()