OLD | NEW |
(Empty) | |
| 1 """ |
| 2 Tests that run inside GDB. |
| 3 |
| 4 Note: debug information is already imported by the file generated by |
| 5 Cython.Debugger.Cygdb.make_command_file() |
| 6 """ |
| 7 |
| 8 import os |
| 9 import re |
| 10 import sys |
| 11 import trace |
| 12 import inspect |
| 13 import warnings |
| 14 import unittest |
| 15 import textwrap |
| 16 import tempfile |
| 17 import functools |
| 18 import traceback |
| 19 import itertools |
| 20 from test import test_support |
| 21 |
| 22 import gdb |
| 23 |
| 24 from Cython.Debugger import libcython |
| 25 from Cython.Debugger import libpython |
| 26 from Cython.Debugger.Tests import TestLibCython as test_libcython |
| 27 |
| 28 # for some reason sys.argv is missing in gdb |
| 29 sys.argv = ['gdb'] |
| 30 |
| 31 |
| 32 def 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 |
| 45 class 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 |
| 53 class 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 |
| 102 class 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 |
| 142 class 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 |
| 151 class 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 |
| 178 class 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 |
| 186 class 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 |
| 196 class 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 |
| 242 class 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 |
| 259 class 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 |
| 280 class 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 |
| 302 class 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 |
| 317 class 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 |
| 325 class 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 |
| 337 class 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 |
| 382 class 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 |
| 392 class 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 |
| 407 class 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') |
| 439 if _do_debug: |
| 440 _debug_file = open('/dev/tty', 'w') |
| 441 |
| 442 def _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 |
| 449 def 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 |
| 471 def 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 |
| 484 def 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() |
OLD | NEW |