2 Tests that run inside GDB.
4 Note: debug information is already imported by the file generated by
5 Cython.Debugger.Cygdb.make_command_file()
20 from test import test_support
24 from Cython.Debugger import libcython
25 from Cython.Debugger import libpython
26 from Cython.Debugger.Tests import TestLibCython as test_libcython
28 # for some reason sys.argv is missing in gdb
32 def print_on_call_decorator(func):
33 @functools.wraps(func)
34 def wrapper(self, *args, **kwargs):
35 _debug(type(self).__name__, func.__name__)
38 return func(self, *args, **kwargs)
40 _debug("An exception occurred:", traceback.format_exc(e))
45 class TraceMethodCallMeta(type):
47 def __init__(self, name, bases, dict):
48 for func_name, func in dict.iteritems():
49 if inspect.isfunction(func):
50 setattr(self, func_name, print_on_call_decorator(func))
53 class DebugTestCase(unittest.TestCase):
55 Base class for test cases. On teardown it kills the inferior and unsets
59 __metaclass__ = TraceMethodCallMeta
61 def __init__(self, name):
62 super(DebugTestCase, self).__init__(name)
63 self.cy = libcython.cy
64 self.module = libcython.cy.cython_namespace['codefile']
65 self.spam_func, self.spam_meth = libcython.cy.functions_by_name['spam']
66 self.ham_func = libcython.cy.functions_by_qualified_name[
68 self.eggs_func = libcython.cy.functions_by_qualified_name[
71 def read_var(self, varname, cast_to=None):
72 result = gdb.parse_and_eval('$cy_cvalue("%s")' % varname)
74 result = cast_to(result)
79 return gdb.execute('info locals', to_string=True)
81 def lineno_equals(self, source_line=None, lineno=None):
82 if source_line is not None:
83 lineno = test_libcython.source_to_lineno[source_line]
84 frame = gdb.selected_frame()
85 self.assertEqual(libcython.cython_info.lineno(frame), lineno)
87 def break_and_run(self, source_line):
88 break_lineno = test_libcython.source_to_lineno[source_line]
89 gdb.execute('cy break codefile:%d' % break_lineno, to_string=True)
90 gdb.execute('run', to_string=True)
93 gdb.execute('delete breakpoints', to_string=True)
95 gdb.execute('kill inferior 1', to_string=True)
99 gdb.execute('set args -c "import codefile"')
102 class TestDebugInformationClasses(DebugTestCase):
104 def test_CythonModule(self):
105 "test that debug information was parsed properly into data structures"
106 self.assertEqual(self.module.name, 'codefile')
107 global_vars = ('c_var', 'python_var', '__name__',
108 '__builtins__', '__doc__', '__file__')
109 assert set(global_vars).issubset(self.module.globals)
111 def test_CythonVariable(self):
112 module_globals = self.module.globals
113 c_var = module_globals['c_var']
114 python_var = module_globals['python_var']
115 self.assertEqual(c_var.type, libcython.CObject)
116 self.assertEqual(python_var.type, libcython.PythonObject)
117 self.assertEqual(c_var.qualified_name, 'codefile.c_var')
119 def test_CythonFunction(self):
120 self.assertEqual(self.spam_func.qualified_name, 'codefile.spam')
121 self.assertEqual(self.spam_meth.qualified_name,
122 'codefile.SomeClass.spam')
123 self.assertEqual(self.spam_func.module, self.module)
125 assert self.eggs_func.pf_cname, (self.eggs_func, self.eggs_func.pf_cname)
126 assert not self.ham_func.pf_cname
127 assert not self.spam_func.pf_cname
128 assert not self.spam_meth.pf_cname
130 self.assertEqual(self.spam_func.type, libcython.CObject)
131 self.assertEqual(self.ham_func.type, libcython.CObject)
133 self.assertEqual(self.spam_func.arguments, ['a'])
134 self.assertEqual(self.spam_func.step_into_functions,
135 set(['puts', 'some_c_function']))
137 expected_lineno = test_libcython.source_to_lineno['def spam(a=0):']
138 self.assertEqual(self.spam_func.lineno, expected_lineno)
139 self.assertEqual(sorted(self.spam_func.locals), list('abcd'))
142 class TestParameters(unittest.TestCase):
144 def test_parameters(self):
145 gdb.execute('set cy_colorize_code on')
146 assert libcython.parameters.colorize_code
147 gdb.execute('set cy_colorize_code off')
148 assert not libcython.parameters.colorize_code
151 class TestBreak(DebugTestCase):
153 def test_break(self):
154 breakpoint_amount = len(gdb.breakpoints() or ())
155 gdb.execute('cy break codefile.spam')
157 self.assertEqual(len(gdb.breakpoints()), breakpoint_amount + 1)
158 bp = gdb.breakpoints()[-1]
159 self.assertEqual(bp.type, gdb.BP_BREAKPOINT)
160 assert self.spam_func.cname in bp.location
163 def test_python_break(self):
164 gdb.execute('cy break -p join')
165 assert 'def join(' in gdb.execute('cy run', to_string=True)
167 def test_break_lineno(self):
168 beginline = 'import os'
169 nextline = 'cdef int c_var = 12'
171 self.break_and_run(beginline)
172 self.lineno_equals(beginline)
173 step_result = gdb.execute('cy step', to_string=True)
174 self.lineno_equals(nextline)
175 assert step_result.rstrip().endswith(nextline)
178 class TestKilled(DebugTestCase):
180 def test_abort(self):
181 gdb.execute("set args -c 'import os; os.abort()'")
182 output = gdb.execute('cy run', to_string=True)
183 assert 'abort' in output.lower()
186 class DebugStepperTestCase(DebugTestCase):
188 def step(self, varnames_and_values, source_line=None, lineno=None):
189 gdb.execute(self.command)
190 for varname, value in varnames_and_values:
191 self.assertEqual(self.read_var(varname), value, self.local_info())
193 self.lineno_equals(source_line, lineno)
196 class TestStep(DebugStepperTestCase):
198 Test stepping. Stepping happens in the code found in
199 Cython/Debugger/Tests/codefile.
202 def test_cython_step(self):
203 gdb.execute('cy break codefile.spam')
205 gdb.execute('run', to_string=True)
206 self.lineno_equals('def spam(a=0):')
208 gdb.execute('cy step', to_string=True)
209 self.lineno_equals('b = c = d = 0')
211 self.command = 'cy step'
212 self.step([('b', 0)], source_line='b = 1')
213 self.step([('b', 1), ('c', 0)], source_line='c = 2')
214 self.step([('c', 2)], source_line='int(10)')
215 self.step([], source_line='puts("spam")')
217 gdb.execute('cont', to_string=True)
218 self.assertEqual(len(gdb.inferiors()), 1)
219 self.assertEqual(gdb.inferiors()[0].pid, 0)
221 def test_c_step(self):
222 self.break_and_run('some_c_function()')
223 gdb.execute('cy step', to_string=True)
224 self.assertEqual(gdb.selected_frame().name(), 'some_c_function')
226 def test_python_step(self):
227 self.break_and_run('os.path.join("foo", "bar")')
229 result = gdb.execute('cy step', to_string=True)
231 curframe = gdb.selected_frame()
232 self.assertEqual(curframe.name(), 'PyEval_EvalFrameEx')
234 pyframe = libpython.Frame(curframe).get_pyop()
235 self.assertEqual(str(pyframe.co_name), 'join')
236 assert re.match(r'\d+ def join\(', result), result
239 class TestNext(DebugStepperTestCase):
241 def test_cython_next(self):
242 self.break_and_run('c = 2')
247 'os.path.join("foo", "bar")',
252 gdb.execute('cy next')
253 self.lineno_equals(line)
256 class TestLocalsGlobals(DebugTestCase):
258 def test_locals(self):
259 self.break_and_run('int(10)')
261 result = gdb.execute('cy locals', to_string=True)
262 assert 'a = 0', repr(result)
263 assert 'b = (int) 1', result
264 assert 'c = (int) 2' in result, repr(result)
266 def test_globals(self):
267 self.break_and_run('int(10)')
269 result = gdb.execute('cy globals', to_string=True)
270 assert '__name__ ' in result, repr(result)
271 assert '__doc__ ' in result, repr(result)
272 assert 'os ' in result, repr(result)
273 assert 'c_var ' in result, repr(result)
274 assert 'python_var ' in result, repr(result)
277 class TestBacktrace(DebugTestCase):
279 def test_backtrace(self):
280 libcython.parameters.colorize_code.value = False
282 self.break_and_run('os.path.join("foo", "bar")')
283 result = gdb.execute('cy bt', to_string=True)
285 _debug(libpython.execute, libpython._execute, gdb.execute)
286 _debug(gdb.execute('cy list', to_string=True))
289 assert re.search(r'\#\d+ *0x.* in spam\(\) at .*codefile\.pyx:22',
291 assert 'os.path.join("foo", "bar")' in result, result
293 gdb.execute("cy step")
296 result = gdb.execute('cy bt -a', to_string=True)
297 assert re.search(r'\#0 *0x.* in main\(\)', result), result
300 class TestFunctions(DebugTestCase):
302 def test_functions(self):
303 self.break_and_run('c = 2')
304 result = gdb.execute('print $cy_cname("b")', to_string=True)
305 assert re.search('__pyx_.*b', result), result
307 result = gdb.execute('print $cy_lineno()', to_string=True)
308 supposed_lineno = test_libcython.source_to_lineno['c = 2']
309 assert str(supposed_lineno) in result, (supposed_lineno, result)
311 result = gdb.execute('print $cy_cvalue("b")', to_string=True)
312 assert '= 1' in result
315 class TestPrint(DebugTestCase):
317 def test_print(self):
318 self.break_and_run('c = 2')
319 result = gdb.execute('cy print b', to_string=True)
320 self.assertEqual('b = (int) 1\n', result)
323 class TestUpDown(DebugTestCase):
325 def test_updown(self):
326 self.break_and_run('os.path.join("foo", "bar")')
327 gdb.execute('cy step')
328 self.assertRaises(RuntimeError, gdb.execute, 'cy down')
330 result = gdb.execute('cy up', to_string=True)
331 assert 'spam()' in result
332 assert 'os.path.join("foo", "bar")' in result
335 class TestExec(DebugTestCase):
338 super(TestExec, self).setUp()
339 self.fd, self.tmpfilename = tempfile.mkstemp()
340 self.tmpfile = os.fdopen(self.fd, 'r+')
343 super(TestExec, self).tearDown()
348 os.remove(self.tmpfilename)
350 def eval_command(self, command):
351 gdb.execute('cy exec open(%r, "w").write(str(%s))' %
352 (self.tmpfilename, command))
353 return self.tmpfile.read().strip()
355 def test_cython_exec(self):
356 self.break_and_run('os.path.join("foo", "bar")')
358 # test normal behaviour
359 self.assertEqual("[0]", self.eval_command('[a]'))
361 # test multiline code
362 result = gdb.execute(textwrap.dedent('''\
369 result = self.tmpfile.read().rstrip()
370 self.assertEqual('', result)
372 def test_python_exec(self):
373 self.break_and_run('os.path.join("foo", "bar")')
374 gdb.execute('cy step')
376 gdb.execute('cy exec some_random_var = 14')
377 self.assertEqual('14', self.eval_command('some_random_var'))
379 class TestClosure(DebugTestCase):
381 def break_and_run_func(self, funcname):
382 gdb.execute('cy break ' + funcname)
383 gdb.execute('cy run')
385 def test_inner(self):
386 self.break_and_run_func('inner')
387 self.assertEqual('', gdb.execute('cy locals', to_string=True))
389 # Allow the Cython-generated code to initialize the scope variable
390 gdb.execute('cy step')
392 self.assertEqual(str(self.read_var('a')), "'an object'")
393 print_result = gdb.execute('cy print a', to_string=True).strip()
394 self.assertEqual(print_result, "a = 'an object'")
396 def test_outer(self):
397 self.break_and_run_func('outer')
398 self.assertEqual('', gdb.execute('cy locals', to_string=True))
400 # Initialize scope with 'a' uninitialized
401 gdb.execute('cy step')
402 self.assertEqual('', gdb.execute('cy locals', to_string=True))
404 # Initialize 'a' to 1
405 gdb.execute('cy step')
406 print_result = gdb.execute('cy print a', to_string=True).strip()
407 self.assertEqual(print_result, "a = 'an object'")
410 _do_debug = os.environ.get('GDB_DEBUG')
412 _debug_file = open('/dev/tty', 'w')
414 def _debug(*messages):
416 messages = itertools.chain([sys._getframe(1).f_code.co_name, ':'],
418 _debug_file.write(' '.join(str(msg) for msg in messages) + '\n')
421 def run_unittest_in_module(modulename):
423 gdb.lookup_type('PyModuleObject')
425 msg = ("Unable to run tests, Python was not compiled with "
426 "debugging information. Either compile python with "
427 "-g or get a debug build (configure with --with-pydebug).")
431 m = __import__(modulename, fromlist=[''])
432 tests = inspect.getmembers(m, inspect.isclass)
434 # test_support.run_unittest(tests)
436 test_loader = unittest.TestLoader()
437 suite = unittest.TestSuite(
438 [test_loader.loadTestsFromTestCase(cls) for name, cls in tests])
440 result = unittest.TextTestRunner(verbosity=1).run(suite)
441 return result.wasSuccessful()
445 Run the libcython and libpython tests. Ensure that an appropriate status is
446 returned to the parent test process.
448 from Cython.Debugger.Tests import test_libpython_in_gdb
450 success_libcython = run_unittest_in_module(__name__)
451 success_libpython = run_unittest_in_module(test_libpython_in_gdb.__name__)
453 if not success_libcython or not success_libpython:
456 def main(version, trace_code=False):
457 global inferior_python_version
459 inferior_python_version = version
462 tracer = trace.Trace(count=False, trace=True, outfile=sys.stderr,
463 ignoredirs=[sys.prefix, sys.exec_prefix])
464 tracer.runfunc(runtests)