[Python-checkins] cpython (merge 2.7 -> 2.7): merge heads.
senthil.kumaran
python-checkins at python.org
Thu Sep 3 11:51:28 CEST 2015
https://hg.python.org/cpython/rev/d687912d499f
changeset: 97616:d687912d499f
branch: 2.7
parent: 97615:cb781d3b1e6b
parent: 97611:d739bc20d7b2
user: Senthil Kumaran <senthil at uthcode.com>
date: Thu Sep 03 02:50:51 2015 -0700
summary:
merge heads.
files:
Lib/test/test_gdb.py | 168 +++++++++++++++++++++--
Lib/test/test_py3kwarn.py | 16 ++
Tools/gdb/libpython.py | 183 ++++++++++++++++++++++++-
3 files changed, 338 insertions(+), 29 deletions(-)
diff --git a/Lib/test/test_gdb.py b/Lib/test/test_gdb.py
--- a/Lib/test/test_gdb.py
+++ b/Lib/test/test_gdb.py
@@ -10,21 +10,42 @@
import unittest
import sysconfig
+from test import test_support
from test.test_support import run_unittest, findfile
+# Is this Python configured to support threads?
try:
- gdb_version, _ = subprocess.Popen(["gdb", "-nx", "--version"],
- stdout=subprocess.PIPE).communicate()
-except OSError:
- # This is what "no gdb" looks like. There may, however, be other
- # errors that manifest this way too.
- raise unittest.SkipTest("Couldn't find gdb on the path")
-gdb_version_number = re.search("^GNU gdb [^\d]*(\d+)\.(\d)", gdb_version)
-gdb_major_version = int(gdb_version_number.group(1))
-gdb_minor_version = int(gdb_version_number.group(2))
+ import thread
+except ImportError:
+ thread = None
+
+def get_gdb_version():
+ try:
+ proc = subprocess.Popen(["gdb", "-nx", "--version"],
+ stdout=subprocess.PIPE,
+ universal_newlines=True)
+ version = proc.communicate()[0]
+ except OSError:
+ # This is what "no gdb" looks like. There may, however, be other
+ # errors that manifest this way too.
+ raise unittest.SkipTest("Couldn't find gdb on the path")
+
+ # Regex to parse:
+ # 'GNU gdb (GDB; SUSE Linux Enterprise 12) 7.7\n' -> 7.7
+ # 'GNU gdb (GDB) Fedora 7.9.1-17.fc22\n' -> 7.9
+ # 'GNU gdb 6.1.1 [FreeBSD]\n'
+ match = re.search("^GNU gdb.*? (\d+)\.(\d)", version)
+ if match is None:
+ raise Exception("unable to parse GDB version: %r" % version)
+ return (version, int(match.group(1)), int(match.group(2)))
+
+gdb_version, gdb_major_version, gdb_minor_version = get_gdb_version()
if gdb_major_version < 7:
- raise unittest.SkipTest("gdb versions before 7.0 didn't support python embedding"
- " Saw:\n" + gdb_version)
+ raise unittest.SkipTest("gdb versions before 7.0 didn't support python "
+ "embedding. Saw %s.%s:\n%s"
+ % (gdb_major_version, gdb_minor_version,
+ gdb_version))
+
if sys.platform.startswith("sunos"):
raise unittest.SkipTest("test doesn't work very well on Solaris")
@@ -713,20 +734,133 @@
class PyBtTests(DebuggerTests):
@unittest.skipIf(python_is_optimized(),
"Python was compiled with optimizations")
- def test_basic_command(self):
+ def test_bt(self):
'Verify that the "py-bt" command works'
bt = self.get_stack_trace(script=self.get_sample_script(),
cmds_after_breakpoint=['py-bt'])
self.assertMultilineMatches(bt,
r'''^.*
-#[0-9]+ Frame 0x[0-9a-f]+, for file .*gdb_sample.py, line 7, in bar \(a=1, b=2, c=3\)
+Traceback \(most recent call first\):
+ File ".*gdb_sample.py", line 10, in baz
+ print\(42\)
+ File ".*gdb_sample.py", line 7, in bar
baz\(a, b, c\)
-#[0-9]+ Frame 0x[0-9a-f]+, for file .*gdb_sample.py, line 4, in foo \(a=1, b=2, c=3\)
+ File ".*gdb_sample.py", line 4, in foo
bar\(a, b, c\)
-#[0-9]+ Frame 0x[0-9a-f]+, for file .*gdb_sample.py, line 12, in <module> \(\)
+ File ".*gdb_sample.py", line 12, in <module>
foo\(1, 2, 3\)
''')
+ @unittest.skipIf(python_is_optimized(),
+ "Python was compiled with optimizations")
+ def test_bt_full(self):
+ 'Verify that the "py-bt-full" command works'
+ bt = self.get_stack_trace(script=self.get_sample_script(),
+ cmds_after_breakpoint=['py-bt-full'])
+ self.assertMultilineMatches(bt,
+ r'''^.*
+#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 7, in bar \(a=1, b=2, c=3\)
+ baz\(a, b, c\)
+#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 4, in foo \(a=1, b=2, c=3\)
+ bar\(a, b, c\)
+#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 12, in <module> \(\)
+ foo\(1, 2, 3\)
+''')
+
+ @unittest.skipUnless(thread,
+ "Python was compiled without thread support")
+ def test_threads(self):
+ 'Verify that "py-bt" indicates threads that are waiting for the GIL'
+ cmd = '''
+from threading import Thread
+
+class TestThread(Thread):
+ # These threads would run forever, but we'll interrupt things with the
+ # debugger
+ def run(self):
+ i = 0
+ while 1:
+ i += 1
+
+t = {}
+for i in range(4):
+ t[i] = TestThread()
+ t[i].start()
+
+# Trigger a breakpoint on the main thread
+print 42
+
+'''
+ # Verify with "py-bt":
+ gdb_output = self.get_stack_trace(cmd,
+ cmds_after_breakpoint=['thread apply all py-bt'])
+ self.assertIn('Waiting for the GIL', gdb_output)
+
+ # Verify with "py-bt-full":
+ gdb_output = self.get_stack_trace(cmd,
+ cmds_after_breakpoint=['thread apply all py-bt-full'])
+ self.assertIn('Waiting for the GIL', gdb_output)
+
+ @unittest.skipIf(python_is_optimized(),
+ "Python was compiled with optimizations")
+ # Some older versions of gdb will fail with
+ # "Cannot find new threads: generic error"
+ # unless we add LD_PRELOAD=PATH-TO-libpthread.so.1 as a workaround
+ @unittest.skipUnless(thread,
+ "Python was compiled without thread support")
+ def test_gc(self):
+ 'Verify that "py-bt" indicates if a thread is garbage-collecting'
+ cmd = ('from gc import collect\n'
+ 'print 42\n'
+ 'def foo():\n'
+ ' collect()\n'
+ 'def bar():\n'
+ ' foo()\n'
+ 'bar()\n')
+ # Verify with "py-bt":
+ gdb_output = self.get_stack_trace(cmd,
+ cmds_after_breakpoint=['break update_refs', 'continue', 'py-bt'],
+ )
+ self.assertIn('Garbage-collecting', gdb_output)
+
+ # Verify with "py-bt-full":
+ gdb_output = self.get_stack_trace(cmd,
+ cmds_after_breakpoint=['break update_refs', 'continue', 'py-bt-full'],
+ )
+ self.assertIn('Garbage-collecting', gdb_output)
+
+ @unittest.skipIf(python_is_optimized(),
+ "Python was compiled with optimizations")
+ # Some older versions of gdb will fail with
+ # "Cannot find new threads: generic error"
+ # unless we add LD_PRELOAD=PATH-TO-libpthread.so.1 as a workaround
+ @unittest.skipUnless(thread,
+ "Python was compiled without thread support")
+ def test_pycfunction(self):
+ 'Verify that "py-bt" displays invocations of PyCFunction instances'
+ # Tested function must not be defined with METH_NOARGS or METH_O,
+ # otherwise call_function() doesn't call PyCFunction_Call()
+ cmd = ('from time import gmtime\n'
+ 'def foo():\n'
+ ' gmtime(1)\n'
+ 'def bar():\n'
+ ' foo()\n'
+ 'bar()\n')
+ # Verify with "py-bt":
+ gdb_output = self.get_stack_trace(cmd,
+ breakpoint='time_gmtime',
+ cmds_after_breakpoint=['bt', 'py-bt'],
+ )
+ self.assertIn('<built-in function gmtime', gdb_output)
+
+ # Verify with "py-bt-full":
+ gdb_output = self.get_stack_trace(cmd,
+ breakpoint='time_gmtime',
+ cmds_after_breakpoint=['py-bt-full'],
+ )
+ self.assertIn('#0 <built-in function gmtime', gdb_output)
+
+
class PyPrintTests(DebuggerTests):
@unittest.skipIf(python_is_optimized(),
"Python was compiled with optimizations")
@@ -781,6 +915,10 @@
r".*\na = 1\nb = 2\nc = 3\n.*")
def test_main():
+ if test_support.verbose:
+ print("GDB version %s.%s:" % (gdb_major_version, gdb_minor_version))
+ for line in gdb_version.splitlines():
+ print(" " * 4 + line)
run_unittest(PrettyPrintTests,
PyListTests,
StackNavigationTests,
diff --git a/Lib/test/test_py3kwarn.py b/Lib/test/test_py3kwarn.py
--- a/Lib/test/test_py3kwarn.py
+++ b/Lib/test/test_py3kwarn.py
@@ -2,6 +2,7 @@
import sys
from test.test_support import check_py3k_warnings, CleanImport, run_unittest
import warnings
+from test import test_support
if not sys.py3kwarning:
raise unittest.SkipTest('%s must be run with the -3 flag' % __name__)
@@ -356,6 +357,21 @@
def check_removal(self, module_name, optional=False):
"""Make sure the specified module, when imported, raises a
DeprecationWarning and specifies itself in the message."""
+ if module_name in sys.modules:
+ mod = sys.modules[module_name]
+ filename = getattr(mod, '__file__', '')
+ mod = None
+ # the module is not implemented in C?
+ if not filename.endswith(('.py', '.pyc', '.pyo')):
+ # Issue #23375: If the module was already loaded, reimporting
+ # the module will not emit again the warning. The warning is
+ # emited when the module is loaded, but C modules cannot
+ # unloaded.
+ if test_support.verbose:
+ print("Cannot test the Python 3 DeprecationWarning of the "
+ "%s module, the C module is already loaded"
+ % module_name)
+ return
with CleanImport(module_name), warnings.catch_warnings():
warnings.filterwarnings("error", ".+ (module|package) .+ removed",
DeprecationWarning, __name__)
diff --git a/Tools/gdb/libpython.py b/Tools/gdb/libpython.py
--- a/Tools/gdb/libpython.py
+++ b/Tools/gdb/libpython.py
@@ -45,6 +45,7 @@
from __future__ import print_function, with_statement
import gdb
+import locale
import os
import sys
@@ -76,6 +77,8 @@
MAX_OUTPUT_LEN=1024
+ENCODING = locale.getpreferredencoding()
+
class NullPyObjectPtr(RuntimeError):
pass
@@ -92,6 +95,18 @@
# threshold in case the data was corrupted
return xrange(safety_limit(int(val)))
+if sys.version_info[0] >= 3:
+ def write_unicode(file, text):
+ file.write(text)
+else:
+ def write_unicode(file, text):
+ # Write a byte or unicode string to file. Unicode strings are encoded to
+ # ENCODING encoding with 'backslashreplace' error handler to avoid
+ # UnicodeEncodeError.
+ if isinstance(text, unicode):
+ text = text.encode(ENCODING, 'backslashreplace')
+ file.write(text)
+
class StringTruncated(RuntimeError):
pass
@@ -903,7 +918,12 @@
newline character'''
if self.is_optimized_out():
return '(frame information optimized out)'
- with open(self.filename(), 'r') as f:
+ filename = self.filename()
+ try:
+ f = open(filename, 'r')
+ except IOError:
+ return None
+ with f:
all_lines = f.readlines()
# Convert from 1-based current_line_num to 0-based list offset:
return all_lines[self.current_line_num()-1]
@@ -914,9 +934,9 @@
return
out.write('Frame 0x%x, for file %s, line %i, in %s ('
% (self.as_address(),
- self.co_filename,
+ self.co_filename.proxyval(visited),
self.current_line_num(),
- self.co_name))
+ self.co_name.proxyval(visited)))
first = True
for pyop_name, pyop_value in self.iter_locals():
if not first:
@@ -929,6 +949,16 @@
out.write(')')
+ def print_traceback(self):
+ if self.is_optimized_out():
+ sys.stdout.write(' (frame information optimized out)\n')
+ return
+ visited = set()
+ sys.stdout.write(' File "%s", line %i, in %s\n'
+ % (self.co_filename.proxyval(visited),
+ self.current_line_num(),
+ self.co_name.proxyval(visited)))
+
class PySetObjectPtr(PyObjectPtr):
_typename = 'PySetObject'
@@ -1222,6 +1252,23 @@
iter_frame = iter_frame.newer()
return index
+ # We divide frames into:
+ # - "python frames":
+ # - "bytecode frames" i.e. PyEval_EvalFrameEx
+ # - "other python frames": things that are of interest from a python
+ # POV, but aren't bytecode (e.g. GC, GIL)
+ # - everything else
+
+ def is_python_frame(self):
+ '''Is this a PyEval_EvalFrameEx frame, or some other important
+ frame? (see is_other_python_frame for what "important" means in this
+ context)'''
+ if self.is_evalframeex():
+ return True
+ if self.is_other_python_frame():
+ return True
+ return False
+
def is_evalframeex(self):
'''Is this a PyEval_EvalFrameEx frame?'''
if self._gdbframe.name() == 'PyEval_EvalFrameEx':
@@ -1238,6 +1285,50 @@
return False
+ def is_other_python_frame(self):
+ '''Is this frame worth displaying in python backtraces?
+ Examples:
+ - waiting on the GIL
+ - garbage-collecting
+ - within a CFunction
+ If it is, return a descriptive string
+ For other frames, return False
+ '''
+ if self.is_waiting_for_gil():
+ return 'Waiting for the GIL'
+ elif self.is_gc_collect():
+ return 'Garbage-collecting'
+ else:
+ # Detect invocations of PyCFunction instances:
+ older = self.older()
+ if older and older._gdbframe.name() == 'PyCFunction_Call':
+ # Within that frame:
+ # "func" is the local containing the PyObject* of the
+ # PyCFunctionObject instance
+ # "f" is the same value, but cast to (PyCFunctionObject*)
+ # "self" is the (PyObject*) of the 'self'
+ try:
+ # Use the prettyprinter for the func:
+ func = older._gdbframe.read_var('func')
+ return str(func)
+ except RuntimeError:
+ return 'PyCFunction invocation (unable to read "func")'
+
+ # This frame isn't worth reporting:
+ return False
+
+ def is_waiting_for_gil(self):
+ '''Is this frame waiting on the GIL?'''
+ # This assumes the _POSIX_THREADS version of Python/ceval_gil.h:
+ name = self._gdbframe.name()
+ if name:
+ return ('PyThread_acquire_lock' in name
+ and 'lock_PyThread_acquire_lock' not in name)
+
+ def is_gc_collect(self):
+ '''Is this frame "collect" within the garbage-collector?'''
+ return self._gdbframe.name() == 'collect'
+
def get_pyop(self):
try:
f = self._gdbframe.read_var('f')
@@ -1267,8 +1358,22 @@
@classmethod
def get_selected_python_frame(cls):
- '''Try to obtain the Frame for the python code in the selected frame,
- or None'''
+ '''Try to obtain the Frame for the python-related code in the selected
+ frame, or None'''
+ frame = cls.get_selected_frame()
+
+ while frame:
+ if frame.is_python_frame():
+ return frame
+ frame = frame.older()
+
+ # Not found:
+ return None
+
+ @classmethod
+ def get_selected_bytecode_frame(cls):
+ '''Try to obtain the Frame for the python bytecode interpreter in the
+ selected GDB frame, or None'''
frame = cls.get_selected_frame()
while frame:
@@ -1283,14 +1388,38 @@
if self.is_evalframeex():
pyop = self.get_pyop()
if pyop:
- sys.stdout.write('#%i %s\n' % (self.get_index(), pyop.get_truncated_repr(MAX_OUTPUT_LEN)))
+ line = pyop.get_truncated_repr(MAX_OUTPUT_LEN)
+ write_unicode(sys.stdout, '#%i %s\n' % (self.get_index(), line))
if not pyop.is_optimized_out():
line = pyop.current_line()
- sys.stdout.write(' %s\n' % line.strip())
+ if line is not None:
+ sys.stdout.write(' %s\n' % line.strip())
else:
sys.stdout.write('#%i (unable to read python frame information)\n' % self.get_index())
else:
- sys.stdout.write('#%i\n' % self.get_index())
+ info = self.is_other_python_frame()
+ if info:
+ sys.stdout.write('#%i %s\n' % (self.get_index(), info))
+ else:
+ sys.stdout.write('#%i\n' % self.get_index())
+
+ def print_traceback(self):
+ if self.is_evalframeex():
+ pyop = self.get_pyop()
+ if pyop:
+ pyop.print_traceback()
+ if not pyop.is_optimized_out():
+ line = pyop.current_line()
+ if line is not None:
+ sys.stdout.write(' %s\n' % line.strip())
+ else:
+ sys.stdout.write(' (unable to read python frame information)\n')
+ else:
+ info = self.is_other_python_frame()
+ if info:
+ sys.stdout.write(' %s\n' % info)
+ else:
+ sys.stdout.write(' (not a python frame)\n')
class PyList(gdb.Command):
'''List the current Python source code, if any
@@ -1326,9 +1455,10 @@
if m:
start, end = map(int, m.groups())
- frame = Frame.get_selected_python_frame()
+ # py-list requires an actual PyEval_EvalFrameEx frame:
+ frame = Frame.get_selected_bytecode_frame()
if not frame:
- print('Unable to locate python frame')
+ print('Unable to locate gdb frame for python bytecode interpreter')
return
pyop = frame.get_pyop()
@@ -1346,7 +1476,13 @@
if start<1:
start = 1
- with open(filename, 'r') as f:
+ try:
+ f = open(filename, 'r')
+ except IOError as err:
+ sys.stdout.write('Unable to open %s: %s\n'
+ % (filename, err))
+ return
+ with f:
all_lines = f.readlines()
# start and end are 1-based, all_lines is 0-based;
# so [start-1:end] as a python slice gives us [start, end] as a
@@ -1374,7 +1510,7 @@
if not iter_frame:
break
- if iter_frame.is_evalframeex():
+ if iter_frame.is_python_frame():
# Result:
if iter_frame.select():
iter_frame.print_summary()
@@ -1416,6 +1552,24 @@
PyUp()
PyDown()
+class PyBacktraceFull(gdb.Command):
+ 'Display the current python frame and all the frames within its call stack (if any)'
+ def __init__(self):
+ gdb.Command.__init__ (self,
+ "py-bt-full",
+ gdb.COMMAND_STACK,
+ gdb.COMPLETE_NONE)
+
+
+ def invoke(self, args, from_tty):
+ frame = Frame.get_selected_python_frame()
+ while frame:
+ if frame.is_python_frame():
+ frame.print_summary()
+ frame = frame.older()
+
+PyBacktraceFull()
+
class PyBacktrace(gdb.Command):
'Display the current python frame and all the frames within its call stack (if any)'
def __init__(self):
@@ -1426,10 +1580,11 @@
def invoke(self, args, from_tty):
+ sys.stdout.write('Traceback (most recent call first):\n')
frame = Frame.get_selected_python_frame()
while frame:
- if frame.is_evalframeex():
- frame.print_summary()
+ if frame.is_python_frame():
+ frame.print_traceback()
frame = frame.older()
PyBacktrace()
--
Repository URL: https://hg.python.org/cpython
More information about the Python-checkins
mailing list