blob: 8b029330a2ee507a1a9b9853c2fa05eccb19ea54 [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
Victor Stinner180372c2017-11-27 10:44:50 +0100107 # Were we compiled --with-pydebug or with #define Py_DEBUG?
108 Py_DEBUG = hasattr(sys, 'gettotalrefcount')
109 if Py_DEBUG:
110 text = 'Yes (sys.gettotalrefcount() present)'
111 else:
112 text = 'No (sys.gettotalrefcount() missing)'
113 info_add('Py_DEBUG', text)
114
Victor Stinnercce1cb92017-08-22 03:40:26 +0200115
116def collect_platform(info_add):
117 import platform
118
119 arch = platform.architecture()
120 arch = ' '.join(filter(bool, arch))
121 info_add('platform.architecture', arch)
122
123 info_add('platform.python_implementation',
124 platform.python_implementation())
125 info_add('platform.platform',
126 platform.platform(aliased=True))
127
128
129def collect_locale(info_add):
130 import locale
131
132 info_add('locale.encoding', locale.getpreferredencoding(True))
133
134
135def collect_os(info_add):
136 import os
137
138 attributes = ('name',)
139 copy_attributes(info_add, os, 'os.%s', attributes)
140
141 info_add("os.cwd", os.getcwd())
142
143 call_func(info_add, 'os.uid', os, 'getuid')
144 call_func(info_add, 'os.gid', os, 'getgid')
145 call_func(info_add, 'os.uname', os, 'uname')
146
147 if hasattr(os, 'getgroups'):
148 groups = os.getgroups()
149 groups = map(str, groups)
150 groups = ', '.join(groups)
151 info_add("os.groups", groups)
152
153 if hasattr(os, 'getlogin'):
154 try:
155 login = os.getlogin()
156 except OSError:
157 # getlogin() fails with "OSError: [Errno 25] Inappropriate ioctl
158 # for device" on Travis CI
159 pass
160 else:
161 info_add("os.login", login)
162
163 call_func(info_add, 'os.loadavg', os, 'getloadavg')
164
165 # Get environment variables: filter to list
166 # to not leak sensitive information
167 ENV_VARS = (
168 "CC",
169 "COMSPEC",
170 "DISPLAY",
171 "DISTUTILS_USE_SDK",
172 "DYLD_LIBRARY_PATH",
173 "HOME",
174 "HOMEDRIVE",
175 "HOMEPATH",
176 "LANG",
177 "LD_LIBRARY_PATH",
178 "MACOSX_DEPLOYMENT_TARGET",
179 "MAKEFLAGS",
180 "MSSDK",
181 "PATH",
182 "SDK_TOOLS_BIN",
183 "SHELL",
184 "TEMP",
185 "TERM",
186 "TMP",
187 "TMPDIR",
188 "USERPROFILE",
189 "WAYLAND_DISPLAY",
190 )
191 for name, value in os.environ.items():
192 uname = name.upper()
193 if (uname in ENV_VARS or uname.startswith(("PYTHON", "LC_"))
194 # Visual Studio: VS140COMNTOOLS
195 or (uname.startswith("VS") and uname.endswith("COMNTOOLS"))):
196 info_add('os.environ[%s]' % name, value)
197
198 if hasattr(os, 'umask'):
199 mask = os.umask(0)
200 os.umask(mask)
201 info_add("os.umask", '%03o' % mask)
202
203 try:
204 cpu_count = os.sysconf('SC_NPROCESSORS_ONLN')
205 except (AttributeError, ValueError):
206 pass
207 else:
208 if cpu_count:
209 info_add('os.sysconf(SC_NPROCESSORS_ONLN)', cpu_count)
210
211
212def collect_readline(info_add):
213 try:
214 import readline
215 except ImportError:
216 return
217
218 def format_attr(attr, value):
219 if isinstance(value, (int, long)):
220 return "%#x" % value
221 else:
222 return value
223
224 attributes = (
225 "_READLINE_VERSION",
226 "_READLINE_RUNTIME_VERSION",
227 )
228 copy_attributes(info_add, readline, 'readline.%s', attributes,
229 formatter=format_attr)
230
231
232def collect_gdb(info_add):
233 import subprocess
234
235 try:
236 proc = subprocess.Popen(["gdb", "-nx", "--version"],
237 stdout=subprocess.PIPE,
238 stderr=subprocess.PIPE,
239 universal_newlines=True)
240 version = proc.communicate()[0]
241 except OSError:
242 return
243
244 # Only keep the first line
245 version = version.splitlines()[0]
246 info_add('gdb_version', version)
247
248
249def collect_tkinter(info_add):
250 try:
251 import _tkinter
252 except ImportError:
253 pass
254 else:
255 attributes = ('TK_VERSION', 'TCL_VERSION')
256 copy_attributes(info_add, _tkinter, 'tkinter.%s', attributes)
257
258 try:
259 import Tkinter
260 except ImportError:
261 pass
262 else:
263 tcl = Tkinter.Tcl()
264 patchlevel = tcl.call('info', 'patchlevel')
265 info_add('tkinter.info_patchlevel', patchlevel)
266
267
268def collect_time(info_add):
269 import time
270
271 attributes = (
272 'altzone',
273 'daylight',
274 'timezone',
275 'tzname',
276 )
277 copy_attributes(info_add, time, 'time.%s', attributes)
278
279
280def collect_sysconfig(info_add):
281 import sysconfig
282
283 for name in (
284 'ABIFLAGS',
285 'ANDROID_API_LEVEL',
286 'CC',
287 'CCSHARED',
288 'CFLAGS',
289 'CFLAGSFORSHARED',
290 'PY_LDFLAGS',
291 'CONFIG_ARGS',
292 'HOST_GNU_TYPE',
293 'MACHDEP',
294 'MULTIARCH',
295 'OPT',
296 'PY_CFLAGS',
297 'PY_CFLAGS_NODIST',
298 'Py_DEBUG',
299 'Py_ENABLE_SHARED',
300 'SHELL',
301 'SOABI',
302 'prefix',
303 ):
304 value = sysconfig.get_config_var(name)
305 if name == 'ANDROID_API_LEVEL' and not value:
306 # skip ANDROID_API_LEVEL=0
307 continue
308 value = normalize_text(value)
309 info_add('sysconfig[%s]' % name, value)
310
311
312def collect_ssl(info_add):
313 try:
314 import ssl
315 except ImportError:
316 return
317
318 def format_attr(attr, value):
319 if attr.startswith('OP_'):
320 return '%#8x' % value
321 else:
322 return value
323
324 attributes = (
325 'OPENSSL_VERSION',
326 'OPENSSL_VERSION_INFO',
327 'HAS_SNI',
328 'OP_ALL',
329 'OP_NO_TLSv1_1',
330 )
331 copy_attributes(info_add, ssl, 'ssl.%s', attributes, formatter=format_attr)
332
333
334def collect_socket(info_add):
335 import socket
336
337 hostname = socket.gethostname()
338 info_add('socket.hostname', hostname)
339
340
341def collect_sqlite(info_add):
342 try:
343 import sqlite3
344 except ImportError:
345 return
346
347 attributes = ('version', 'sqlite_version')
348 copy_attributes(info_add, sqlite3, 'sqlite3.%s', attributes)
349
350
351def collect_zlib(info_add):
352 try:
353 import zlib
354 except ImportError:
355 return
356
357 attributes = ('ZLIB_VERSION', 'ZLIB_RUNTIME_VERSION')
358 copy_attributes(info_add, zlib, 'zlib.%s', attributes)
359
360
361def collect_expat(info_add):
362 try:
363 from xml.parsers import expat
364 except ImportError:
365 return
366
367 attributes = ('EXPAT_VERSION',)
368 copy_attributes(info_add, expat, 'expat.%s', attributes)
369
370
371def collect_multiprocessing(info_add):
372 try:
373 import multiprocessing
374 except ImportError:
375 return
376
377 cpu_count = multiprocessing.cpu_count()
378 if cpu_count:
379 info_add('multiprocessing.cpu_count', cpu_count)
380
381
382def collect_info(info):
383 error = False
384 info_add = info.add
385
386 for collect_func in (
387 collect_expat,
388 collect_gdb,
389 collect_locale,
390 collect_os,
391 collect_platform,
392 collect_readline,
393 collect_socket,
394 collect_sqlite,
395 collect_ssl,
396 collect_sys,
397 collect_sysconfig,
398 collect_time,
399 collect_tkinter,
400 collect_zlib,
401 collect_multiprocessing,
402 ):
403 try:
404 collect_func(info_add)
405 except Exception as exc:
406 error = True
407 print("ERROR: %s() failed" % (collect_func.__name__),
408 file=sys.stderr)
409 traceback.print_exc(file=sys.stderr)
410 print(file=sys.stderr)
411 sys.stderr.flush()
412
413 return error
414
415
416def dump_info(info, file=None):
417 title = "Python debug information"
418 print(title)
419 print("=" * len(title))
420 print()
421
422 infos = info.get_infos()
423 infos = sorted(infos.items())
424 for key, value in infos:
425 value = value.replace("\n", " ")
426 print("%s: %s" % (key, value))
427 print()
428
429
430def main():
431 info = PythonInfo()
432 error = collect_info(info)
433 dump_info(info)
434
435 if error:
436 print("Collection failed: exit with error", file=sys.stderr)
437 sys.exit(1)
438
439
440if __name__ == "__main__":
441 main()