Commit 51714ebf authored by Mark Florisson's avatar Mark Florisson

Drop Python 2.5 support + unicode UCS4 builds support + add more tests

parent 83940073
...@@ -122,14 +122,16 @@ class GdbDebuggerTestCase(DebuggerTestCase): ...@@ -122,14 +122,16 @@ class GdbDebuggerTestCase(DebuggerTestCase):
python python
from Cython.Debugger.Tests import test_libcython_in_gdb from Cython.Debugger.Tests import test_libcython_in_gdb
test_libcython_in_gdb.main() test_libcython_in_gdb.main(version=%r)
end end
''') ''' % (sys.version_info[:2],))
self.gdb_command_file = cygdb.make_command_file(self.tempdir, self.gdb_command_file = cygdb.make_command_file(self.tempdir,
prefix_code) prefix_code)
open(self.gdb_command_file, 'a').write(code)
with open(self.gdb_command_file, 'a') as f:
f.write(code)
args = ['gdb', '-batch', '-x', self.gdb_command_file, '-n', '--args', args = ['gdb', '-batch', '-x', self.gdb_command_file, '-n', '--args',
sys.executable, '-c', 'import codefile'] sys.executable, '-c', 'import codefile']
...@@ -149,7 +151,7 @@ class GdbDebuggerTestCase(DebuggerTestCase): ...@@ -149,7 +151,7 @@ class GdbDebuggerTestCase(DebuggerTestCase):
# gdb was not installed # gdb was not installed
have_gdb = False have_gdb = False
else: else:
gdb_version = p.stdout.read() gdb_version = p.stdout.read().decode('ascii')
p.wait() p.wait()
p.stdout.close() p.stdout.close()
...@@ -158,7 +160,8 @@ class GdbDebuggerTestCase(DebuggerTestCase): ...@@ -158,7 +160,8 @@ class GdbDebuggerTestCase(DebuggerTestCase):
regex = "^GNU gdb [^\d]*(\d+)\.(\d+)" regex = "^GNU gdb [^\d]*(\d+)\.(\d+)"
gdb_version_number = re.search(regex, gdb_version).groups() gdb_version_number = re.search(regex, gdb_version).groups()
if not have_gdb or map(int, gdb_version_number) < [7, 2]: # Be Python 3 compatible
if not have_gdb or list(map(int, gdb_version_number)) < [7, 2]:
self.p = None self.p = None
warnings.warn('Skipping gdb tests, need gdb >= 7.2') warnings.warn('Skipping gdb tests, need gdb >= 7.2')
else: else:
...@@ -186,7 +189,7 @@ class TestAll(GdbDebuggerTestCase): ...@@ -186,7 +189,7 @@ class TestAll(GdbDebuggerTestCase):
border = '*' * 30 border = '*' * 30
start = '%s v INSIDE GDB v %s' % (border, border) start = '%s v INSIDE GDB v %s' % (border, border)
end = '%s ^ INSIDE GDB ^ %s' % (border, border) end = '%s ^ INSIDE GDB ^ %s' % (border, border)
errmsg = '\n%s\n%s%s' % (start, err, end) errmsg = '\n%s\n%s%s' % (start, err.decode('UTF-8'), end)
self.assertEquals(0, self.p.wait(), errmsg) self.assertEquals(0, self.p.wait(), errmsg)
sys.stderr.write(err) sys.stderr.write(err)
......
...@@ -24,7 +24,6 @@ from Cython.Debugger import libcython ...@@ -24,7 +24,6 @@ from Cython.Debugger import libcython
from Cython.Debugger import libpython from Cython.Debugger import libpython
from Cython.Debugger.Tests import TestLibCython as test_libcython from Cython.Debugger.Tests import TestLibCython as test_libcython
# for some reason sys.argv is missing in gdb # for some reason sys.argv is missing in gdb
sys.argv = ['gdb'] sys.argv = ['gdb']
...@@ -204,6 +203,7 @@ class TestStep(DebugStepperTestCase): ...@@ -204,6 +203,7 @@ class TestStep(DebugStepperTestCase):
self.assertEqual(str(pyframe.co_name), 'join') self.assertEqual(str(pyframe.co_name), 'join')
assert re.match(r'\d+ def join\(', result), result assert re.match(r'\d+ def join\(', result), result
class TestNext(DebugStepperTestCase): class TestNext(DebugStepperTestCase):
def test_cython_next(self): def test_cython_next(self):
...@@ -345,17 +345,18 @@ class TestExec(DebugTestCase): ...@@ -345,17 +345,18 @@ class TestExec(DebugTestCase):
self.assertEqual('14', self.eval_command('some_random_var')) self.assertEqual('14', self.eval_command('some_random_var'))
_do_debug = os.environ.get('CYTHON_GDB_DEBUG') _do_debug = os.environ.get('GDB_DEBUG')
if _do_debug: if _do_debug:
_debug_file = open('/dev/tty', 'w') _debug_file = open('/dev/tty', 'w')
def _debug(*messages): def _debug(*messages):
if _do_debug: if _do_debug:
messages = itertools.chain([sys._getframe(1).f_code.co_name], messages = itertools.chain([sys._getframe(1).f_code.co_name, ':'],
messages) messages)
_debug_file.write(' '.join(str(msg) for msg in messages) + '\n') _debug_file.write(' '.join(str(msg) for msg in messages) + '\n')
def _main():
def run_unittest_in_module(modulename):
try: try:
gdb.lookup_type('PyModuleObject') gdb.lookup_type('PyModuleObject')
except RuntimeError: except RuntimeError:
...@@ -365,7 +366,7 @@ def _main(): ...@@ -365,7 +366,7 @@ def _main():
warnings.warn(msg) warnings.warn(msg)
os._exit(1) os._exit(1)
else: else:
m = __import__(__name__, fromlist=['']) m = __import__(modulename, fromlist=[''])
tests = inspect.getmembers(m, inspect.isclass) tests = inspect.getmembers(m, inspect.isclass)
# test_support.run_unittest(tests) # test_support.run_unittest(tests)
...@@ -375,15 +376,29 @@ def _main(): ...@@ -375,15 +376,29 @@ def _main():
[test_loader.loadTestsFromTestCase(cls) for name, cls in tests]) [test_loader.loadTestsFromTestCase(cls) for name, cls in tests])
result = unittest.TextTestRunner(verbosity=1).run(suite) result = unittest.TextTestRunner(verbosity=1).run(suite)
if not result.wasSuccessful(): return result.wasSuccessful()
os._exit(1)
def runtests():
"""
Run the libcython and libpython tests. Ensure that an appropriate status is
returned to the parent test process.
"""
from Cython.Debugger.Tests import test_libpython_in_gdb
success_libcython = run_unittest_in_module(__name__)
success_libpython = run_unittest_in_module(test_libpython_in_gdb.__name__)
if not success_libcython or not success_libpython:
sys.exit(1)
def main(version, trace_code=False):
global inferior_python_version
inferior_python_version = version
def main(trace_code=False):
if trace_code: if trace_code:
tracer = trace.Trace(count=False, trace=True, outfile=sys.stderr, tracer = trace.Trace(count=False, trace=True, outfile=sys.stderr,
ignoredirs=[sys.prefix, sys.exec_prefix]) ignoredirs=[sys.prefix, sys.exec_prefix])
tracer.runfunc(_main) tracer.runfunc(runtests)
else: else:
_main() runtests()
\ No newline at end of file
main()
\ No newline at end of file
...@@ -55,6 +55,7 @@ import locale ...@@ -55,6 +55,7 @@ import locale
import atexit import atexit
import warnings import warnings
import tempfile import tempfile
import textwrap
import itertools import itertools
import gdb import gdb
...@@ -69,12 +70,11 @@ if sys.version_info[0] < 3: ...@@ -69,12 +70,11 @@ if sys.version_info[0] < 3:
# Look up the gdb.Type for some standard types: # Look up the gdb.Type for some standard types:
_type_char_ptr = gdb.lookup_type('char').pointer() # char* _type_char_ptr = gdb.lookup_type('char').pointer() # char*
_type_unsigned_char_ptr = gdb.lookup_type('unsigned char').pointer() # unsigned char* _type_unsigned_char_ptr = gdb.lookup_type('unsigned char').pointer()
_type_void_ptr = gdb.lookup_type('void').pointer() # void* _type_void_ptr = gdb.lookup_type('void').pointer() # void*
SIZEOF_VOID_P = _type_void_ptr.sizeof SIZEOF_VOID_P = _type_void_ptr.sizeof
Py_TPFLAGS_HEAPTYPE = (1L << 9) Py_TPFLAGS_HEAPTYPE = (1L << 9)
Py_TPFLAGS_INT_SUBCLASS = (1L << 23) Py_TPFLAGS_INT_SUBCLASS = (1L << 23)
...@@ -88,8 +88,7 @@ Py_TPFLAGS_DICT_SUBCLASS = (1L << 29) ...@@ -88,8 +88,7 @@ Py_TPFLAGS_DICT_SUBCLASS = (1L << 29)
Py_TPFLAGS_BASE_EXC_SUBCLASS = (1L << 30) Py_TPFLAGS_BASE_EXC_SUBCLASS = (1L << 30)
Py_TPFLAGS_TYPE_SUBCLASS = (1L << 31) Py_TPFLAGS_TYPE_SUBCLASS = (1L << 31)
MAX_OUTPUT_LEN = 1024
MAX_OUTPUT_LEN=1024
hexdigits = "0123456789abcdef" hexdigits = "0123456789abcdef"
...@@ -158,6 +157,17 @@ class TruncatedStringIO(object): ...@@ -158,6 +157,17 @@ class TruncatedStringIO(object):
def getvalue(self): def getvalue(self):
return self._val return self._val
# pretty printer lookup
all_pretty_typenames = set()
class PrettyPrinterTrackerMeta(type):
def __init__(self, name, bases, dict):
super(PrettyPrinterTrackerMeta, self).__init__(name, bases, dict)
all_pretty_typenames.add(self._typename)
class PyObjectPtr(object): class PyObjectPtr(object):
""" """
Class wrapping a gdb.Value that's a either a (PyObject*) within the Class wrapping a gdb.Value that's a either a (PyObject*) within the
...@@ -169,6 +179,9 @@ class PyObjectPtr(object): ...@@ -169,6 +179,9 @@ class PyObjectPtr(object):
Note that at every stage the underlying pointer could be NULL, point Note that at every stage the underlying pointer could be NULL, point
to corrupt data, etc; this is the debugger, after all. to corrupt data, etc; this is the debugger, after all.
""" """
__metaclass__ = PrettyPrinterTrackerMeta
_typename = 'PyObject' _typename = 'PyObject'
def __init__(self, gdbval, cast_to=None): def __init__(self, gdbval, cast_to=None):
...@@ -408,6 +421,11 @@ class PyObjectPtr(object): ...@@ -408,6 +421,11 @@ class PyObjectPtr(object):
def as_address(self): def as_address(self):
return long(self._gdbval) return long(self._gdbval)
if not isinstance(PyObjectPtr, PrettyPrinterTrackerMeta):
# Python 3, ensure metaclass
PyObjectPtr = PrettyPrinterTrackerMeta(
PyObjectPtr.__name__, PyObjectPtr.__bases__, vars(PyObjectPtr))
class PyVarObjectPtr(PyObjectPtr): class PyVarObjectPtr(PyObjectPtr):
_typename = 'PyVarObject' _typename = 'PyVarObject'
...@@ -823,9 +841,12 @@ class PyBoolObjectPtr(PyLongObjectPtr): ...@@ -823,9 +841,12 @@ class PyBoolObjectPtr(PyLongObjectPtr):
Class wrapping a gdb.Value that's a PyBoolObject* i.e. one of the two Class wrapping a gdb.Value that's a PyBoolObject* i.e. one of the two
<bool> instances (Py_True/Py_False) within the process being debugged. <bool> instances (Py_True/Py_False) within the process being debugged.
""" """
_typename = 'PyBoolObject'
def proxyval(self, visited): def proxyval(self, visited):
return bool(PyLongObjectPtr.proxyval(self, visited)) castto = gdb.lookup_type('PyLongObject').pointer()
self._gdbval = self._gdbval.cast(castto)
return bool(PyLongObjectPtr(self._gdbval).proxyval(visited))
class PyNoneStructPtr(PyObjectPtr): class PyNoneStructPtr(PyObjectPtr):
...@@ -1043,7 +1064,7 @@ class PyBytesObjectPtr(PyObjectPtr): ...@@ -1043,7 +1064,7 @@ class PyBytesObjectPtr(PyObjectPtr):
def proxyval(self, visited): def proxyval(self, visited):
return str(self) return str(self)
def write_repr(self, out, visited): def write_repr(self, out, visited, py3=True):
# Write this out as a Python 3 bytes literal, i.e. with a "b" prefix # Write this out as a Python 3 bytes literal, i.e. with a "b" prefix
# Get a PyStringObject* within the Python 2 gdb process: # Get a PyStringObject* within the Python 2 gdb process:
...@@ -1054,7 +1075,10 @@ class PyBytesObjectPtr(PyObjectPtr): ...@@ -1054,7 +1075,10 @@ class PyBytesObjectPtr(PyObjectPtr):
quote = "'" quote = "'"
if "'" in proxy and not '"' in proxy: if "'" in proxy and not '"' in proxy:
quote = '"' quote = '"'
if py3:
out.write('b') out.write('b')
out.write(quote) out.write(quote)
for byte in proxy: for byte in proxy:
if byte == quote or byte == '\\': if byte == quote or byte == '\\':
...@@ -1077,6 +1101,8 @@ class PyBytesObjectPtr(PyObjectPtr): ...@@ -1077,6 +1101,8 @@ class PyBytesObjectPtr(PyObjectPtr):
class PyStringObjectPtr(PyBytesObjectPtr): class PyStringObjectPtr(PyBytesObjectPtr):
_typename = 'PyStringObject' _typename = 'PyStringObject'
def write_repr(self, out, visited):
return super(PyStringObjectPtr, self).write_repr(out, visited, py3=False)
class PyTupleObjectPtr(PyObjectPtr): class PyTupleObjectPtr(PyObjectPtr):
_typename = 'PyTupleObject' _typename = 'PyTupleObject'
...@@ -1184,13 +1210,20 @@ class PyUnicodeObjectPtr(PyObjectPtr): ...@@ -1184,13 +1210,20 @@ class PyUnicodeObjectPtr(PyObjectPtr):
return result return result
def write_repr(self, out, visited): def write_repr(self, out, visited):
# Write this out as a Python 3 str literal, i.e. without a "u" prefix
# Get a PyUnicodeObject* within the Python 2 gdb process: # Get a PyUnicodeObject* within the Python 2 gdb process:
proxy = self.proxyval(visited) proxy = self.proxyval(visited)
# Transliteration of Python 3's Object/unicodeobject.c:unicode_repr # Transliteration of Python 3's Object/unicodeobject.c:unicode_repr
# to Python 2: # to Python 2:
try:
gdb.parse_and_eval('PyString_Type')
except RuntimeError:
# Python 3, don't write 'u' as prefix
pass
else:
# Python 2, write the 'u'
out.write('u')
if "'" in proxy and '"' not in proxy: if "'" in proxy and '"' not in proxy:
quote = '"' quote = '"'
else: else:
...@@ -1292,8 +1325,6 @@ class PyUnicodeObjectPtr(PyObjectPtr): ...@@ -1292,8 +1325,6 @@ class PyUnicodeObjectPtr(PyObjectPtr):
out.write(quote) out.write(quote)
def int_from_int(gdbval): def int_from_int(gdbval):
return int(str(gdbval)) return int(str(gdbval))
...@@ -1324,16 +1355,11 @@ class PyObjectPtrPrinter: ...@@ -1324,16 +1355,11 @@ class PyObjectPtrPrinter:
proxyval = pyop.proxyval(set()) proxyval = pyop.proxyval(set())
return stringify(proxyval) return stringify(proxyval)
def pretty_printer_lookup(gdbval): def pretty_printer_lookup(gdbval):
type = gdbval.type.unqualified() type = gdbval.type.unqualified()
if type.code == gdb.TYPE_CODE_PTR: if type.code == gdb.TYPE_CODE_PTR:
type = type.target().unqualified() type = type.target().unqualified()
# do this every time to allow new subclasses to "register" if str(type) in all_pretty_typenames:
# alternatively, we could use a metaclass to register all the typenames
classes = [PyObjectPtr]
classes.extend(PyObjectPtr.__subclasses__())
if str(type) in [cls._typename for cls in classes]:
return PyObjectPtrPrinter(gdbval) return PyObjectPtrPrinter(gdbval)
""" """
...@@ -1364,8 +1390,6 @@ def register (obj): ...@@ -1364,8 +1390,6 @@ def register (obj):
register (gdb.current_objfile ()) register (gdb.current_objfile ())
# Unfortunately, the exact API exposed by the gdb module varies somewhat # Unfortunately, the exact API exposed by the gdb module varies somewhat
# from build to build # from build to build
# See http://bugs.python.org/issue8279?#msg102276 # See http://bugs.python.org/issue8279?#msg102276
...@@ -1868,11 +1892,8 @@ class GenericCodeStepper(gdb.Command): ...@@ -1868,11 +1892,8 @@ class GenericCodeStepper(gdb.Command):
Keep all breakpoints around and simply disable/enable them each time Keep all breakpoints around and simply disable/enable them each time
we are stepping. We need this because if you set and delete a we are stepping. We need this because if you set and delete a
breakpoint, gdb will not repeat your command (this is due to 'delete'). breakpoint, gdb will not repeat your command (this is due to 'delete').
Why? I'm buggered if I know. To further annoy us, we can't use the We also can't use the breakpoint API because there's no option to make
breakpoint API because there's no option to make breakpoint setting breakpoint setting silent.
silent.
So now! We may have an insane amount of breakpoints to list when the
user does 'info breakpoints' :(
This method must be called whenever the list of functions we should This method must be called whenever the list of functions we should
step into changes. It can be called on any GenericCodeStepper instance. step into changes. It can be called on any GenericCodeStepper instance.
...@@ -1890,10 +1911,11 @@ class GenericCodeStepper(gdb.Command): ...@@ -1890,10 +1911,11 @@ class GenericCodeStepper(gdb.Command):
except RuntimeError: except RuntimeError:
# gdb.Breakpoint does take an 'internal' argument, use it # gdb.Breakpoint does take an 'internal' argument, use it
# and hide output # and hide output
result = gdb.execute( result = gdb.execute(textwrap.dedent("""\
"python bp = gdb.Breakpoint(%r, gdb.BP_BREAKPOINT, internal=True); " python bp = gdb.Breakpoint(%r, gdb.BP_BREAKPOINT, \
"print bp.number", internal=True); \
to_string=True) print bp.number""",
to_string=True))
breakpoint = int(result) breakpoint = int(result)
...@@ -2177,6 +2199,19 @@ def pointervalue(gdbval): ...@@ -2177,6 +2199,19 @@ def pointervalue(gdbval):
return pointer return pointer
def get_inferior_unicode_postfix():
try:
gdb.parse_and_eval('PyUnicode_FromEncodedObject')
except RuntimeError:
try:
gdb.parse_and_eval('PyUnicodeUCS2_FromEncodedObject')
except RuntimeError:
return 'UCS4'
else:
return 'UCS2'
else:
return ''
class PythonCodeExecutor(object): class PythonCodeExecutor(object):
def malloc(self, size): def malloc(self, size):
...@@ -2197,15 +2232,13 @@ class PythonCodeExecutor(object): ...@@ -2197,15 +2232,13 @@ class PythonCodeExecutor(object):
def alloc_pystring(self, string): def alloc_pystring(self, string):
stringp = self.alloc_string(string) stringp = self.alloc_string(string)
PyString_FromStringAndSize = 'PyString_FromStringAndSize' PyString_FromStringAndSize = 'PyString_FromStringAndSize'
try: try:
gdb.parse_and_eval(PyString_FromStringAndSize) gdb.parse_and_eval(PyString_FromStringAndSize)
except RuntimeError: except RuntimeError:
try: # Python 3
gdb.parse_and_eval('PyUnicode_FromStringAndSize') PyString_FromStringAndSize = ('PyUnicode%s_FromStringAndSize' %
except RuntimeError: (get_inferior_unicode_postfix,))
PyString_FromStringAndSize = 'PyUnicodeUCS2_FromStringAndSize'
else:
PyString_FromStringAndSize = 'PyUnicode_FromStringAndSize'
try: try:
result = gdb.parse_and_eval( result = gdb.parse_and_eval(
...@@ -2259,7 +2292,7 @@ class PythonCodeExecutor(object): ...@@ -2259,7 +2292,7 @@ class PythonCodeExecutor(object):
code = """ code = """
PyRun_String( PyRun_String(
(PyObject *) %(code)d, (char *) %(code)d,
(int) %(start)d, (int) %(start)d,
(PyObject *) %(globals)s, (PyObject *) %(globals)s,
(PyObject *) %(locals)d) (PyObject *) %(locals)d)
......
...@@ -643,7 +643,7 @@ class CythonUnitTestCase(CythonCompileTestCase): ...@@ -643,7 +643,7 @@ class CythonUnitTestCase(CythonCompileTestCase):
except Exception: except Exception:
pass pass
include_debugger = sys.version_info[:2] > (2, 4) include_debugger = sys.version_info[:2] > (2, 5)
def collect_unittests(path, module_prefix, suite, selectors): def collect_unittests(path, module_prefix, suite, selectors):
def file_matches(filename): def file_matches(filename):
......
...@@ -71,7 +71,7 @@ else: ...@@ -71,7 +71,7 @@ else:
setuptools_extra_args = {} setuptools_extra_args = {}
# tells whether to include cygdb (the script and the Cython.Debugger package # tells whether to include cygdb (the script and the Cython.Debugger package
include_debugger = sys.version_info[:2] > (2, 4) include_debugger = sys.version_info[:2] > (2, 5)
if 'setuptools' in sys.modules: if 'setuptools' in sys.modules:
setuptools_extra_args['zip_safe'] = False setuptools_extra_args['zip_safe'] = False
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment