From 51714ebf8f113fc3d7f631597dabedeb3ebdeacc Mon Sep 17 00:00:00 2001 From: Mark Florisson Date: Thu, 9 Dec 2010 21:29:46 +0100 Subject: [PATCH] Drop Python 2.5 support + unicode UCS4 builds support + add more tests --- Cython/Debugger/Tests/TestLibCython.py | 15 ++- .../Debugger/Tests/test_libcython_in_gdb.py | 39 ++++-- Cython/Debugger/libpython.py | 125 +++++++++++------- runtests.py | 2 +- setup.py | 2 +- 5 files changed, 117 insertions(+), 66 deletions(-) diff --git a/Cython/Debugger/Tests/TestLibCython.py b/Cython/Debugger/Tests/TestLibCython.py index 8489c79e..8afb3279 100644 --- a/Cython/Debugger/Tests/TestLibCython.py +++ b/Cython/Debugger/Tests/TestLibCython.py @@ -122,14 +122,16 @@ class GdbDebuggerTestCase(DebuggerTestCase): python from Cython.Debugger.Tests import test_libcython_in_gdb - test_libcython_in_gdb.main() + test_libcython_in_gdb.main(version=%r) end - ''') + ''' % (sys.version_info[:2],)) self.gdb_command_file = cygdb.make_command_file(self.tempdir, 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', sys.executable, '-c', 'import codefile'] @@ -149,7 +151,7 @@ class GdbDebuggerTestCase(DebuggerTestCase): # gdb was not installed have_gdb = False else: - gdb_version = p.stdout.read() + gdb_version = p.stdout.read().decode('ascii') p.wait() p.stdout.close() @@ -158,7 +160,8 @@ class GdbDebuggerTestCase(DebuggerTestCase): regex = "^GNU gdb [^\d]*(\d+)\.(\d+)" 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 warnings.warn('Skipping gdb tests, need gdb >= 7.2') else: @@ -186,7 +189,7 @@ class TestAll(GdbDebuggerTestCase): border = '*' * 30 start = '%s v INSIDE GDB v %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) sys.stderr.write(err) diff --git a/Cython/Debugger/Tests/test_libcython_in_gdb.py b/Cython/Debugger/Tests/test_libcython_in_gdb.py index 1d61cd23..728328a4 100644 --- a/Cython/Debugger/Tests/test_libcython_in_gdb.py +++ b/Cython/Debugger/Tests/test_libcython_in_gdb.py @@ -24,7 +24,6 @@ from Cython.Debugger import libcython from Cython.Debugger import libpython from Cython.Debugger.Tests import TestLibCython as test_libcython - # for some reason sys.argv is missing in gdb sys.argv = ['gdb'] @@ -204,6 +203,7 @@ class TestStep(DebugStepperTestCase): self.assertEqual(str(pyframe.co_name), 'join') assert re.match(r'\d+ def join\(', result), result + class TestNext(DebugStepperTestCase): def test_cython_next(self): @@ -345,17 +345,18 @@ class TestExec(DebugTestCase): 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: _debug_file = open('/dev/tty', 'w') def _debug(*messages): if _do_debug: - messages = itertools.chain([sys._getframe(1).f_code.co_name], + messages = itertools.chain([sys._getframe(1).f_code.co_name, ':'], messages) _debug_file.write(' '.join(str(msg) for msg in messages) + '\n') -def _main(): + +def run_unittest_in_module(modulename): try: gdb.lookup_type('PyModuleObject') except RuntimeError: @@ -365,7 +366,7 @@ def _main(): warnings.warn(msg) os._exit(1) else: - m = __import__(__name__, fromlist=['']) + m = __import__(modulename, fromlist=['']) tests = inspect.getmembers(m, inspect.isclass) # test_support.run_unittest(tests) @@ -375,15 +376,29 @@ def _main(): [test_loader.loadTestsFromTestCase(cls) for name, cls in tests]) result = unittest.TextTestRunner(verbosity=1).run(suite) - if not result.wasSuccessful(): - os._exit(1) + return result.wasSuccessful() -def main(trace_code=False): +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 + if trace_code: tracer = trace.Trace(count=False, trace=True, outfile=sys.stderr, ignoredirs=[sys.prefix, sys.exec_prefix]) - tracer.runfunc(_main) + tracer.runfunc(runtests) else: - _main() - -main() \ No newline at end of file + runtests() \ No newline at end of file diff --git a/Cython/Debugger/libpython.py b/Cython/Debugger/libpython.py index b871756e..f3aae848 100644 --- a/Cython/Debugger/libpython.py +++ b/Cython/Debugger/libpython.py @@ -55,6 +55,7 @@ import locale import atexit import warnings import tempfile +import textwrap import itertools import gdb @@ -69,12 +70,11 @@ if sys.version_info[0] < 3: # Look up the gdb.Type for some standard types: _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* SIZEOF_VOID_P = _type_void_ptr.sizeof - Py_TPFLAGS_HEAPTYPE = (1L << 9) Py_TPFLAGS_INT_SUBCLASS = (1L << 23) @@ -88,8 +88,7 @@ Py_TPFLAGS_DICT_SUBCLASS = (1L << 29) Py_TPFLAGS_BASE_EXC_SUBCLASS = (1L << 30) Py_TPFLAGS_TYPE_SUBCLASS = (1L << 31) - -MAX_OUTPUT_LEN=1024 +MAX_OUTPUT_LEN = 1024 hexdigits = "0123456789abcdef" @@ -158,6 +157,17 @@ class TruncatedStringIO(object): def getvalue(self): 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 wrapping a gdb.Value that's a either a (PyObject*) within the @@ -169,8 +179,11 @@ class PyObjectPtr(object): Note that at every stage the underlying pointer could be NULL, point to corrupt data, etc; this is the debugger, after all. """ + + __metaclass__ = PrettyPrinterTrackerMeta + _typename = 'PyObject' - + def __init__(self, gdbval, cast_to=None): if cast_to: self._gdbval = gdbval.cast(cast_to) @@ -355,7 +368,7 @@ class PyObjectPtr(object): } if tp_name in name_map: return name_map[tp_name] - + if tp_flags & Py_TPFLAGS_HEAPTYPE: return HeapTypeObjectPtr @@ -408,6 +421,11 @@ class PyObjectPtr(object): def as_address(self): return long(self._gdbval) +if not isinstance(PyObjectPtr, PrettyPrinterTrackerMeta): + # Python 3, ensure metaclass + PyObjectPtr = PrettyPrinterTrackerMeta( + PyObjectPtr.__name__, PyObjectPtr.__bases__, vars(PyObjectPtr)) + class PyVarObjectPtr(PyObjectPtr): _typename = 'PyVarObject' @@ -472,7 +490,7 @@ def _PyObject_VAR_SIZE(typeobj, nitems): class HeapTypeObjectPtr(PyObjectPtr): _typename = 'PyObject' - + def get_attr_dict(self): ''' Get the PyDictObject ptr representing the attribute dictionary @@ -550,7 +568,7 @@ class PyBaseExceptionObjectPtr(PyObjectPtr): within the process being debugged. """ _typename = 'PyBaseExceptionObject' - + def proxyval(self, visited): # Guard against infinite loops: if self.as_address() in visited: @@ -697,7 +715,7 @@ class PyDictObjectPtr(PyObjectPtr): class PyInstanceObjectPtr(PyObjectPtr): _typename = 'PyInstanceObject' - + def proxyval(self, visited): # Guard against infinite loops: if self.as_address() in visited: @@ -742,7 +760,7 @@ class PyIntObjectPtr(PyObjectPtr): class PyListObjectPtr(PyObjectPtr): _typename = 'PyListObject' - + def __getitem__(self, i): # Get the gdb.Value for the (PyObject*) with the given index: field_ob_item = self.field('ob_item') @@ -775,7 +793,7 @@ class PyListObjectPtr(PyObjectPtr): class PyLongObjectPtr(PyObjectPtr): _typename = 'PyLongObject' - + def proxyval(self, visited): ''' Python's Include/longobjrep.h has this declaration: @@ -793,7 +811,7 @@ class PyLongObjectPtr(PyObjectPtr): where SHIFT can be either: #define PyLong_SHIFT 30 #define PyLong_SHIFT 15 - ''' + ''' ob_size = long(self.field('ob_size')) if ob_size == 0: return 0L @@ -823,9 +841,12 @@ class PyBoolObjectPtr(PyLongObjectPtr): Class wrapping a gdb.Value that's a PyBoolObject* i.e. one of the two instances (Py_True/Py_False) within the process being debugged. """ + _typename = 'PyBoolObject' 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): @@ -1033,7 +1054,7 @@ class PySetObjectPtr(PyObjectPtr): class PyBytesObjectPtr(PyObjectPtr): _typename = 'PyBytesObject' - + def __str__(self): field_ob_size = self.field('ob_size') field_ob_sval = self.field('ob_sval') @@ -1043,7 +1064,7 @@ class PyBytesObjectPtr(PyObjectPtr): def proxyval(self, visited): 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 # Get a PyStringObject* within the Python 2 gdb process: @@ -1054,7 +1075,10 @@ class PyBytesObjectPtr(PyObjectPtr): quote = "'" if "'" in proxy and not '"' in proxy: quote = '"' - out.write('b') + + if py3: + out.write('b') + out.write(quote) for byte in proxy: if byte == quote or byte == '\\': @@ -1077,6 +1101,8 @@ class PyBytesObjectPtr(PyObjectPtr): class PyStringObjectPtr(PyBytesObjectPtr): _typename = 'PyStringObject' + def write_repr(self, out, visited): + return super(PyStringObjectPtr, self).write_repr(out, visited, py3=False) class PyTupleObjectPtr(PyObjectPtr): _typename = 'PyTupleObject' @@ -1184,13 +1210,20 @@ class PyUnicodeObjectPtr(PyObjectPtr): return result 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: proxy = self.proxyval(visited) # Transliteration of Python 3's Object/unicodeobject.c:unicode_repr # 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: quote = '"' else: @@ -1292,8 +1325,6 @@ class PyUnicodeObjectPtr(PyObjectPtr): out.write(quote) - - def int_from_int(gdbval): return int(str(gdbval)) @@ -1324,16 +1355,11 @@ class PyObjectPtrPrinter: proxyval = pyop.proxyval(set()) return stringify(proxyval) - def pretty_printer_lookup(gdbval): type = gdbval.type.unqualified() if type.code == gdb.TYPE_CODE_PTR: type = type.target().unqualified() - # do this every time to allow new subclasses to "register" - # 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]: + if str(type) in all_pretty_typenames: return PyObjectPtrPrinter(gdbval) """ @@ -1364,8 +1390,6 @@ def register (obj): register (gdb.current_objfile ()) - - # Unfortunately, the exact API exposed by the gdb module varies somewhat # from build to build # See http://bugs.python.org/issue8279?#msg102276 @@ -1868,11 +1892,8 @@ class GenericCodeStepper(gdb.Command): Keep all breakpoints around and simply disable/enable them each time 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'). - Why? I'm buggered if I know. To further annoy us, we can't use the - breakpoint API because there's no option to make breakpoint setting - silent. - So now! We may have an insane amount of breakpoints to list when the - user does 'info breakpoints' :( + We also can't use the breakpoint API because there's no option to make + breakpoint setting silent. This method must be called whenever the list of functions we should step into changes. It can be called on any GenericCodeStepper instance. @@ -1890,10 +1911,11 @@ class GenericCodeStepper(gdb.Command): except RuntimeError: # gdb.Breakpoint does take an 'internal' argument, use it # and hide output - result = gdb.execute( - "python bp = gdb.Breakpoint(%r, gdb.BP_BREAKPOINT, internal=True); " - "print bp.number", - to_string=True) + result = gdb.execute(textwrap.dedent("""\ + python bp = gdb.Breakpoint(%r, gdb.BP_BREAKPOINT, \ + internal=True); \ + print bp.number""", + to_string=True)) breakpoint = int(result) @@ -2177,6 +2199,19 @@ def pointervalue(gdbval): 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): def malloc(self, size): @@ -2197,16 +2232,14 @@ class PythonCodeExecutor(object): def alloc_pystring(self, string): stringp = self.alloc_string(string) PyString_FromStringAndSize = 'PyString_FromStringAndSize' + try: gdb.parse_and_eval(PyString_FromStringAndSize) except RuntimeError: - try: - gdb.parse_and_eval('PyUnicode_FromStringAndSize') - except RuntimeError: - PyString_FromStringAndSize = 'PyUnicodeUCS2_FromStringAndSize' - else: - PyString_FromStringAndSize = 'PyUnicode_FromStringAndSize' - + # Python 3 + PyString_FromStringAndSize = ('PyUnicode%s_FromStringAndSize' % + (get_inferior_unicode_postfix,)) + try: result = gdb.parse_and_eval( '(PyObject *) %s((char *) %d, (size_t) %d)' % ( @@ -2259,7 +2292,7 @@ class PythonCodeExecutor(object): code = """ PyRun_String( - (PyObject *) %(code)d, + (char *) %(code)d, (int) %(start)d, (PyObject *) %(globals)s, (PyObject *) %(locals)d) @@ -2384,4 +2417,4 @@ class PyExec(gdb.Command): executor.evalcode(expr, input_type, global_dict, local_dict) py_exec = FixGdbCommand('py-exec', '-py-exec') -_py_exec = PyExec("-py-exec", gdb.COMMAND_DATA, gdb.COMPLETE_NONE) \ No newline at end of file +_py_exec = PyExec("-py-exec", gdb.COMMAND_DATA, gdb.COMPLETE_NONE) diff --git a/runtests.py b/runtests.py index 866ddc19..1c2ba2d0 100644 --- a/runtests.py +++ b/runtests.py @@ -643,7 +643,7 @@ class CythonUnitTestCase(CythonCompileTestCase): except Exception: 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 file_matches(filename): diff --git a/setup.py b/setup.py index 0a0584dd..e81582e1 100644 --- a/setup.py +++ b/setup.py @@ -71,7 +71,7 @@ else: setuptools_extra_args = {} # 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: setuptools_extra_args['zip_safe'] = False -- 2.26.2