Commit f1e63d52 authored by Mark Florisson's avatar Mark Florisson

Tests!

(run: python runtests.py Cython.Tests.TestStringIOTree \
                         Cython.Debugger.Tests.TestLibCython \
                         Cython.Compiler.Tests.TestParseTreeTransforms)

--HG--
rename : Cython/Debugger/cygdb.py => Cython/Debugger/Cygdb.py
parent 1f4dc2f4
...@@ -181,9 +181,10 @@ class Context(object): ...@@ -181,9 +181,10 @@ class Context(object):
test_support.append(TreeAssertVisitor()) test_support.append(TreeAssertVisitor())
if options.debug: if options.debug:
from Cython.Debugger import debug_output from Cython.Debugger import DebugWriter
from ParseTreeTransforms import DebugTransform from ParseTreeTransforms import DebugTransform
self.debug_outputwriter = debug_output.CythonDebugWriter(options) self.debug_outputwriter = DebugWriter.CythonDebugWriter(
options.output_dir)
debug_transform = [DebugTransform(self)] debug_transform = [DebugTransform(self)]
else: else:
debug_transform = [] debug_transform = []
......
...@@ -1523,7 +1523,7 @@ class DebugTransform(CythonTransform): ...@@ -1523,7 +1523,7 @@ class DebugTransform(CythonTransform):
def serialize_local_variables(self, entries): def serialize_local_variables(self, entries):
for entry in entries.values(): for entry in entries.values():
if entry.type.is_pyobject: if entry.type.is_pyobject:
vartype = 'PyObject' vartype = 'PythonObject'
else: else:
vartype = 'CObject' vartype = 'CObject'
......
import os
from Cython.Debugger import DebugWriter
from Cython.Compiler import Main
from Cython.Compiler import CmdLine
from Cython.TestUtils import TransformTest from Cython.TestUtils import TransformTest
from Cython.Compiler.ParseTreeTransforms import * from Cython.Compiler.ParseTreeTransforms import *
from Cython.Compiler.Nodes import * from Cython.Compiler.Nodes import *
from Cython.Debugger.Tests import TestLibCython
class TestNormalizeTree(TransformTest): class TestNormalizeTree(TransformTest):
def test_parserbehaviour_is_what_we_coded_for(self): def test_parserbehaviour_is_what_we_coded_for(self):
...@@ -139,6 +145,66 @@ class TestWithTransform(object): # (TransformTest): # Disabled! ...@@ -139,6 +145,66 @@ class TestWithTransform(object): # (TransformTest): # Disabled!
""", t) """, t)
class TestDebugTransform(TestLibCython.DebuggerTestCase):
def elem_hasattrs(self, elem, attrs):
return all(attr in elem.attrib for attr in attrs)
def test_debug_info(self):
try:
assert os.path.exists(self.debug_dest)
t = DebugWriter.etree.parse(self.debug_dest)
# the xpath of the standard ElementTree is primitive, don't use
# anything fancy
L = list(t.find('/Module/Globals'))
# assertTrue is retarded, use the normal assert statement
assert L
xml_globals = dict((e.attrib['name'], e.attrib['type']) for e in L)
self.assertEqual(len(L), len(xml_globals))
L = list(t.find('/Module/Functions'))
assert L
xml_funcs = dict((e.attrib['qualified_name'], e) for e in L)
self.assertEqual(len(L), len(xml_funcs))
# test globals
self.assertEqual('CObject', xml_globals.get('c_var'))
self.assertEqual('PythonObject', xml_globals.get('python_var'))
# test functions
funcnames = 'codefile.spam', 'codefile.ham', 'codefile.eggs'
required_xml_attrs = 'name', 'cname', 'qualified_name'
assert all(f in xml_funcs for f in funcnames)
spam, ham, eggs = (xml_funcs[funcname] for funcname in funcnames)
self.assertEqual(spam.attrib['name'], 'spam')
self.assertNotEqual('spam', spam.attrib['cname'])
assert self.elem_hasattrs(spam, required_xml_attrs)
# test locals of functions
spam_locals = list(spam.find('Locals'))
assert spam_locals
spam_locals.sort(key=lambda e: e.attrib['name'])
names = [e.attrib['name'] for e in spam_locals]
self.assertEqual(list('abcd'), names)
assert self.elem_hasattrs(spam_locals[0], required_xml_attrs)
# test arguments of functions
spam_arguments = list(spam.find('Arguments'))
assert spam_arguments
self.assertEqual(1, len(list(spam_arguments)))
# test step-into functions
spam_stepinto = list(spam.find('StepIntoFunctions'))
assert spam_stepinto
self.assertEqual(1, len(list(spam_stepinto)))
self.assertEqual('puts', list(spam_stepinto)[0].attrib['name'])
except:
print open(self.debug_dest).read()
raise
if __name__ == "__main__": if __name__ == "__main__":
import unittest import unittest
......
...@@ -20,36 +20,33 @@ import subprocess ...@@ -20,36 +20,33 @@ import subprocess
def usage(): def usage():
print("Usage: cygdb [PATH GDB_ARGUMENTS]") print("Usage: cygdb [PATH GDB_ARGUMENTS]")
def main(gdb_argv=[], import_libpython=False, path_to_debug_info=os.curdir): def make_command_file(path_to_debug_info):
"""
Start the Cython debugger. This tells gdb to import the Cython and Python
extensions (libpython.py and libcython.py) and it enables gdb's pending
breakpoints
import_libpython indicates whether we should just 'import libpython',
or import it from Cython.Debugger
path_to_debug_info is the path to the cython_debug directory
"""
debug_files = glob.glob( debug_files = glob.glob(
os.path.join(path_to_debug_info, 'cython_debug/cython_debug_info_*')) os.path.join(path_to_debug_info, 'cython_debug/cython_debug_info_*'))
if not debug_files: if not debug_files:
usage() usage()
sys.exit('No debug files were found in %s. Aborting.' % ( sys.exit('No debug files were found in %s. Aborting.' % (
os.path.abspath(path_to_debug_info))) os.path.abspath(path_to_debug_info)))
fd, tempfilename = tempfile.mkstemp() fd, tempfilename = tempfile.mkstemp()
f = os.fdopen(fd, 'w') f = os.fdopen(fd, 'w')
f.write('set breakpoint pending on\n') f.write('set breakpoint pending on\n')
f.write('python from Cython.Debugger import libcython\n') f.write('python from Cython.Debugger import libcython\n')
if import_libpython:
f.write('python import libpython')
else:
f.write('python from Cython.Debugger import libpython\n')
f.write('\n'.join('cy import %s\n' % fn for fn in debug_files)) f.write('\n'.join('cy import %s\n' % fn for fn in debug_files))
f.close() f.close()
return tempfilename
def main(gdb_argv=[], path_to_debug_info=os.curdir):
"""
Start the Cython debugger. This tells gdb to import the Cython and Python
extensions (libpython.py and libcython.py) and it enables gdb's pending
breakpoints
path_to_debug_info is the path to the cython_debug directory
"""
tempfilename = make_command_file(path_to_debug_info)
p = subprocess.Popen(['gdb', '-command', tempfilename] + gdb_argv) p = subprocess.Popen(['gdb', '-command', tempfilename] + gdb_argv)
while True: while True:
try: try:
......
import os
import re
import sys
import uuid
import shutil
import textwrap
import unittest
import tempfile
import subprocess
import distutils.core
from distutils import sysconfig
import Cython.Distutils.extension
from Cython.Debugger import Cygdb as cygdb
class DebuggerTestCase(unittest.TestCase):
def setUp(self):
"""
Run gdb and have cygdb import the debug information from the code
defined in TestParseTreeTransforms's setUp method
"""
self.tempdir = tempfile.mkdtemp()
self.destfile = os.path.join(self.tempdir, 'codefile.pyx')
self.debug_dest = os.path.join(self.tempdir,
'cython_debug',
'cython_debug_info_codefile')
code = textwrap.dedent("""
cdef extern from "stdio.h":
int puts(char *s)
cdef int c_var = 0
python_var = 0
def spam(a=0):
cdef:
int b, c, d
b = c = d = 0
b = 1
c = 2
d = 3
int(10)
puts("spam")
cdef ham():
pass
cpdef eggs():
pass
cdef class SomeClass(object):
def spam(self):
pass
spam()
""")
self.cwd = os.getcwd()
os.chdir(self.tempdir)
open(self.destfile, 'w').write(code)
ext = Cython.Distutils.extension.Extension(
'codefile',
['codefile.pyx'],
pyrex_debug=True)
distutils.core.setup(
script_args=['build_ext', '--inplace'],
ext_modules=[ext],
cmdclass=dict(build_ext=Cython.Distutils.build_ext)
)
def tearDown(self):
os.chdir(self.cwd)
shutil.rmtree(self.tempdir)
class GdbDebuggerTestCase(DebuggerTestCase):
def setUp(self):
super(GdbDebuggerTestCase, self).setUp()
self.gdb_command_file = cygdb.make_command_file(self.tempdir)
with open(self.gdb_command_file, 'a') as f:
f.write('python '
'from Cython.Debugger.Tests import test_libcython_in_gdb;'
'test_libcython_in_gdb.main()\n')
args = ['gdb', '-batch', '-x', self.gdb_command_file, '-n', '--args',
sys.executable, '-c', 'import codefile']
paths = []
path = os.environ.get('PYTHONPATH')
if path:
paths.append(path)
paths.append(os.path.dirname(os.path.dirname(
os.path.abspath(Cython.__file__))))
env = dict(os.environ, PYTHONPATH=os.pathsep.join(paths))
self.p = subprocess.Popen(
args,
stdout=open(os.devnull, 'w'),
stderr=subprocess.PIPE,
env=env)
def tearDown(self):
super(GdbDebuggerTestCase, self).tearDown()
self.p.stderr.close()
self.p.wait()
os.remove(self.gdb_command_file)
class TestAll(GdbDebuggerTestCase):
def test_all(self):
out, err = self.p.communicate()
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)
self.assertEquals(0, self.p.wait(), errmsg)
sys.stderr.write(err)
if __name__ == '__main__':
unittest.main()
\ No newline at end of file
"""
Tests that run inside GDB.
Note: debug information is already imported by the file generated by
Cython.Debugger.Cygdb.make_command_file()
"""
import sys
# First, fix gdb's python. Make sure to do this before importing modules
# that bind output streams as default parameters
# for some reason sys.argv is missing in gdb
sys.argv = ['gdb']
# Allow gdb to capture output, but have errors end up on stderr
# sys.stdout = sys.__stdout__
sys.stderr = sys.__stderr__
import os
import warnings
import unittest
import traceback
from test import test_support
import gdb
from Cython.Debugger import libcython
class DebugTestCase(unittest.TestCase):
def __init__(self, name):
super(DebugTestCase, self).__init__(name)
self.cy = libcython.cy
self.module = libcython.cy.cython_namespace['codefile']
self.spam_func, self.spam_meth = libcython.cy.functions_by_name['spam']
self.ham_func = libcython.cy.functions_by_qualified_name[
'codefile.ham']
self.eggs_func = libcython.cy.functions_by_qualified_name[
'codefile.eggs']
def read_var(self, varname):
return gdb.parse_and_eval('$cy_cname("%s")' % varname)
def local_info(self):
return gdb.execute('info locals', to_string=True)
class TestDebugInformationClasses(DebugTestCase):
def test_CythonModule(self):
"test that debug information was parsed properly into data structures"
self.assertEqual(self.module.name, 'codefile')
global_vars = ('c_var', 'python_var', 'SomeClass', '__name__',
'__builtins__', '__doc__', '__file__')
assert set(global_vars).issubset(self.module.globals)
def test_CythonVariable(self):
module_globals = self.module.globals
c_var = module_globals['c_var']
python_var = module_globals['python_var']
self.assertEqual(c_var.type, libcython.CObject)
self.assertEqual(python_var.type, libcython.PythonObject)
self.assertEqual(c_var.qualified_name, 'codefile.c_var')
def test_CythonFunction(self):
self.assertEqual(self.spam_func.qualified_name, 'codefile.spam')
self.assertEqual(self.spam_meth.qualified_name,
'codefile.SomeClass.spam')
self.assertEqual(self.spam_func.module, self.module)
assert self.eggs_func.pf_cname
assert not self.ham_func.pf_cname
assert not self.spam_func.pf_cname
assert not self.spam_meth.pf_cname
self.assertEqual(self.spam_func.type, libcython.CObject)
self.assertEqual(self.ham_func.type, libcython.CObject)
self.assertEqual(self.spam_func.arguments, ['a'])
self.assertEqual(self.spam_func.step_into_functions, set(['puts']))
self.assertEqual(self.spam_func.lineno, 8)
self.assertEqual(sorted(self.spam_func.locals), list('abcd'))
class TestParameters(unittest.TestCase):
def test_parameters(self):
assert libcython.parameters.colorize_code
gdb.execute('set cy_colorize_code off')
assert not libcython.parameters.colorize_code
class TestBreak(DebugTestCase):
def test_break(self):
result = gdb.execute('cy break codefile.spam', to_string=True)
assert self.spam_func.cname in result
self.assertEqual(len(gdb.breakpoints()), 1)
bp, = gdb.breakpoints()
self.assertEqual(bp.type, gdb.BP_BREAKPOINT)
self.assertEqual(bp.location, self.spam_func.cname)
assert bp.enabled
class TestStep(DebugTestCase):
def test_step(self):
# Note: breakpoint for spam is still set
gdb.execute('run')
gdb.execute('cy step', to_string=True) # b = c = d = 0
gdb.execute('cy step', to_string=True) # b = 1
self.assertEqual(self.read_var('b'), 1, self.local_info())
self.assertRaises(RuntimeError, self.read_var('b'))
gdb.execute('cont')
class TestNext(DebugTestCase):
def test_next(self):
pass
def main():
try:
# unittest.main(module=__import__(__name__, fromlist=['']))
try:
gdb.lookup_type('PyModuleObject')
except RuntimeError:
msg = ("Unable to run tests, Python was not compiled with "
"debugging information. Either compile python with "
"-g or get a debug build (configure with --with-pydebug).")
warnings.warn(msg)
else:
tests = (
TestDebugInformationClasses,
TestParameters,
TestBreak,
TestStep
)
# test_support.run_unittest(tests)
test_loader = unittest.TestLoader()
suite = unittest.TestSuite(
[test_loader.loadTestsFromTestCase(cls) for cls in tests])
unittest.TextTestRunner(verbosity=1).run(suite)
except Exception:
traceback.print_exc()
os._exit(1)
\ No newline at end of file
...@@ -45,30 +45,17 @@ else: ...@@ -45,30 +45,17 @@ else:
from Cython.Debugger import libpython from Cython.Debugger import libpython
# Cython module namespace
cython_namespace = {}
# C or Python type # C or Python type
CObject = object() CObject = 'CObject'
PythonObject = object() PythonObject = 'PythonObject'
# maps (unique) qualified function names (e.g.
# cythonmodule.ClassName.method_name) to the CythonFunction object
functions_by_qualified_name = {}
# unique cnames of Cython functions
functions_by_cname = {}
# map function names like method_name to a list of all such CythonFunction
# objects
functions_by_name = collections.defaultdict(list)
_data_types = dict(CObject=CObject, PythonObject=PythonObject)
_filesystemencoding = sys.getfilesystemencoding() or 'UTF-8' _filesystemencoding = sys.getfilesystemencoding() or 'UTF-8'
# decorators # decorators
def dont_suppress_errors(function): def dont_suppress_errors(function):
"*sigh*, readline"
@functools.wraps(function) @functools.wraps(function)
def wrapper(*args, **kwargs): def wrapper(*args, **kwargs):
try: try:
...@@ -79,17 +66,31 @@ def dont_suppress_errors(function): ...@@ -79,17 +66,31 @@ def dont_suppress_errors(function):
return wrapper return wrapper
def default_selected_gdb_frame(function): def default_selected_gdb_frame(err=True):
@functools.wraps(function) def decorator(function):
def wrapper(self, frame=None, **kwargs): @functools.wraps(function)
frame = frame or gdb.selected_frame() def wrapper(self, frame=None, **kwargs):
if frame.name() is None: try:
raise NoFunctionNameInFrameError() frame = frame or gdb.selected_frame()
except RuntimeError:
return function(self, frame) raise gdb.GdbError("No frame is currently selected.")
return wrapper
if err and frame.name() is None:
raise NoFunctionNameInFrameError()
return function(self, frame, **kwargs)
return wrapper
return decorator
def require_cython_frame(function):
@functools.wraps(function)
def wrapper(self, *args, **kwargs):
if not self.is_cython_function():
raise gdb.GdbError('Selected frame does not correspond with a '
'Cython function we know about.')
return function(self, *args, **kwargs)
return wrapper
# Classes that represent the debug information # Classes that represent the debug information
# Don't rename the parameters of these classes, they come directly from the XML # Don't rename the parameters of these classes, they come directly from the XML
...@@ -97,12 +98,14 @@ class CythonModule(object): ...@@ -97,12 +98,14 @@ class CythonModule(object):
def __init__(self, module_name, filename): def __init__(self, module_name, filename):
self.name = module_name self.name = module_name
self.filename = filename self.filename = filename
self.functions = {}
self.globals = {} self.globals = {}
# {cython_lineno: min(c_linenos)} # {cython_lineno: min(c_linenos)}
self.lineno_cy2c = {} self.lineno_cy2c = {}
# {c_lineno: cython_lineno} # {c_lineno: cython_lineno}
self.lineno_c2cy = {} self.lineno_c2cy = {}
def qualified_name(self, varname):
return '.'.join(self.name, varname)
class CythonVariable(object): class CythonVariable(object):
...@@ -127,15 +130,78 @@ class CythonFunction(CythonVariable): ...@@ -127,15 +130,78 @@ class CythonFunction(CythonVariable):
type) type)
self.module = module self.module = module
self.pf_cname = pf_cname self.pf_cname = pf_cname
self.lineno = lineno self.lineno = int(lineno)
self.locals = {} self.locals = {}
self.arguments = [] self.arguments = []
self.step_into_functions = set() self.step_into_functions = set()
# General purpose classes
class CythonBase(object):
@default_selected_gdb_frame(err=False)
def is_cython_function(self, frame):
return frame.name() in self.cy.functions_by_cname
@default_selected_gdb_frame(err=False)
def is_python_function(self, frame):
return frame.name() == 'PyEval_EvalFrameEx'
@default_selected_gdb_frame()
def is_python_function(self, frame):
return libpython.Frame(frame).is_evalframeex()
@default_selected_gdb_frame()
def get_c_function_name(self, frame):
return frame.name()
@default_selected_gdb_frame()
def get_c_lineno(self, frame):
return frame.find_sal().line
@default_selected_gdb_frame()
def get_cython_function(self, frame):
result = self.cy.functions_by_cname.get(frame.name())
if result is None:
raise NoCythonFunctionInFrameError()
return result
@default_selected_gdb_frame()
def get_cython_lineno(self, frame):
cyfunc = self.get_cython_function(frame)
return cyfunc.module.lineno_c2cy.get(self.get_c_lineno(frame))
@default_selected_gdb_frame()
def get_source_desc(self, frame):
filename = lineno = lexer = None
if self.is_cython_function():
filename = self.get_cython_function(frame).module.filename
lineno = self.get_cython_lineno(frame)
if pygments:
lexer = pygments.lexers.CythonLexer()
elif self.is_python_function():
pyframeobject = libpython.Frame(frame).get_pyop()
if not pyframeobject:
raise GdbError('Unable to read information on python frame')
filename = pyframeobject.filename()
lineno = pyframeobject.current_line_num()
if pygments:
lexer = pygments.lexers.PythonLexer()
return SourceFileDescriptor(filename, lexer), lineno
@default_selected_gdb_frame()
def get_source_line(self, frame):
source_desc, lineno = self.get_source_desc()
return source_desc.get_source(lineno)
class SourceFileDescriptor(object): class SourceFileDescriptor(object):
def __init__(self, filename, lineno, lexer, formatter=None): def __init__(self, filename, lexer, formatter=None):
self.filename = filename self.filename = filename
self.lineno = lineno
self.lexer = lexer self.lexer = lexer
self.formatter = formatter self.formatter = formatter
...@@ -143,8 +209,8 @@ class SourceFileDescriptor(object): ...@@ -143,8 +209,8 @@ class SourceFileDescriptor(object):
return self.filename is not None return self.filename is not None
def lex(self, code): def lex(self, code):
if pygments and parameter.colorize_code: if pygments and self.lexer and parameters.colorize_code:
bg = parameter.terminal_background.value bg = parameters.terminal_background.value
if self.formatter is None: if self.formatter is None:
formatter = pygments.formatters.TerminalFormatter(bg=bg) formatter = pygments.formatters.TerminalFormatter(bg=bg)
else: else:
...@@ -154,25 +220,31 @@ class SourceFileDescriptor(object): ...@@ -154,25 +220,31 @@ class SourceFileDescriptor(object):
return code return code
def get_source(self, start=0, stop=None, lex_source=True): def _get_source(self, start, stop, lex_source, mark_line):
# todo: have it detect the source file's encoding
if not self.filename:
return 'Unable to retrieve source code'
start = max(self.lineno + start, 0)
if stop is None:
stop = self.lineno + 1
else:
stop = self.lineno + stop
with open(self.filename) as f: with open(self.filename) as f:
source = itertools.islice(f, start, stop)
if lex_source: if lex_source:
return [self.lex(line) for line in source] # to provide proper colouring, the entire code needs to be
# lexed
lines = self.lex(f.read()).splitlines()
else: else:
return list(source) lines = f
for idx, line in enumerate(itertools.islice(lines, start - 1, stop - 1)):
if start + idx == mark_line:
prefix = '>'
else:
prefix = ' '
yield '%s %4d %s' % (prefix, start + idx, line)
def get_source(self, start, stop=None, lex_source=True, mark_line=0):
if not self.filename:
raise GdbError('Unable to retrieve source code')
if stop is None:
stop = start + 1
return '\n'.join(self._get_source(start, stop, lex_source, mark_line))
# Errors # Errors
...@@ -235,32 +307,41 @@ class TerminalBackground(CythonParameter): ...@@ -235,32 +307,41 @@ class TerminalBackground(CythonParameter):
Tell cygdb about the user's terminal background (light or dark) Tell cygdb about the user's terminal background (light or dark)
""" """
class Parameter(object): class CythonParameters(object):
""" """
Simple container class that might get more functionality in the distant Simple container class that might get more functionality in the distant
future (mostly to remind us that we're dealing with parameters) future (mostly to remind us that we're dealing with parameters)
""" """
complete_unqualified = CompleteUnqualifiedFunctionNames(
'cy_complete_unqualified', def __init__(self):
gdb.COMMAND_BREAKPOINTS, self.complete_unqualified = CompleteUnqualifiedFunctionNames(
gdb.PARAM_BOOLEAN, 'cy_complete_unqualified',
True) gdb.COMMAND_BREAKPOINTS,
colorize_code = ColorizeSourceCode( gdb.PARAM_BOOLEAN,
'cy_colorize_code', True)
gdb.COMMAND_FILES, self.colorize_code = ColorizeSourceCode(
gdb.PARAM_BOOLEAN, 'cy_colorize_code',
True) gdb.COMMAND_FILES,
terminal_background = TerminalBackground( gdb.PARAM_BOOLEAN,
'cy_terminal_background_color', True)
gdb.COMMAND_FILES, self.terminal_background = TerminalBackground(
gdb.PARAM_STRING, 'cy_terminal_background_color',
"dark") gdb.COMMAND_FILES,
gdb.PARAM_STRING,
parameter = Parameter() "dark")
parameters = CythonParameters()
# Commands # Commands
class CythonCommand(gdb.Command): class CythonCommand(gdb.Command, CythonBase):
"""
Base class for Cython commands
"""
class CyCy(CythonCommand):
""" """
Invoke a Cython command. Available commands are: Invoke a Cython command. Available commands are:
...@@ -272,53 +353,49 @@ class CythonCommand(gdb.Command): ...@@ -272,53 +353,49 @@ class CythonCommand(gdb.Command):
cy locals cy locals
cy globals cy globals
cy backtrace cy backtrace
cy info line cy up
cy down
""" """
def is_cython_function(self, frame=None): def __init__(self):
func_name = (frame or gdb.selected_frame()).name() super(CythonCommand, self).__init__(
return func_name is not None and func_name in functions_by_cname 'cy', gdb.COMMAND_NONE, gdb.COMPLETE_COMMAND, prefix=True)
@default_selected_gdb_frame self.import_ = CyImport(
def is_python_function(self, frame): 'cy import', gdb.COMMAND_STATUS, gdb.COMPLETE_FILENAME)
return libpython.Frame(frame).is_evalframeex()
@default_selected_gdb_frame
def get_c_function_name(self, frame):
return frame.name()
@default_selected_gdb_frame
def get_c_lineno(self, frame):
return frame.find_sal().line
@default_selected_gdb_frame
def get_cython_function(self, frame):
result = functions_by_cname.get(frame.name())
if result is None:
raise NoCythonFunctionInFrameError()
return result self.break_ = CyBreak('cy break', gdb.COMMAND_BREAKPOINTS)
self.step = CyStep('cy step', gdb.COMMAND_RUNNING, gdb.COMPLETE_NONE)
@default_selected_gdb_frame self.next = CyNext('cy next', gdb.COMMAND_RUNNING, gdb.COMPLETE_NONE)
def get_cython_lineno(self, frame): self.list = CyList('cy list', gdb.COMMAND_FILES, gdb.COMPLETE_NONE)
cyfunc = self.get_cython_function(frame) self.print_ = CyPrint('cy print', gdb.COMMAND_DATA)
return cyfunc.module.lineno_c2cy.get(self.get_c_lineno(frame))
self.locals = CyLocals(
@default_selected_gdb_frame 'cy locals', gdb.COMMAND_STACK, gdb.COMPLETE_NONE)
def get_source_desc(self, frame): self.globals = CyGlobals(
if self.is_cython_function(): 'cy globals', gdb.COMMAND_STACK, gdb.COMPLETE_NONE)
filename = self.get_cython_function(frame).module.filename
lineno = self.get_cython_lineno(frame) self.cy_cname = CyCName('cy_cname')
lexer = pygments.lexers.CythonLexer()
else: objs = (self.import_, self.break_, self.step, self.list, self.print_,
filename = None self.locals, self.globals, self.cy_cname)
lineno = -1
lexer = None
return SourceFileDescriptor(filename, lineno, lexer)
for obj in objs:
cy = CythonCommand('cy', gdb.COMMAND_NONE, gdb.COMPLETE_COMMAND, prefix=True) obj.cy = self
# Cython module namespace
self.cython_namespace = {}
# maps (unique) qualified function names (e.g.
# cythonmodule.ClassName.method_name) to the CythonFunction object
self.functions_by_qualified_name = {}
# unique cnames of Cython functions
self.functions_by_cname = {}
# map function names like method_name to a list of all such
# CythonFunction objects
self.functions_by_name = collections.defaultdict(list)
class CyImport(CythonCommand): class CyImport(CythonCommand):
...@@ -340,7 +417,7 @@ class CyImport(CythonCommand): ...@@ -340,7 +417,7 @@ class CyImport(CythonCommand):
for module in t.getroot(): for module in t.getroot():
cython_module = CythonModule(**module.attrib) cython_module = CythonModule(**module.attrib)
cython_namespace[cython_module.name] = cython_module self.cy.cython_namespace[cython_module.name] = cython_module
for variable in module.find('Globals'): for variable in module.find('Globals'):
d = variable.attrib d = variable.attrib
...@@ -349,15 +426,14 @@ class CyImport(CythonCommand): ...@@ -349,15 +426,14 @@ class CyImport(CythonCommand):
for function in module.find('Functions'): for function in module.find('Functions'):
cython_function = CythonFunction(module=cython_module, cython_function = CythonFunction(module=cython_module,
**function.attrib) **function.attrib)
cython_module.functions[cython_function.name] = \
cython_function
# update the global function mappings # update the global function mappings
functions_by_name[cython_function.name].append( self.cy.functions_by_name[cython_function.name].append(
cython_function) cython_function)
functions_by_qualified_name[ self.cy.functions_by_qualified_name[
cython_function.qualified_name] = cython_function cython_function.qualified_name] = cython_function
functions_by_cname[cython_function.cname] = cython_function self.cy.functions_by_cname[
cython_function.cname] = cython_function
for local in function.find('Locals'): for local in function.find('Locals'):
d = local.attrib d = local.attrib
...@@ -377,9 +453,6 @@ class CyImport(CythonCommand): ...@@ -377,9 +453,6 @@ class CyImport(CythonCommand):
for c_lineno in c_linenos: for c_lineno in c_linenos:
cython_module.lineno_c2cy[c_lineno] = cython_lineno cython_module.lineno_c2cy[c_lineno] = cython_lineno
cy.import_ = CyImport('cy import', gdb.COMMAND_STATUS, gdb.COMPLETE_FILENAME)
class CyBreak(CythonCommand): class CyBreak(CythonCommand):
""" """
...@@ -399,20 +472,21 @@ class CyBreak(CythonCommand): ...@@ -399,20 +472,21 @@ class CyBreak(CythonCommand):
def _break_pyx(self, name): def _break_pyx(self, name):
modulename, _, lineno = name.partition(':') modulename, _, lineno = name.partition(':')
lineno = int(lineno) lineno = int(lineno)
cython_module = cython_namespace[modulename] cython_module = self.cy.cython_namespace[modulename]
if lineno in cython_module.lineno_cy2c: if lineno in cython_module.lineno_cy2c:
c_lineno = cython_module.lineno_cy2c[lineno] c_lineno = cython_module.lineno_cy2c[lineno]
breakpoint = '%s:%s' % (cython_module.name, c_lineno) breakpoint = '%s:%s' % (cython_module.name, c_lineno)
gdb.execute('break ' + breakpoint) gdb.execute('break ' + breakpoint)
else: else:
sys.stderr.write("Not a valid line number (does it contain actual code?)\n") raise GdbError("Not a valid line number. "
"Does it contain actual code?")
def _break_funcname(self, funcname): def _break_funcname(self, funcname):
func = functions_by_qualified_name.get(funcname) func = self.cy.functions_by_qualified_name.get(funcname)
break_funcs = [func] break_funcs = [func]
if not func: if not func:
funcs = functions_by_name.get(funcname) funcs = self.cy.functions_by_name.get(funcname)
if not funcs: if not funcs:
gdb.execute('break ' + funcname) gdb.execute('break ' + funcname)
return return
...@@ -459,13 +533,13 @@ class CyBreak(CythonCommand): ...@@ -459,13 +533,13 @@ class CyBreak(CythonCommand):
@dont_suppress_errors @dont_suppress_errors
def complete(self, text, word): def complete(self, text, word):
names = functions_by_qualified_name names = self.cy.functions_by_qualified_name
if parameter.complete_unqualified: if parameters.complete_unqualified:
names = itertools.chain(names, functions_by_name) names = itertools.chain(names, self.cy.functions_by_name)
words = text.strip().split() words = text.strip().split()
if words and '.' in words[-1]: if words and '.' in words[-1]:
compl = [n for n in functions_by_qualified_name compl = [n for n in self.cy.functions_by_qualified_name
if n.startswith(lastword)] if n.startswith(lastword)]
else: else:
seen = set(text[:-len(word)].split()) seen = set(text[:-len(word)].split())
...@@ -479,27 +553,37 @@ class CyBreak(CythonCommand): ...@@ -479,27 +553,37 @@ class CyBreak(CythonCommand):
return compl return compl
cy.break_ = CyBreak('cy break', gdb.COMMAND_BREAKPOINTS)
class CodeStepperMixin(object):
def init_stepping(self):
self.cython_func = self.get_cython_function()
self.beginline = self.get_cython_lineno()
self.curframe = gdb.selected_frame()
def next_step(self, command):
"returns whether to continue stepping"
result = gdb.execute(command, to_string=True)
newframe = gdb.selected_frame()
c1 = result.startswith('Breakpoint')
c2 = (newframe == self.curframe and
self.get_cython_lineno() > self.beginline)
return not c1 and not c2
def end_stepping(self):
sys.stdout.write(self.get_source_line())
class CyStep(CythonCommand): class CyStep(CythonCommand, CodeStepperMixin):
def step(self, from_tty=True, nsteps=1): def step(self, nsteps=1):
for nthstep in xrange(nsteps): for nthstep in xrange(nsteps):
cython_func = self.get_cython_function() self.init_stepping()
beginline = self.get_cython_lineno()
curframe = gdb.selected_frame() while self.next_step('step'):
while True:
result = gdb.execute('step', False, True)
if result.startswith('Breakpoint'):
break
newframe = gdb.selected_frame() newframe = gdb.selected_frame()
if newframe == curframe: if newframe != self.curframe:
# still in the same function
if self.get_cython_lineno() > beginline:
break
else:
# we entered a function # we entered a function
funcname = self.get_c_function_name(newframe) funcname = self.get_c_function_name(newframe)
if (self.is_cython_function() or if (self.is_cython_function() or
...@@ -507,64 +591,73 @@ class CyStep(CythonCommand): ...@@ -507,64 +591,73 @@ class CyStep(CythonCommand):
funcname in cython_function.step_into_functions): funcname in cython_function.step_into_functions):
break break
line, = self.get_source_desc().get_source() self.end_stepping()
sys.stdout.write(line)
def invoke(self, steps, from_tty): def invoke(self, steps, from_tty):
if self.is_cython_function(): if self.is_cython_function():
if steps: if steps:
self.step(from_tty, int(steps)) self.step(int(steps))
else: else:
self.step(from_tty) self.step()
else: else:
gdb.execute('step ' + steps) gdb.execute('step ' + steps, from_tty)
cy.step = CyStep('cy step', gdb.COMMAND_RUNNING, gdb.COMPLETE_NONE)
class CyNext(CythonCommand, CodeStepperMixin):
def next(self, nsteps=1):
for nthstep in xrange(nsteps):
self.init_stepping()
while self.next_step('next'):
pass
self.end_stepping()
def invoke(self, steps, from_tty):
if self.is_cython_function():
if steps:
self.next(int(steps))
else:
self.next()
else:
gdb.execute('next ' + steps, from_tty)
class CyList(CythonCommand): class CyList(CythonCommand):
def invoke(self, _, from_tty): def invoke(self, _, from_tty):
sd = self.get_source_desc() sd, lineno = self.get_source_desc()
it = enumerate(sd.get_source(-5, +5)) source = sd.get_source(lineno - 5, lineno + 5, mark_line=lineno)
sys.stdout.write( print source
''.join('%4d %s' % (sd.lineno + i, line) for i, line in it))
cy.list = CyList('cy list', gdb.COMMAND_FILES, gdb.COMPLETE_NONE)
class CyPrint(CythonCommand): class CyPrint(CythonCommand):
""" """
Print a Cython variable using 'cy-print x' or 'cy-print module.function.x' Print a Cython variable using 'cy-print x' or 'cy-print module.function.x'
""" """
def invoke(self, name, from_tty): def invoke(self, name, from_tty):
cname = None try:
if self.is_cython_function(): cname = cy.cy_cname.invoke(name)
cython_function = self.get_cython_function() except gdb.GdbError:
if name in cython_function.locals: cname = name
cname = cython_function.locals[name].cname
elif name in cython_function.module.globals:
cname = cython_function.module.globals[name].cname
# let the pretty printers do the work
cname = cname or name
gdb.execute('print ' + cname) gdb.execute('print ' + cname)
def complete(self): def complete(self):
if self.is_cython_function(): if self.is_cython_function():
cf = self.get_cython_function() f = self.get_cython_function()
return list(itertools.chain(cf.locals, cf.globals)) return list(itertools.chain(f.locals, f.globals))
return [] return []
cy.print_ = CyPrint('cy print', gdb.COMMAND_DATA)
class CyLocals(CythonCommand): class CyLocals(CythonCommand):
def ns(self): def ns(self):
return self.get_cython_function().locals return self.get_cython_function().locals
@require_cython_frame
def invoke(self, name, from_tty): def invoke(self, name, from_tty):
try: try:
ns = self.ns() ns = self.ns()
...@@ -573,9 +666,10 @@ class CyLocals(CythonCommand): ...@@ -573,9 +666,10 @@ class CyLocals(CythonCommand):
return return
if ns is None: if ns is None:
print ('Information of Cython locals could not be obtained. ' raise gdb.GdbError(
'Is this an actual Cython function and did you ' 'Information of Cython locals could not be obtained. '
"'cy import' the debug information?") 'Is this an actual Cython function and did you '
"'cy import' the debug information?")
for var in ns.itervalues(): for var in ns.itervalues():
val = gdb.parse_and_eval(var.cname) val = gdb.parse_and_eval(var.cname)
...@@ -591,6 +685,7 @@ class CyGlobals(CythonCommand): ...@@ -591,6 +685,7 @@ class CyGlobals(CythonCommand):
def ns(self): def ns(self):
return self.get_cython_function().globals return self.get_cython_function().globals
@require_cython_frame
def invoke(self, name, from_tty): def invoke(self, name, from_tty):
# include globals from the debug info XML file! # include globals from the debug info XML file!
m = gdb.parse_and_eval('__pyx_m') m = gdb.parse_and_eval('__pyx_m')
...@@ -610,5 +705,30 @@ class CyGlobals(CythonCommand): ...@@ -610,5 +705,30 @@ class CyGlobals(CythonCommand):
print d.get_truncated_repr(1000) print d.get_truncated_repr(1000)
cy.locals = CyLocals('cy locals', gdb.COMMAND_STACK, gdb.COMPLETE_NONE) # Functions
cy.globals = CyGlobals('cy globals', gdb.COMMAND_STACK, gdb.COMPLETE_NONE)
class CyCName(gdb.Function, CythonBase):
"""
Get the C name of a Cython variable.
"""
@require_cython_frame
def invoke(self, cyname, frame=None):
frame = frame or gdb.selected_frame()
cname = None
cyname = cyname.string()
if self.is_cython_function(frame):
cython_function = self.get_cython_function(frame)
if cyname in cython_function.locals:
cname = cython_function.locals[cyname].cname
elif cyname in cython_function.module.globals:
cname = cython_function.module.globals[cyname].cname
if not cname:
raise gdb.GdbError('No such Cython variable: %s' % cyname)
return cname
cy = CyCy()
\ No newline at end of file
#!/usr/bin/python #!/usr/bin/python
# NOTE: this file is taken from the Python source distribution
# It can be found under Tools/gdb/libpython.py
''' '''
From gdb 7 onwards, gdb's build can be configured --with-python, allowing gdb From gdb 7 onwards, gdb's build can be configured --with-python, allowing gdb
to be extended with Python code e.g. for library-specific data visualizations, to be extended with Python code e.g. for library-specific data visualizations,
...@@ -41,6 +45,7 @@ The module also extends gdb with some python-specific commands. ...@@ -41,6 +45,7 @@ The module also extends gdb with some python-specific commands.
''' '''
from __future__ import with_statement from __future__ import with_statement
import gdb import gdb
import sys
# 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*
...@@ -1009,6 +1014,18 @@ class PyTypeObjectPtr(PyObjectPtr): ...@@ -1009,6 +1014,18 @@ class PyTypeObjectPtr(PyObjectPtr):
_typename = 'PyTypeObject' _typename = 'PyTypeObject'
if sys.maxunicode >= 0x10000:
_unichr = unichr
else:
# Needed for proper surrogate support if sizeof(Py_UNICODE) is 2 in gdb
def _unichr(x):
if x < 0x10000:
return unichr(x)
x -= 0x10000
ch1 = 0xD800 | (x >> 10)
ch2 = 0xDC00 | (x & 0x3FF)
return unichr(ch1) + unichr(ch2)
class PyUnicodeObjectPtr(PyObjectPtr): class PyUnicodeObjectPtr(PyObjectPtr):
_typename = 'PyUnicodeObject' _typename = 'PyUnicodeObject'
...@@ -1025,37 +1042,36 @@ class PyUnicodeObjectPtr(PyObjectPtr): ...@@ -1025,37 +1042,36 @@ class PyUnicodeObjectPtr(PyObjectPtr):
# Gather a list of ints from the Py_UNICODE array; these are either # Gather a list of ints from the Py_UNICODE array; these are either
# UCS-2 or UCS-4 code points: # UCS-2 or UCS-4 code points:
Py_UNICODEs = [int(field_str[i]) for i in safe_range(field_length)] if self.char_width() > 2:
Py_UNICODEs = [int(field_str[i]) for i in safe_range(field_length)]
else:
# A more elaborate routine if sizeof(Py_UNICODE) is 2 in the
# inferior process: we must join surrogate pairs.
Py_UNICODEs = []
i = 0
limit = safety_limit(field_length)
while i < limit:
ucs = int(field_str[i])
i += 1
if ucs < 0xD800 or ucs >= 0xDC00 or i == field_length:
Py_UNICODEs.append(ucs)
continue
# This could be a surrogate pair.
ucs2 = int(field_str[i])
if ucs2 < 0xDC00 or ucs2 > 0xDFFF:
continue
code = (ucs & 0x03FF) << 10
code |= ucs2 & 0x03FF
code += 0x00010000
Py_UNICODEs.append(code)
i += 1
# Convert the int code points to unicode characters, and generate a # Convert the int code points to unicode characters, and generate a
# local unicode instance: # local unicode instance.
result = u''.join([unichr(ucs) for ucs in Py_UNICODEs]) # This splits surrogate pairs if sizeof(Py_UNICODE) is 2 here (in gdb).
result = u''.join([_unichr(ucs) for ucs in Py_UNICODEs])
return result return result
def write_repr(self, out, visited):
proxy = self.proxyval(visited)
if self.char_width() == 2:
# sizeof(Py_UNICODE)==2: join surrogates
proxy2 = []
i = 0
while i < len(proxy):
ch = proxy[i]
i += 1
if (i < len(proxy)
and 0xD800 <= ord(ch) < 0xDC00 \
and 0xDC00 <= ord(proxy[i]) <= 0xDFFF):
# Get code point from surrogate pair
ch2 = proxy[i]
code = (ord(ch) & 0x03FF) << 10
code |= ord(ch2) & 0x03FF
code += 0x00010000
i += 1
proxy2.append(unichr(code))
else:
proxy2.append(ch)
proxy = u''.join(proxy2)
out.write(repr(proxy))
def int_from_int(gdbval): def int_from_int(gdbval):
return int(str(gdbval)) return int(str(gdbval))
...@@ -1431,4 +1447,4 @@ class PyLocals(gdb.Command): ...@@ -1431,4 +1447,4 @@ class PyLocals(gdb.Command):
% (pyop_name.proxyval(set()), % (pyop_name.proxyval(set()),
pyop_value.get_truncated_repr(MAX_OUTPUT_LEN))) pyop_value.get_truncated_repr(MAX_OUTPUT_LEN)))
PyLocals() PyLocals()
\ No newline at end of file
...@@ -16,7 +16,6 @@ class StringIOTree(object): ...@@ -16,7 +16,6 @@ class StringIOTree(object):
def getvalue(self): def getvalue(self):
content = [x.getvalue() for x in self.prepended_children] content = [x.getvalue() for x in self.prepended_children]
content.append(self.stream.getvalue()) content.append(self.stream.getvalue())
print self.linenumber_map()
return "".join(content) return "".join(content)
def copyto(self, target): def copyto(self, target):
...@@ -33,6 +32,8 @@ class StringIOTree(object): ...@@ -33,6 +32,8 @@ class StringIOTree(object):
# itself is empty -- this makes it ready for insertion # itself is empty -- this makes it ready for insertion
if self.stream.tell(): if self.stream.tell():
self.prepended_children.append(StringIOTree(self.stream)) self.prepended_children.append(StringIOTree(self.stream))
self.prepended_children[-1].markers = self.markers
self.markers = []
self.stream = StringIO() self.stream = StringIO()
self.write = self.stream.write self.write = self.stream.write
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
import sys import sys
from Cython.Debugger import cygdb from Cython.Debugger import Cygdb as cygdb
if __name__ == '__main__': if __name__ == '__main__':
if len(sys.argv) > 1: if len(sys.argv) > 1:
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
import sys import sys
from Cython.Debugger import cygdb from Cython.Debugger import Cygdb as cygdb
if __name__ == '__main__': if __name__ == '__main__':
if len(sys.argv) > 1: if len(sys.argv) > 1:
......
...@@ -606,8 +606,13 @@ def collect_doctests(path, module_prefix, suite, selectors): ...@@ -606,8 +606,13 @@ def collect_doctests(path, module_prefix, suite, selectors):
def package_matches(dirname): def package_matches(dirname):
return dirname not in ("Mac", "Distutils", "Plex") return dirname not in ("Mac", "Distutils", "Plex")
def file_matches(filename): def file_matches(filename):
return (filename.endswith(".py") and not ('~' in filename filename, ext = os.path.splitext(filename)
or '#' in filename or filename.startswith('.'))) blacklist = ('libcython', 'libpython', 'test_libcython_in_gdb')
return (ext == '.py' and not
'~' in filename and not
'#' in filename and not
filename.startswith('.') and not
filename in blacklist)
import doctest, types import doctest, types
for dirpath, dirnames, filenames in os.walk(path): for dirpath, dirnames, filenames in os.walk(path):
parentname = os.path.split(dirpath)[-1] parentname = os.path.split(dirpath)[-1]
......
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