1""" 2Tests that run inside GDB. 3 4Note: debug information is already imported by the file generated by 5Cython.Debugger.Cygdb.make_command_file() 6""" 7 8import os 9import re 10import sys 11import trace 12import inspect 13import warnings 14import unittest 15import textwrap 16import tempfile 17import functools 18import traceback 19import itertools 20from test import test_support 21 22import gdb 23 24from Cython.Debugger import libcython 25from Cython.Debugger import libpython 26from Cython.Debugger.Tests import TestLibCython as test_libcython 27 28# for some reason sys.argv is missing in gdb 29sys.argv = ['gdb'] 30 31 32def print_on_call_decorator(func): 33 @functools.wraps(func) 34 def wrapper(self, *args, **kwargs): 35 _debug(type(self).__name__, func.__name__) 36 37 try: 38 return func(self, *args, **kwargs) 39 except Exception, e: 40 _debug("An exception occurred:", traceback.format_exc(e)) 41 raise 42 43 return wrapper 44 45class TraceMethodCallMeta(type): 46 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)) 51 52 53class DebugTestCase(unittest.TestCase): 54 """ 55 Base class for test cases. On teardown it kills the inferior and unsets 56 all breakpoints. 57 """ 58 59 __metaclass__ = TraceMethodCallMeta 60 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[ 67 'codefile.ham'] 68 self.eggs_func = libcython.cy.functions_by_qualified_name[ 69 'codefile.eggs'] 70 71 def read_var(self, varname, cast_to=None): 72 result = gdb.parse_and_eval('$cy_cvalue("%s")' % varname) 73 if cast_to: 74 result = cast_to(result) 75 76 return result 77 78 def local_info(self): 79 return gdb.execute('info locals', to_string=True) 80 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) 86 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) 91 92 def tearDown(self): 93 gdb.execute('delete breakpoints', to_string=True) 94 try: 95 gdb.execute('kill inferior 1', to_string=True) 96 except RuntimeError: 97 pass 98 99 gdb.execute('set args -c "import codefile"') 100 101 102class TestDebugInformationClasses(DebugTestCase): 103 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) 110 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') 118 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) 124 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 129 130 self.assertEqual(self.spam_func.type, libcython.CObject) 131 self.assertEqual(self.ham_func.type, libcython.CObject) 132 133 self.assertEqual(self.spam_func.arguments, ['a']) 134 self.assertEqual(self.spam_func.step_into_functions, 135 set(['puts', 'some_c_function'])) 136 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')) 140 141 142class TestParameters(unittest.TestCase): 143 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 149 150 151class TestBreak(DebugTestCase): 152 153 def test_break(self): 154 breakpoint_amount = len(gdb.breakpoints() or ()) 155 gdb.execute('cy break codefile.spam') 156 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 161 assert bp.enabled 162 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) 166 167 def test_break_lineno(self): 168 beginline = 'import os' 169 nextline = 'cdef int c_var = 12' 170 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) 176 177 178class TestKilled(DebugTestCase): 179 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() 184 185 186class DebugStepperTestCase(DebugTestCase): 187 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()) 192 193 self.lineno_equals(source_line, lineno) 194 195 196class TestStep(DebugStepperTestCase): 197 """ 198 Test stepping. Stepping happens in the code found in 199 Cython/Debugger/Tests/codefile. 200 """ 201 202 def test_cython_step(self): 203 gdb.execute('cy break codefile.spam') 204 205 gdb.execute('run', to_string=True) 206 self.lineno_equals('def spam(a=0):') 207 208 gdb.execute('cy step', to_string=True) 209 self.lineno_equals('b = c = d = 0') 210 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")') 216 217 gdb.execute('cont', to_string=True) 218 self.assertEqual(len(gdb.inferiors()), 1) 219 self.assertEqual(gdb.inferiors()[0].pid, 0) 220 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') 225 226 def test_python_step(self): 227 self.break_and_run('os.path.join("foo", "bar")') 228 229 result = gdb.execute('cy step', to_string=True) 230 231 curframe = gdb.selected_frame() 232 self.assertEqual(curframe.name(), 'PyEval_EvalFrameEx') 233 234 pyframe = libpython.Frame(curframe).get_pyop() 235 # With Python 3 inferiors, pyframe.co_name will return a PyUnicodePtr, 236 # be compatible 237 frame_name = pyframe.co_name.proxyval(set()) 238 self.assertEqual(frame_name, 'join') 239 assert re.match(r'\d+ def join\(', result), result 240 241 242class TestNext(DebugStepperTestCase): 243 244 def test_cython_next(self): 245 self.break_and_run('c = 2') 246 247 lines = ( 248 'int(10)', 249 'puts("spam")', 250 'os.path.join("foo", "bar")', 251 'some_c_function()', 252 ) 253 254 for line in lines: 255 gdb.execute('cy next') 256 self.lineno_equals(line) 257 258 259class TestLocalsGlobals(DebugTestCase): 260 261 def test_locals(self): 262 self.break_and_run('int(10)') 263 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) 268 269 def test_globals(self): 270 self.break_and_run('int(10)') 271 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) 278 279 280class TestBacktrace(DebugTestCase): 281 282 def test_backtrace(self): 283 libcython.parameters.colorize_code.value = False 284 285 self.break_and_run('os.path.join("foo", "bar")') 286 287 def match_backtrace_output(result): 288 assert re.search(r'\#\d+ *0x.* in spam\(\) at .*codefile\.pyx:22', 289 result), result 290 assert 'os.path.join("foo", "bar")' in result, result 291 292 result = gdb.execute('cy bt', to_string=True) 293 match_backtrace_output(result) 294 295 result = gdb.execute('cy bt -a', to_string=True) 296 match_backtrace_output(result) 297 298 # Apparently not everyone has main() 299 # assert re.search(r'\#0 *0x.* in main\(\)', result), result 300 301 302class TestFunctions(DebugTestCase): 303 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 308 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) 312 313 result = gdb.execute('print $cy_cvalue("b")', to_string=True) 314 assert '= 1' in result 315 316 317class TestPrint(DebugTestCase): 318 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) 323 324 325class TestUpDown(DebugTestCase): 326 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') 331 332 result = gdb.execute('cy up', to_string=True) 333 assert 'spam()' in result 334 assert 'os.path.join("foo", "bar")' in result 335 336 337class TestExec(DebugTestCase): 338 339 def setUp(self): 340 super(TestExec, self).setUp() 341 self.fd, self.tmpfilename = tempfile.mkstemp() 342 self.tmpfile = os.fdopen(self.fd, 'r+') 343 344 def tearDown(self): 345 super(TestExec, self).tearDown() 346 347 try: 348 self.tmpfile.close() 349 finally: 350 os.remove(self.tmpfilename) 351 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() 356 357 def test_cython_exec(self): 358 self.break_and_run('os.path.join("foo", "bar")') 359 360 # test normal behaviour 361 self.assertEqual("[0]", self.eval_command('[a]')) 362 363 # test multiline code 364 result = gdb.execute(textwrap.dedent('''\ 365 cy exec 366 pass 367 368 "nothing" 369 end 370 ''')) 371 result = self.tmpfile.read().rstrip() 372 self.assertEqual('', result) 373 374 def test_python_exec(self): 375 self.break_and_run('os.path.join("foo", "bar")') 376 gdb.execute('cy step') 377 378 gdb.execute('cy exec some_random_var = 14') 379 self.assertEqual('14', self.eval_command('some_random_var')) 380 381 382class CySet(DebugTestCase): 383 384 def test_cyset(self): 385 self.break_and_run('os.path.join("foo", "bar")') 386 387 gdb.execute('cy set a = $cy_eval("{None: []}")') 388 stringvalue = self.read_var("a", cast_to=str) 389 self.assertEqual(stringvalue, "{None: []}") 390 391 392class TestCyEval(DebugTestCase): 393 "Test the $cy_eval() gdb function." 394 395 def test_cy_eval(self): 396 # This function leaks a few objects in the GDB python process. This 397 # is no biggie 398 self.break_and_run('os.path.join("foo", "bar")') 399 400 result = gdb.execute('print $cy_eval("None")', to_string=True) 401 assert re.match(r'\$\d+ = None\n', result), result 402 403 result = gdb.execute('print $cy_eval("[a]")', to_string=True) 404 assert re.match(r'\$\d+ = \[0\]', result), result 405 406 407class TestClosure(DebugTestCase): 408 409 def break_and_run_func(self, funcname): 410 gdb.execute('cy break ' + funcname) 411 gdb.execute('cy run') 412 413 def test_inner(self): 414 self.break_and_run_func('inner') 415 self.assertEqual('', gdb.execute('cy locals', to_string=True)) 416 417 # Allow the Cython-generated code to initialize the scope variable 418 gdb.execute('cy step') 419 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'") 423 424 def test_outer(self): 425 self.break_and_run_func('outer') 426 self.assertEqual('', gdb.execute('cy locals', to_string=True)) 427 428 # Initialize scope with 'a' uninitialized 429 gdb.execute('cy step') 430 self.assertEqual('', gdb.execute('cy locals', to_string=True)) 431 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'") 436 437 438_do_debug = os.environ.get('GDB_DEBUG') 439if _do_debug: 440 _debug_file = open('/dev/tty', 'w') 441 442def _debug(*messages): 443 if _do_debug: 444 messages = itertools.chain([sys._getframe(1).f_code.co_name, ':'], 445 messages) 446 _debug_file.write(' '.join(str(msg) for msg in messages) + '\n') 447 448 449def run_unittest_in_module(modulename): 450 try: 451 gdb.lookup_type('PyModuleObject') 452 except RuntimeError: 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).") 456 warnings.warn(msg) 457 os._exit(1) 458 else: 459 m = __import__(modulename, fromlist=['']) 460 tests = inspect.getmembers(m, inspect.isclass) 461 462 # test_support.run_unittest(tests) 463 464 test_loader = unittest.TestLoader() 465 suite = unittest.TestSuite( 466 [test_loader.loadTestsFromTestCase(cls) for name, cls in tests]) 467 468 result = unittest.TextTestRunner(verbosity=1).run(suite) 469 return result.wasSuccessful() 470 471def runtests(): 472 """ 473 Run the libcython and libpython tests. Ensure that an appropriate status is 474 returned to the parent test process. 475 """ 476 from Cython.Debugger.Tests import test_libpython_in_gdb 477 478 success_libcython = run_unittest_in_module(__name__) 479 success_libpython = run_unittest_in_module(test_libpython_in_gdb.__name__) 480 481 if not success_libcython or not success_libpython: 482 sys.exit(2) 483 484def main(version, trace_code=False): 485 global inferior_python_version 486 487 inferior_python_version = version 488 489 if trace_code: 490 tracer = trace.Trace(count=False, trace=True, outfile=sys.stderr, 491 ignoredirs=[sys.prefix, sys.exec_prefix]) 492 tracer.runfunc(runtests) 493 else: 494 runtests() 495