Spaces:
Build error
Build error
""" | |
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): | |
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)) | |
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() | |