first commit
This commit is contained in:
commit
417e54da96
5696 changed files with 900003 additions and 0 deletions
|
@ -0,0 +1,274 @@
|
|||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import shutil
|
||||
import warnings
|
||||
import textwrap
|
||||
import unittest
|
||||
import tempfile
|
||||
import subprocess
|
||||
#import distutils.core
|
||||
#from distutils import sysconfig
|
||||
from distutils import ccompiler
|
||||
|
||||
import runtests
|
||||
import Cython.Distutils.extension
|
||||
import Cython.Distutils.old_build_ext as build_ext
|
||||
from Cython.Debugger import Cygdb as cygdb
|
||||
|
||||
root = os.path.dirname(os.path.abspath(__file__))
|
||||
codefile = os.path.join(root, 'codefile')
|
||||
cfuncs_file = os.path.join(root, 'cfuncs.c')
|
||||
|
||||
with open(codefile) as f:
|
||||
source_to_lineno = dict((line.strip(), i + 1) for i, line in enumerate(f))
|
||||
|
||||
|
||||
have_gdb = None
|
||||
def test_gdb():
|
||||
global have_gdb
|
||||
if have_gdb is not None:
|
||||
return have_gdb
|
||||
|
||||
have_gdb = False
|
||||
try:
|
||||
p = subprocess.Popen(['gdb', '-nx', '--version'], stdout=subprocess.PIPE)
|
||||
except OSError:
|
||||
# gdb not found
|
||||
gdb_version = None
|
||||
else:
|
||||
stdout, _ = p.communicate()
|
||||
# Based on Lib/test/test_gdb.py
|
||||
regex = r"GNU gdb [^\d]*(\d+)\.(\d+)"
|
||||
gdb_version = re.match(regex, stdout.decode('ascii', 'ignore'))
|
||||
|
||||
if gdb_version:
|
||||
gdb_version_number = list(map(int, gdb_version.groups()))
|
||||
if gdb_version_number >= [7, 2]:
|
||||
have_gdb = True
|
||||
with tempfile.NamedTemporaryFile(mode='w+') as python_version_script:
|
||||
python_version_script.write(
|
||||
'python import sys; print("%s %s" % sys.version_info[:2])')
|
||||
python_version_script.flush()
|
||||
p = subprocess.Popen(['gdb', '-batch', '-x', python_version_script.name],
|
||||
stdout=subprocess.PIPE)
|
||||
stdout, _ = p.communicate()
|
||||
try:
|
||||
internal_python_version = list(map(int, stdout.decode('ascii', 'ignore').split()))
|
||||
if internal_python_version < [2, 6]:
|
||||
have_gdb = False
|
||||
except ValueError:
|
||||
have_gdb = False
|
||||
|
||||
if not have_gdb:
|
||||
warnings.warn('Skipping gdb tests, need gdb >= 7.2 with Python >= 2.6')
|
||||
|
||||
return have_gdb
|
||||
|
||||
|
||||
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
|
||||
"""
|
||||
if not test_gdb():
|
||||
return
|
||||
|
||||
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')
|
||||
self.cfuncs_destfile = os.path.join(self.tempdir, 'cfuncs')
|
||||
|
||||
self.cwd = os.getcwd()
|
||||
try:
|
||||
os.chdir(self.tempdir)
|
||||
|
||||
shutil.copy(codefile, self.destfile)
|
||||
shutil.copy(cfuncs_file, self.cfuncs_destfile + '.c')
|
||||
shutil.copy(cfuncs_file.replace('.c', '.h'),
|
||||
self.cfuncs_destfile + '.h')
|
||||
|
||||
compiler = ccompiler.new_compiler()
|
||||
compiler.compile(['cfuncs.c'], debug=True, extra_postargs=['-fPIC'])
|
||||
|
||||
opts = dict(
|
||||
test_directory=self.tempdir,
|
||||
module='codefile',
|
||||
)
|
||||
|
||||
optimization_disabler = build_ext.Optimization()
|
||||
|
||||
cython_compile_testcase = runtests.CythonCompileTestCase(
|
||||
workdir=self.tempdir,
|
||||
# we clean up everything (not only compiled files)
|
||||
cleanup_workdir=False,
|
||||
tags=runtests.parse_tags(codefile),
|
||||
**opts
|
||||
)
|
||||
|
||||
|
||||
new_stderr = open(os.devnull, 'w')
|
||||
|
||||
stderr = sys.stderr
|
||||
sys.stderr = new_stderr
|
||||
|
||||
optimization_disabler.disable_optimization()
|
||||
try:
|
||||
cython_compile_testcase.run_cython(
|
||||
targetdir=self.tempdir,
|
||||
incdir=None,
|
||||
annotate=False,
|
||||
extra_compile_options={
|
||||
'gdb_debug':True,
|
||||
'output_dir':self.tempdir,
|
||||
},
|
||||
**opts
|
||||
)
|
||||
|
||||
cython_compile_testcase.run_distutils(
|
||||
incdir=None,
|
||||
workdir=self.tempdir,
|
||||
extra_extension_args={'extra_objects':['cfuncs.o']},
|
||||
**opts
|
||||
)
|
||||
finally:
|
||||
optimization_disabler.restore_state()
|
||||
sys.stderr = stderr
|
||||
new_stderr.close()
|
||||
|
||||
# ext = Cython.Distutils.extension.Extension(
|
||||
# 'codefile',
|
||||
# ['codefile.pyx'],
|
||||
# cython_gdb=True,
|
||||
# extra_objects=['cfuncs.o'])
|
||||
#
|
||||
# distutils.core.setup(
|
||||
# script_args=['build_ext', '--inplace'],
|
||||
# ext_modules=[ext],
|
||||
# cmdclass=dict(build_ext=Cython.Distutils.build_ext)
|
||||
# )
|
||||
|
||||
except:
|
||||
os.chdir(self.cwd)
|
||||
raise
|
||||
|
||||
def tearDown(self):
|
||||
if not test_gdb():
|
||||
return
|
||||
os.chdir(self.cwd)
|
||||
shutil.rmtree(self.tempdir)
|
||||
|
||||
|
||||
class GdbDebuggerTestCase(DebuggerTestCase):
|
||||
|
||||
def setUp(self):
|
||||
if not test_gdb():
|
||||
return
|
||||
|
||||
super(GdbDebuggerTestCase, self).setUp()
|
||||
|
||||
prefix_code = textwrap.dedent('''\
|
||||
python
|
||||
|
||||
import os
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
def excepthook(type, value, tb):
|
||||
traceback.print_exception(type, value, tb)
|
||||
sys.stderr.flush()
|
||||
sys.stdout.flush()
|
||||
os._exit(1)
|
||||
|
||||
sys.excepthook = excepthook
|
||||
|
||||
# Have tracebacks end up on sys.stderr (gdb replaces sys.stderr
|
||||
# with an object that calls gdb.write())
|
||||
sys.stderr = sys.__stderr__
|
||||
|
||||
end
|
||||
''')
|
||||
|
||||
code = textwrap.dedent('''\
|
||||
python
|
||||
|
||||
from Cython.Debugger.Tests import test_libcython_in_gdb
|
||||
test_libcython_in_gdb.main(version=%r)
|
||||
|
||||
end
|
||||
''' % (sys.version_info[:2],))
|
||||
|
||||
self.gdb_command_file = cygdb.make_command_file(self.tempdir,
|
||||
prefix_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']
|
||||
|
||||
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=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
env=env)
|
||||
|
||||
def tearDown(self):
|
||||
if not test_gdb():
|
||||
return
|
||||
|
||||
try:
|
||||
super(GdbDebuggerTestCase, self).tearDown()
|
||||
if self.p:
|
||||
try: self.p.stdout.close()
|
||||
except: pass
|
||||
try: self.p.stderr.close()
|
||||
except: pass
|
||||
self.p.wait()
|
||||
finally:
|
||||
os.remove(self.gdb_command_file)
|
||||
|
||||
|
||||
class TestAll(GdbDebuggerTestCase):
|
||||
|
||||
def test_all(self):
|
||||
if not test_gdb():
|
||||
return
|
||||
|
||||
out, err = self.p.communicate()
|
||||
out = out.decode('UTF-8')
|
||||
err = err.decode('UTF-8')
|
||||
|
||||
exit_status = self.p.returncode
|
||||
|
||||
if exit_status == 1:
|
||||
sys.stderr.write(out)
|
||||
sys.stderr.write(err)
|
||||
elif exit_status >= 2:
|
||||
border = u'*' * 30
|
||||
start = u'%s v INSIDE GDB v %s' % (border, border)
|
||||
stderr = u'%s v STDERR v %s' % (border, border)
|
||||
end = u'%s ^ INSIDE GDB ^ %s' % (border, border)
|
||||
errmsg = u'\n%s\n%s%s\n%s%s' % (start, out, stderr, err, end)
|
||||
|
||||
sys.stderr.write(errmsg)
|
||||
|
||||
# FIXME: re-enable this to make the test fail on internal failures
|
||||
#self.assertEqual(exit_status, 0)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
|
@ -0,0 +1 @@
|
|||
# empty file
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,8 @@
|
|||
void
|
||||
some_c_function(void)
|
||||
{
|
||||
int a, b, c;
|
||||
|
||||
a = 1;
|
||||
b = 2;
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
cdef extern from "stdio.h":
|
||||
int puts(char *s)
|
||||
|
||||
cdef extern from "cfuncs.h":
|
||||
void some_c_function()
|
||||
|
||||
import os
|
||||
|
||||
cdef int c_var = 12
|
||||
python_var = 13
|
||||
|
||||
def spam(a=0):
|
||||
cdef:
|
||||
int b, c
|
||||
|
||||
b = c = d = 0
|
||||
|
||||
b = 1
|
||||
c = 2
|
||||
int(10)
|
||||
puts("spam")
|
||||
os.path.join("foo", "bar")
|
||||
some_c_function()
|
||||
|
||||
cpdef eggs():
|
||||
pass
|
||||
|
||||
cdef ham():
|
||||
pass
|
||||
|
||||
cdef class SomeClass(object):
|
||||
def spam(self):
|
||||
pass
|
||||
|
||||
def outer():
|
||||
cdef object a = "an object"
|
||||
def inner():
|
||||
b = 2
|
||||
# access closed over variables
|
||||
print a, b
|
||||
return inner
|
||||
|
||||
|
||||
outer()()
|
||||
|
||||
spam()
|
||||
print "bye!"
|
||||
|
||||
def use_ham():
|
||||
ham()
|
|
@ -0,0 +1,496 @@
|
|||
"""
|
||||
Tests that run inside GDB.
|
||||
|
||||
Note: debug information is already imported by the file generated by
|
||||
Cython.Debugger.Cygdb.make_command_file()
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import trace
|
||||
import inspect
|
||||
import warnings
|
||||
import unittest
|
||||
import textwrap
|
||||
import tempfile
|
||||
import functools
|
||||
import traceback
|
||||
import itertools
|
||||
#from test import test_support
|
||||
|
||||
import gdb
|
||||
|
||||
from .. import libcython
|
||||
from .. import libpython
|
||||
from . import TestLibCython as test_libcython
|
||||
from ...Utils import add_metaclass
|
||||
|
||||
# for some reason sys.argv is missing in gdb
|
||||
sys.argv = ['gdb']
|
||||
|
||||
|
||||
def print_on_call_decorator(func):
|
||||
@functools.wraps(func)
|
||||
def wrapper(self, *args, **kwargs):
|
||||
_debug(type(self).__name__, func.__name__)
|
||||
|
||||
try:
|
||||
return func(self, *args, **kwargs)
|
||||
except Exception:
|
||||
_debug("An exception occurred:", traceback.format_exc())
|
||||
raise
|
||||
|
||||
return wrapper
|
||||
|
||||
class TraceMethodCallMeta(type):
|
||||
|
||||
def __init__(self, name, bases, dict):
|
||||
for func_name, func in dict.items():
|
||||
if inspect.isfunction(func):
|
||||
setattr(self, func_name, print_on_call_decorator(func))
|
||||
|
||||
|
||||
@add_metaclass(TraceMethodCallMeta)
|
||||
class DebugTestCase(unittest.TestCase):
|
||||
"""
|
||||
Base class for test cases. On teardown it kills the inferior and unsets
|
||||
all breakpoints.
|
||||
"""
|
||||
|
||||
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, cast_to=None):
|
||||
result = gdb.parse_and_eval('$cy_cvalue("%s")' % varname)
|
||||
if cast_to:
|
||||
result = cast_to(result)
|
||||
|
||||
return result
|
||||
|
||||
def local_info(self):
|
||||
return gdb.execute('info locals', to_string=True)
|
||||
|
||||
def lineno_equals(self, source_line=None, lineno=None):
|
||||
if source_line is not None:
|
||||
lineno = test_libcython.source_to_lineno[source_line]
|
||||
frame = gdb.selected_frame()
|
||||
self.assertEqual(libcython.cython_info.lineno(frame), lineno)
|
||||
|
||||
def break_and_run(self, source_line):
|
||||
break_lineno = test_libcython.source_to_lineno[source_line]
|
||||
gdb.execute('cy break codefile:%d' % break_lineno, to_string=True)
|
||||
gdb.execute('run', to_string=True)
|
||||
|
||||
def tearDown(self):
|
||||
gdb.execute('delete breakpoints', to_string=True)
|
||||
try:
|
||||
gdb.execute('kill inferior 1', to_string=True)
|
||||
except RuntimeError:
|
||||
pass
|
||||
|
||||
gdb.execute('set args -c "import codefile"')
|
||||
|
||||
|
||||
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', '__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, (self.eggs_func, 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', 'some_c_function']))
|
||||
|
||||
expected_lineno = test_libcython.source_to_lineno['def spam(a=0):']
|
||||
self.assertEqual(self.spam_func.lineno, expected_lineno)
|
||||
self.assertEqual(sorted(self.spam_func.locals), list('abcd'))
|
||||
|
||||
|
||||
class TestParameters(unittest.TestCase):
|
||||
|
||||
def test_parameters(self):
|
||||
gdb.execute('set cy_colorize_code on')
|
||||
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):
|
||||
breakpoint_amount = len(gdb.breakpoints() or ())
|
||||
gdb.execute('cy break codefile.spam')
|
||||
|
||||
self.assertEqual(len(gdb.breakpoints()), breakpoint_amount + 1)
|
||||
bp = gdb.breakpoints()[-1]
|
||||
self.assertEqual(bp.type, gdb.BP_BREAKPOINT)
|
||||
assert self.spam_func.cname in bp.location
|
||||
assert bp.enabled
|
||||
|
||||
def test_python_break(self):
|
||||
gdb.execute('cy break -p join')
|
||||
assert 'def join(' in gdb.execute('cy run', to_string=True)
|
||||
|
||||
def test_break_lineno(self):
|
||||
beginline = 'import os'
|
||||
nextline = 'cdef int c_var = 12'
|
||||
|
||||
self.break_and_run(beginline)
|
||||
self.lineno_equals(beginline)
|
||||
step_result = gdb.execute('cy step', to_string=True)
|
||||
self.lineno_equals(nextline)
|
||||
assert step_result.rstrip().endswith(nextline)
|
||||
|
||||
|
||||
class TestKilled(DebugTestCase):
|
||||
|
||||
def test_abort(self):
|
||||
gdb.execute("set args -c 'import os; os.abort()'")
|
||||
output = gdb.execute('cy run', to_string=True)
|
||||
assert 'abort' in output.lower()
|
||||
|
||||
|
||||
class DebugStepperTestCase(DebugTestCase):
|
||||
|
||||
def step(self, varnames_and_values, source_line=None, lineno=None):
|
||||
gdb.execute(self.command)
|
||||
for varname, value in varnames_and_values:
|
||||
self.assertEqual(self.read_var(varname), value, self.local_info())
|
||||
|
||||
self.lineno_equals(source_line, lineno)
|
||||
|
||||
|
||||
class TestStep(DebugStepperTestCase):
|
||||
"""
|
||||
Test stepping. Stepping happens in the code found in
|
||||
Cython/Debugger/Tests/codefile.
|
||||
"""
|
||||
|
||||
def test_cython_step(self):
|
||||
gdb.execute('cy break codefile.spam')
|
||||
|
||||
gdb.execute('run', to_string=True)
|
||||
self.lineno_equals('def spam(a=0):')
|
||||
|
||||
gdb.execute('cy step', to_string=True)
|
||||
self.lineno_equals('b = c = d = 0')
|
||||
|
||||
self.command = 'cy step'
|
||||
self.step([('b', 0)], source_line='b = 1')
|
||||
self.step([('b', 1), ('c', 0)], source_line='c = 2')
|
||||
self.step([('c', 2)], source_line='int(10)')
|
||||
self.step([], source_line='puts("spam")')
|
||||
|
||||
gdb.execute('cont', to_string=True)
|
||||
self.assertEqual(len(gdb.inferiors()), 1)
|
||||
self.assertEqual(gdb.inferiors()[0].pid, 0)
|
||||
|
||||
def test_c_step(self):
|
||||
self.break_and_run('some_c_function()')
|
||||
gdb.execute('cy step', to_string=True)
|
||||
self.assertEqual(gdb.selected_frame().name(), 'some_c_function')
|
||||
|
||||
def test_python_step(self):
|
||||
self.break_and_run('os.path.join("foo", "bar")')
|
||||
|
||||
result = gdb.execute('cy step', to_string=True)
|
||||
|
||||
curframe = gdb.selected_frame()
|
||||
self.assertEqual(curframe.name(), 'PyEval_EvalFrameEx')
|
||||
|
||||
pyframe = libpython.Frame(curframe).get_pyop()
|
||||
# With Python 3 inferiors, pyframe.co_name will return a PyUnicodePtr,
|
||||
# be compatible
|
||||
frame_name = pyframe.co_name.proxyval(set())
|
||||
self.assertEqual(frame_name, 'join')
|
||||
assert re.match(r'\d+ def join\(', result), result
|
||||
|
||||
|
||||
class TestNext(DebugStepperTestCase):
|
||||
|
||||
def test_cython_next(self):
|
||||
self.break_and_run('c = 2')
|
||||
|
||||
lines = (
|
||||
'int(10)',
|
||||
'puts("spam")',
|
||||
'os.path.join("foo", "bar")',
|
||||
'some_c_function()',
|
||||
)
|
||||
|
||||
for line in lines:
|
||||
gdb.execute('cy next')
|
||||
self.lineno_equals(line)
|
||||
|
||||
|
||||
class TestLocalsGlobals(DebugTestCase):
|
||||
|
||||
def test_locals(self):
|
||||
self.break_and_run('int(10)')
|
||||
|
||||
result = gdb.execute('cy locals', to_string=True)
|
||||
assert 'a = 0', repr(result)
|
||||
assert 'b = (int) 1', result
|
||||
assert 'c = (int) 2' in result, repr(result)
|
||||
|
||||
def test_globals(self):
|
||||
self.break_and_run('int(10)')
|
||||
|
||||
result = gdb.execute('cy globals', to_string=True)
|
||||
assert '__name__ ' in result, repr(result)
|
||||
assert '__doc__ ' in result, repr(result)
|
||||
assert 'os ' in result, repr(result)
|
||||
assert 'c_var ' in result, repr(result)
|
||||
assert 'python_var ' in result, repr(result)
|
||||
|
||||
|
||||
class TestBacktrace(DebugTestCase):
|
||||
|
||||
def test_backtrace(self):
|
||||
libcython.parameters.colorize_code.value = False
|
||||
|
||||
self.break_and_run('os.path.join("foo", "bar")')
|
||||
|
||||
def match_backtrace_output(result):
|
||||
assert re.search(r'\#\d+ *0x.* in spam\(\) at .*codefile\.pyx:22',
|
||||
result), result
|
||||
assert 'os.path.join("foo", "bar")' in result, result
|
||||
|
||||
result = gdb.execute('cy bt', to_string=True)
|
||||
match_backtrace_output(result)
|
||||
|
||||
result = gdb.execute('cy bt -a', to_string=True)
|
||||
match_backtrace_output(result)
|
||||
|
||||
# Apparently not everyone has main()
|
||||
# assert re.search(r'\#0 *0x.* in main\(\)', result), result
|
||||
|
||||
|
||||
class TestFunctions(DebugTestCase):
|
||||
|
||||
def test_functions(self):
|
||||
self.break_and_run('c = 2')
|
||||
result = gdb.execute('print $cy_cname("b")', to_string=True)
|
||||
assert re.search('__pyx_.*b', result), result
|
||||
|
||||
result = gdb.execute('print $cy_lineno()', to_string=True)
|
||||
supposed_lineno = test_libcython.source_to_lineno['c = 2']
|
||||
assert str(supposed_lineno) in result, (supposed_lineno, result)
|
||||
|
||||
result = gdb.execute('print $cy_cvalue("b")', to_string=True)
|
||||
assert '= 1' in result
|
||||
|
||||
|
||||
class TestPrint(DebugTestCase):
|
||||
|
||||
def test_print(self):
|
||||
self.break_and_run('c = 2')
|
||||
result = gdb.execute('cy print b', to_string=True)
|
||||
self.assertEqual('b = (int) 1\n', result)
|
||||
|
||||
|
||||
class TestUpDown(DebugTestCase):
|
||||
|
||||
def test_updown(self):
|
||||
self.break_and_run('os.path.join("foo", "bar")')
|
||||
gdb.execute('cy step')
|
||||
self.assertRaises(RuntimeError, gdb.execute, 'cy down')
|
||||
|
||||
result = gdb.execute('cy up', to_string=True)
|
||||
assert 'spam()' in result
|
||||
assert 'os.path.join("foo", "bar")' in result
|
||||
|
||||
|
||||
class TestExec(DebugTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestExec, self).setUp()
|
||||
self.fd, self.tmpfilename = tempfile.mkstemp()
|
||||
self.tmpfile = os.fdopen(self.fd, 'r+')
|
||||
|
||||
def tearDown(self):
|
||||
super(TestExec, self).tearDown()
|
||||
|
||||
try:
|
||||
self.tmpfile.close()
|
||||
finally:
|
||||
os.remove(self.tmpfilename)
|
||||
|
||||
def eval_command(self, command):
|
||||
gdb.execute('cy exec open(%r, "w").write(str(%s))' %
|
||||
(self.tmpfilename, command))
|
||||
return self.tmpfile.read().strip()
|
||||
|
||||
def test_cython_exec(self):
|
||||
self.break_and_run('os.path.join("foo", "bar")')
|
||||
|
||||
# test normal behaviour
|
||||
self.assertEqual("[0]", self.eval_command('[a]'))
|
||||
|
||||
# test multiline code
|
||||
result = gdb.execute(textwrap.dedent('''\
|
||||
cy exec
|
||||
pass
|
||||
|
||||
"nothing"
|
||||
end
|
||||
'''))
|
||||
result = self.tmpfile.read().rstrip()
|
||||
self.assertEqual('', result)
|
||||
|
||||
def test_python_exec(self):
|
||||
self.break_and_run('os.path.join("foo", "bar")')
|
||||
gdb.execute('cy step')
|
||||
|
||||
gdb.execute('cy exec some_random_var = 14')
|
||||
self.assertEqual('14', self.eval_command('some_random_var'))
|
||||
|
||||
|
||||
class CySet(DebugTestCase):
|
||||
|
||||
def test_cyset(self):
|
||||
self.break_and_run('os.path.join("foo", "bar")')
|
||||
|
||||
gdb.execute('cy set a = $cy_eval("{None: []}")')
|
||||
stringvalue = self.read_var("a", cast_to=str)
|
||||
self.assertEqual(stringvalue, "{None: []}")
|
||||
|
||||
|
||||
class TestCyEval(DebugTestCase):
|
||||
"Test the $cy_eval() gdb function."
|
||||
|
||||
def test_cy_eval(self):
|
||||
# This function leaks a few objects in the GDB python process. This
|
||||
# is no biggie
|
||||
self.break_and_run('os.path.join("foo", "bar")')
|
||||
|
||||
result = gdb.execute('print $cy_eval("None")', to_string=True)
|
||||
assert re.match(r'\$\d+ = None\n', result), result
|
||||
|
||||
result = gdb.execute('print $cy_eval("[a]")', to_string=True)
|
||||
assert re.match(r'\$\d+ = \[0\]', result), result
|
||||
|
||||
|
||||
class TestClosure(DebugTestCase):
|
||||
|
||||
def break_and_run_func(self, funcname):
|
||||
gdb.execute('cy break ' + funcname)
|
||||
gdb.execute('cy run')
|
||||
|
||||
def test_inner(self):
|
||||
self.break_and_run_func('inner')
|
||||
self.assertEqual('', gdb.execute('cy locals', to_string=True))
|
||||
|
||||
# Allow the Cython-generated code to initialize the scope variable
|
||||
gdb.execute('cy step')
|
||||
|
||||
self.assertEqual(str(self.read_var('a')), "'an object'")
|
||||
print_result = gdb.execute('cy print a', to_string=True).strip()
|
||||
self.assertEqual(print_result, "a = 'an object'")
|
||||
|
||||
def test_outer(self):
|
||||
self.break_and_run_func('outer')
|
||||
self.assertEqual('', gdb.execute('cy locals', to_string=True))
|
||||
|
||||
# Initialize scope with 'a' uninitialized
|
||||
gdb.execute('cy step')
|
||||
self.assertEqual('', gdb.execute('cy locals', to_string=True))
|
||||
|
||||
# Initialize 'a' to 1
|
||||
gdb.execute('cy step')
|
||||
print_result = gdb.execute('cy print a', to_string=True).strip()
|
||||
self.assertEqual(print_result, "a = 'an object'")
|
||||
|
||||
|
||||
_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)
|
||||
_debug_file.write(' '.join(str(msg) for msg in messages) + '\n')
|
||||
|
||||
|
||||
def run_unittest_in_module(modulename):
|
||||
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)
|
||||
os._exit(1)
|
||||
else:
|
||||
m = __import__(modulename, fromlist=[''])
|
||||
tests = inspect.getmembers(m, inspect.isclass)
|
||||
|
||||
# test_support.run_unittest(tests)
|
||||
|
||||
test_loader = unittest.TestLoader()
|
||||
suite = unittest.TestSuite(
|
||||
[test_loader.loadTestsFromTestCase(cls) for name, cls in tests])
|
||||
|
||||
result = unittest.TextTestRunner(verbosity=1).run(suite)
|
||||
return result.wasSuccessful()
|
||||
|
||||
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(2)
|
||||
|
||||
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(runtests)
|
||||
else:
|
||||
runtests()
|
|
@ -0,0 +1,115 @@
|
|||
# -*- coding: UTF-8 -*-
|
||||
|
||||
"""
|
||||
Test libpython.py. This is already partly tested by test_libcython_in_gdb and
|
||||
Lib/test/test_gdb.py in the Python source. These tests are run in gdb and
|
||||
called from test_libcython_in_gdb.main()
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
import gdb
|
||||
|
||||
from Cython.Debugger import libcython
|
||||
from Cython.Debugger import libpython
|
||||
|
||||
from . import test_libcython_in_gdb
|
||||
from .test_libcython_in_gdb import _debug, inferior_python_version
|
||||
|
||||
|
||||
class TestPrettyPrinters(test_libcython_in_gdb.DebugTestCase):
|
||||
"""
|
||||
Test whether types of Python objects are correctly inferred and that
|
||||
the right libpython.PySomeTypeObjectPtr classes are instantiated.
|
||||
|
||||
Also test whether values are appropriately formatted (don't be too
|
||||
laborious as Lib/test/test_gdb.py already covers this extensively).
|
||||
|
||||
Don't take care of decreffing newly allocated objects as a new
|
||||
interpreter is started for every test anyway.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(TestPrettyPrinters, self).setUp()
|
||||
self.break_and_run('b = c = d = 0')
|
||||
|
||||
def get_pyobject(self, code):
|
||||
value = gdb.parse_and_eval(code)
|
||||
assert libpython.pointervalue(value) != 0
|
||||
return value
|
||||
|
||||
def pyobject_fromcode(self, code, gdbvar=None):
|
||||
if gdbvar is not None:
|
||||
d = {'varname':gdbvar, 'code':code}
|
||||
gdb.execute('set $%(varname)s = %(code)s' % d)
|
||||
code = '$' + gdbvar
|
||||
|
||||
return libpython.PyObjectPtr.from_pyobject_ptr(self.get_pyobject(code))
|
||||
|
||||
def get_repr(self, pyobject):
|
||||
return pyobject.get_truncated_repr(libpython.MAX_OUTPUT_LEN)
|
||||
|
||||
def alloc_bytestring(self, string, gdbvar=None):
|
||||
if inferior_python_version < (3, 0):
|
||||
funcname = 'PyString_FromStringAndSize'
|
||||
else:
|
||||
funcname = 'PyBytes_FromStringAndSize'
|
||||
|
||||
assert b'"' not in string
|
||||
|
||||
# ensure double quotes
|
||||
code = '(PyObject *) %s("%s", %d)' % (funcname, string.decode('iso8859-1'), len(string))
|
||||
return self.pyobject_fromcode(code, gdbvar=gdbvar)
|
||||
|
||||
def alloc_unicodestring(self, string, gdbvar=None):
|
||||
postfix = libpython.get_inferior_unicode_postfix()
|
||||
funcname = 'PyUnicode%s_DecodeUnicodeEscape' % (postfix,)
|
||||
|
||||
data = string.encode("unicode_escape").decode('iso8859-1')
|
||||
return self.pyobject_fromcode(
|
||||
'(PyObject *) %s("%s", %d, "strict")' % (
|
||||
funcname, data.replace('"', r'\"').replace('\\', r'\\'), len(data)),
|
||||
gdbvar=gdbvar)
|
||||
|
||||
def test_bytestring(self):
|
||||
bytestring = self.alloc_bytestring(b"spam")
|
||||
|
||||
if inferior_python_version < (3, 0):
|
||||
bytestring_class = libpython.PyStringObjectPtr
|
||||
expected = repr(b"spam")
|
||||
else:
|
||||
bytestring_class = libpython.PyBytesObjectPtr
|
||||
expected = "b'spam'"
|
||||
|
||||
self.assertEqual(type(bytestring), bytestring_class)
|
||||
self.assertEqual(self.get_repr(bytestring), expected)
|
||||
|
||||
def test_unicode(self):
|
||||
unicode_string = self.alloc_unicodestring(u"spam ἄλφα")
|
||||
|
||||
expected = u"'spam ἄλφα'"
|
||||
if inferior_python_version < (3, 0):
|
||||
expected = 'u' + expected
|
||||
|
||||
self.assertEqual(type(unicode_string), libpython.PyUnicodeObjectPtr)
|
||||
self.assertEqual(self.get_repr(unicode_string), expected)
|
||||
|
||||
def test_int(self):
|
||||
if inferior_python_version < (3, 0):
|
||||
intval = self.pyobject_fromcode('PyInt_FromLong(100)')
|
||||
self.assertEqual(type(intval), libpython.PyIntObjectPtr)
|
||||
self.assertEqual(self.get_repr(intval), '100')
|
||||
|
||||
def test_long(self):
|
||||
longval = self.pyobject_fromcode('PyLong_FromLong(200)',
|
||||
gdbvar='longval')
|
||||
assert gdb.parse_and_eval('$longval->ob_type == &PyLong_Type')
|
||||
|
||||
self.assertEqual(type(longval), libpython.PyLongObjectPtr)
|
||||
self.assertEqual(self.get_repr(longval), '200')
|
||||
|
||||
def test_frame_type(self):
|
||||
frame = self.pyobject_fromcode('PyEval_GetFrame()')
|
||||
|
||||
self.assertEqual(type(frame), libpython.PyFrameObjectPtr)
|
Loading…
Add table
Add a link
Reference in a new issue