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 # With Python 3 inferiors, pyframe.co_name will return a PyUnicodePtr,
237 frame_name
= pyframe
.co_name
.proxyval(set())
238 self
.assertEqual(frame_name
, 'join')
239 assert re
.match(r
'\d+ def join\(', result
), result
242 class TestNext(DebugStepperTestCase
):
244 def test_cython_next(self
):
245 self
.break_and_run('c = 2')
250 'os.path.join("foo", "bar")',
255 gdb
.execute('cy next')
256 self
.lineno_equals(line
)
259 class TestLocalsGlobals(DebugTestCase
):
261 def test_locals(self
):
262 self
.break_and_run('int(10)')
264 result
= gdb
.execute('cy locals', to_string
=True)
265 assert 'a = 0', repr(result
)
266 assert 'b = (int) 1', result
267 assert 'c = (int) 2' in result
, repr(result
)
269 def test_globals(self
):
270 self
.break_and_run('int(10)')
272 result
= gdb
.execute('cy globals', to_string
=True)
273 assert '__name__ ' in result
, repr(result
)
274 assert '__doc__ ' in result
, repr(result
)
275 assert 'os ' in result
, repr(result
)
276 assert 'c_var ' in result
, repr(result
)
277 assert 'python_var ' in result
, repr(result
)
280 class TestBacktrace(DebugTestCase
):
282 def test_backtrace(self
):
283 libcython
.parameters
.colorize_code
.value
= False
285 self
.break_and_run('os.path.join("foo", "bar")')
287 def match_backtrace_output(result
):
288 assert re
.search(r
'\#\d+ *0x.* in spam\(\) at .*codefile\.pyx:22',
290 assert 'os.path.join("foo", "bar")' in result
, result
292 result
= gdb
.execute('cy bt', to_string
=True)
293 match_backtrace_output(result
)
295 result
= gdb
.execute('cy bt -a', to_string
=True)
296 match_backtrace_output(result
)
298 # Apparently not everyone has main()
299 # assert re.search(r'\#0 *0x.* in main\(\)', result), result
302 class TestFunctions(DebugTestCase
):
304 def test_functions(self
):
305 self
.break_and_run('c = 2')
306 result
= gdb
.execute('print $cy_cname("b")', to_string
=True)
307 assert re
.search('__pyx_.*b', result
), result
309 result
= gdb
.execute('print $cy_lineno()', to_string
=True)
310 supposed_lineno
= test_libcython
.source_to_lineno
['c = 2']
311 assert str(supposed_lineno
) in result
, (supposed_lineno
, result
)
313 result
= gdb
.execute('print $cy_cvalue("b")', to_string
=True)
314 assert '= 1' in result
317 class TestPrint(DebugTestCase
):
319 def test_print(self
):
320 self
.break_and_run('c = 2')
321 result
= gdb
.execute('cy print b', to_string
=True)
322 self
.assertEqual('b = (int) 1\n', result
)
325 class TestUpDown(DebugTestCase
):
327 def test_updown(self
):
328 self
.break_and_run('os.path.join("foo", "bar")')
329 gdb
.execute('cy step')
330 self
.assertRaises(RuntimeError, gdb
.execute
, 'cy down')
332 result
= gdb
.execute('cy up', to_string
=True)
333 assert 'spam()' in result
334 assert 'os.path.join("foo", "bar")' in result
337 class TestExec(DebugTestCase
):
340 super(TestExec
, self
).setUp()
341 self
.fd
, self
.tmpfilename
= tempfile
.mkstemp()
342 self
.tmpfile
= os
.fdopen(self
.fd
, 'r+')
345 super(TestExec
, self
).tearDown()
350 os
.remove(self
.tmpfilename
)
352 def eval_command(self
, command
):
353 gdb
.execute('cy exec open(%r, "w").write(str(%s))' %
354 (self
.tmpfilename
, command
))
355 return self
.tmpfile
.read().strip()
357 def test_cython_exec(self
):
358 self
.break_and_run('os.path.join("foo", "bar")')
360 # test normal behaviour
361 self
.assertEqual("[0]", self
.eval_command('[a]'))
363 # test multiline code
364 result
= gdb
.execute(textwrap
.dedent('''\
371 result
= self
.tmpfile
.read().rstrip()
372 self
.assertEqual('', result
)
374 def test_python_exec(self
):
375 self
.break_and_run('os.path.join("foo", "bar")')
376 gdb
.execute('cy step')
378 gdb
.execute('cy exec some_random_var = 14')
379 self
.assertEqual('14', self
.eval_command('some_random_var'))
382 class CySet(DebugTestCase
):
384 def test_cyset(self
):
385 self
.break_and_run('os.path.join("foo", "bar")')
387 gdb
.execute('cy set a = $cy_eval("{None: []}")')
388 stringvalue
= self
.read_var("a", cast_to
=str)
389 self
.assertEqual(stringvalue
, "{None: []}")
392 class TestCyEval(DebugTestCase
):
393 "Test the $cy_eval() gdb function."
395 def test_cy_eval(self
):
396 # This function leaks a few objects in the GDB python process. This
398 self
.break_and_run('os.path.join("foo", "bar")')
400 result
= gdb
.execute('print $cy_eval("None")', to_string
=True)
401 assert re
.match(r
'\$\d+ = None\n', result
), result
403 result
= gdb
.execute('print $cy_eval("[a]")', to_string
=True)
404 assert re
.match(r
'\$\d+ = \[0\]', result
), result
407 class TestClosure(DebugTestCase
):
409 def break_and_run_func(self
, funcname
):
410 gdb
.execute('cy break ' + funcname
)
411 gdb
.execute('cy run')
413 def test_inner(self
):
414 self
.break_and_run_func('inner')
415 self
.assertEqual('', gdb
.execute('cy locals', to_string
=True))
417 # Allow the Cython-generated code to initialize the scope variable
418 gdb
.execute('cy step')
420 self
.assertEqual(str(self
.read_var('a')), "'an object'")
421 print_result
= gdb
.execute('cy print a', to_string
=True).strip()
422 self
.assertEqual(print_result
, "a = 'an object'")
424 def test_outer(self
):
425 self
.break_and_run_func('outer')
426 self
.assertEqual('', gdb
.execute('cy locals', to_string
=True))
428 # Initialize scope with 'a' uninitialized
429 gdb
.execute('cy step')
430 self
.assertEqual('', gdb
.execute('cy locals', to_string
=True))
432 # Initialize 'a' to 1
433 gdb
.execute('cy step')
434 print_result
= gdb
.execute('cy print a', to_string
=True).strip()
435 self
.assertEqual(print_result
, "a = 'an object'")
438 _do_debug
= os
.environ
.get('GDB_DEBUG')
440 _debug_file
= open('/dev/tty', 'w')
442 def _debug(*messages
):
444 messages
= itertools
.chain([sys
._getframe
(1).f_code
.co_name
, ':'],
446 _debug_file
.write(' '.join(str(msg
) for msg
in messages
) + '\n')
449 def run_unittest_in_module(modulename
):
451 gdb
.lookup_type('PyModuleObject')
453 msg
= ("Unable to run tests, Python was not compiled with "
454 "debugging information. Either compile python with "
455 "-g or get a debug build (configure with --with-pydebug).")
459 m
= __import__(modulename
, fromlist
=[''])
460 tests
= inspect
.getmembers(m
, inspect
.isclass
)
462 # test_support.run_unittest(tests)
464 test_loader
= unittest
.TestLoader()
465 suite
= unittest
.TestSuite(
466 [test_loader
.loadTestsFromTestCase(cls
) for name
, cls
in tests
])
468 result
= unittest
.TextTestRunner(verbosity
=1).run(suite
)
469 return result
.wasSuccessful()
473 Run the libcython and libpython tests. Ensure that an appropriate status is
474 returned to the parent test process.
476 from Cython
.Debugger
.Tests
import test_libpython_in_gdb
478 success_libcython
= run_unittest_in_module(__name__
)
479 success_libpython
= run_unittest_in_module(test_libpython_in_gdb
.__name
__)
481 if not success_libcython
or not success_libpython
:
484 def main(version
, trace_code
=False):
485 global inferior_python_version
487 inferior_python_version
= version
490 tracer
= trace
.Trace(count
=False, trace
=True, outfile
=sys
.stderr
,
491 ignoredirs
=[sys
.prefix
, sys
.exec_prefix
])
492 tracer
.runfunc(runtests
)