1""" Test the bdb module. 2 3 A test defines a list of tuples that may be seen as paired tuples, each 4 pair being defined by 'expect_tuple, set_tuple' as follows: 5 6 ([event, [lineno[, co_name[, eargs]]]]), (set_type, [sargs]) 7 8 * 'expect_tuple' describes the expected current state of the Bdb instance. 9 It may be the empty tuple and no check is done in that case. 10 * 'set_tuple' defines the set_*() method to be invoked when the Bdb 11 instance reaches this state. 12 13 Example of an 'expect_tuple, set_tuple' pair: 14 15 ('line', 2, 'tfunc_main'), ('step', ) 16 17 Definitions of the members of the 'expect_tuple': 18 event: 19 Name of the trace event. The set methods that do not give back 20 control to the tracer [1] do not trigger a tracer event and in 21 that case the next 'event' may be 'None' by convention, its value 22 is not checked. 23 [1] Methods that trigger a trace event are set_step(), set_next(), 24 set_return(), set_until() and set_continue(). 25 lineno: 26 Line number. Line numbers are relative to the start of the 27 function when tracing a function in the test_bdb module (i.e. this 28 module). 29 co_name: 30 Name of the function being currently traced. 31 eargs: 32 A tuple: 33 * On an 'exception' event the tuple holds a class object, the 34 current exception must be an instance of this class. 35 * On a 'line' event, the tuple holds a dictionary and a list. The 36 dictionary maps each breakpoint number that has been hit on this 37 line to its hits count. The list holds the list of breakpoint 38 number temporaries that are being deleted. 39 40 Definitions of the members of the 'set_tuple': 41 set_type: 42 The type of the set method to be invoked. This may 43 be the type of one of the Bdb set methods: 'step', 'next', 44 'until', 'return', 'continue', 'break', 'quit', or the type of one 45 of the set methods added by test_bdb.Bdb: 'ignore', 'enable', 46 'disable', 'clear', 'up', 'down'. 47 sargs: 48 The arguments of the set method if any, packed in a tuple. 49""" 50 51import bdb as _bdb 52import sys 53import os 54import unittest 55import textwrap 56import importlib 57import linecache 58from contextlib import contextmanager 59from itertools import islice, repeat 60from test.support import import_helper 61from test.support import os_helper 62 63 64class BdbException(Exception): pass 65class BdbError(BdbException): """Error raised by the Bdb instance.""" 66class BdbSyntaxError(BdbException): """Syntax error in the test case.""" 67class BdbNotExpectedError(BdbException): """Unexpected result.""" 68 69# When 'dry_run' is set to true, expect tuples are ignored and the actual 70# state of the tracer is printed after running each set_*() method of the test 71# case. The full list of breakpoints and their attributes is also printed 72# after each 'line' event where a breakpoint has been hit. 73dry_run = 0 74 75def reset_Breakpoint(): 76 _bdb.Breakpoint.clearBreakpoints() 77 78def info_breakpoints(): 79 bp_list = [bp for bp in _bdb.Breakpoint.bpbynumber if bp] 80 if not bp_list: 81 return '' 82 83 header_added = False 84 for bp in bp_list: 85 if not header_added: 86 info = 'BpNum Temp Enb Hits Ignore Where\n' 87 header_added = True 88 89 disp = 'yes ' if bp.temporary else 'no ' 90 enab = 'yes' if bp.enabled else 'no ' 91 info += ('%-5d %s %s %-4d %-6d at %s:%d' % 92 (bp.number, disp, enab, bp.hits, bp.ignore, 93 os.path.basename(bp.file), bp.line)) 94 if bp.cond: 95 info += '\n\tstop only if %s' % (bp.cond,) 96 info += '\n' 97 return info 98 99class Bdb(_bdb.Bdb): 100 """Extend Bdb to enhance test coverage.""" 101 102 def trace_dispatch(self, frame, event, arg): 103 self.currentbp = None 104 return super().trace_dispatch(frame, event, arg) 105 106 def set_break(self, filename, lineno, temporary=False, cond=None, 107 funcname=None): 108 if isinstance(funcname, str): 109 if filename == __file__: 110 globals_ = globals() 111 else: 112 module = importlib.import_module(filename[:-3]) 113 globals_ = module.__dict__ 114 func = eval(funcname, globals_) 115 code = func.__code__ 116 filename = code.co_filename 117 lineno = code.co_firstlineno 118 funcname = code.co_name 119 120 res = super().set_break(filename, lineno, temporary=temporary, 121 cond=cond, funcname=funcname) 122 if isinstance(res, str): 123 raise BdbError(res) 124 return res 125 126 def get_stack(self, f, t): 127 self.stack, self.index = super().get_stack(f, t) 128 self.frame = self.stack[self.index][0] 129 return self.stack, self.index 130 131 def set_ignore(self, bpnum): 132 """Increment the ignore count of Breakpoint number 'bpnum'.""" 133 bp = self.get_bpbynumber(bpnum) 134 bp.ignore += 1 135 136 def set_enable(self, bpnum): 137 bp = self.get_bpbynumber(bpnum) 138 bp.enabled = True 139 140 def set_disable(self, bpnum): 141 bp = self.get_bpbynumber(bpnum) 142 bp.enabled = False 143 144 def set_clear(self, fname, lineno): 145 err = self.clear_break(fname, lineno) 146 if err: 147 raise BdbError(err) 148 149 def set_up(self): 150 """Move up in the frame stack.""" 151 if not self.index: 152 raise BdbError('Oldest frame') 153 self.index -= 1 154 self.frame = self.stack[self.index][0] 155 156 def set_down(self): 157 """Move down in the frame stack.""" 158 if self.index + 1 == len(self.stack): 159 raise BdbError('Newest frame') 160 self.index += 1 161 self.frame = self.stack[self.index][0] 162 163class Tracer(Bdb): 164 """A tracer for testing the bdb module.""" 165 166 def __init__(self, expect_set, skip=None, dry_run=False, test_case=None): 167 super().__init__(skip=skip) 168 self.expect_set = expect_set 169 self.dry_run = dry_run 170 self.header = ('Dry-run results for %s:' % test_case if 171 test_case is not None else None) 172 self.init_test() 173 174 def init_test(self): 175 self.cur_except = None 176 self.expect_set_no = 0 177 self.breakpoint_hits = None 178 self.expected_list = list(islice(self.expect_set, 0, None, 2)) 179 self.set_list = list(islice(self.expect_set, 1, None, 2)) 180 181 def trace_dispatch(self, frame, event, arg): 182 # On an 'exception' event, call_exc_trace() in Python/ceval.c discards 183 # a BdbException raised by the Tracer instance, so we raise it on the 184 # next trace_dispatch() call that occurs unless the set_quit() or 185 # set_continue() method has been invoked on the 'exception' event. 186 if self.cur_except is not None: 187 raise self.cur_except 188 189 if event == 'exception': 190 try: 191 res = super().trace_dispatch(frame, event, arg) 192 return res 193 except BdbException as e: 194 self.cur_except = e 195 return self.trace_dispatch 196 else: 197 return super().trace_dispatch(frame, event, arg) 198 199 def user_call(self, frame, argument_list): 200 # Adopt the same behavior as pdb and, as a side effect, skip also the 201 # first 'call' event when the Tracer is started with Tracer.runcall() 202 # which may be possibly considered as a bug. 203 if not self.stop_here(frame): 204 return 205 self.process_event('call', frame, argument_list) 206 self.next_set_method() 207 208 def user_line(self, frame): 209 self.process_event('line', frame) 210 211 if self.dry_run and self.breakpoint_hits: 212 info = info_breakpoints().strip('\n') 213 # Indent each line. 214 for line in info.split('\n'): 215 print(' ' + line) 216 self.delete_temporaries() 217 self.breakpoint_hits = None 218 219 self.next_set_method() 220 221 def user_return(self, frame, return_value): 222 self.process_event('return', frame, return_value) 223 self.next_set_method() 224 225 def user_exception(self, frame, exc_info): 226 self.exc_info = exc_info 227 self.process_event('exception', frame) 228 self.next_set_method() 229 230 def do_clear(self, arg): 231 # The temporary breakpoints are deleted in user_line(). 232 bp_list = [self.currentbp] 233 self.breakpoint_hits = (bp_list, bp_list) 234 235 def delete_temporaries(self): 236 if self.breakpoint_hits: 237 for n in self.breakpoint_hits[1]: 238 self.clear_bpbynumber(n) 239 240 def pop_next(self): 241 self.expect_set_no += 1 242 try: 243 self.expect = self.expected_list.pop(0) 244 except IndexError: 245 raise BdbNotExpectedError( 246 'expect_set list exhausted, cannot pop item %d' % 247 self.expect_set_no) 248 self.set_tuple = self.set_list.pop(0) 249 250 def process_event(self, event, frame, *args): 251 # Call get_stack() to enable walking the stack with set_up() and 252 # set_down(). 253 tb = None 254 if event == 'exception': 255 tb = self.exc_info[2] 256 self.get_stack(frame, tb) 257 258 # A breakpoint has been hit and it is not a temporary. 259 if self.currentbp is not None and not self.breakpoint_hits: 260 bp_list = [self.currentbp] 261 self.breakpoint_hits = (bp_list, []) 262 263 # Pop next event. 264 self.event= event 265 self.pop_next() 266 if self.dry_run: 267 self.print_state(self.header) 268 return 269 270 # Validate the expected results. 271 if self.expect: 272 self.check_equal(self.expect[0], event, 'Wrong event type') 273 self.check_lno_name() 274 275 if event in ('call', 'return'): 276 self.check_expect_max_size(3) 277 elif len(self.expect) > 3: 278 if event == 'line': 279 bps, temporaries = self.expect[3] 280 bpnums = sorted(bps.keys()) 281 if not self.breakpoint_hits: 282 self.raise_not_expected( 283 'No breakpoints hit at expect_set item %d' % 284 self.expect_set_no) 285 self.check_equal(bpnums, self.breakpoint_hits[0], 286 'Breakpoint numbers do not match') 287 self.check_equal([bps[n] for n in bpnums], 288 [self.get_bpbynumber(n).hits for 289 n in self.breakpoint_hits[0]], 290 'Wrong breakpoint hit count') 291 self.check_equal(sorted(temporaries), self.breakpoint_hits[1], 292 'Wrong temporary breakpoints') 293 294 elif event == 'exception': 295 if not isinstance(self.exc_info[1], self.expect[3]): 296 self.raise_not_expected( 297 "Wrong exception at expect_set item %d, got '%s'" % 298 (self.expect_set_no, self.exc_info)) 299 300 def check_equal(self, expected, result, msg): 301 if expected == result: 302 return 303 self.raise_not_expected("%s at expect_set item %d, got '%s'" % 304 (msg, self.expect_set_no, result)) 305 306 def check_lno_name(self): 307 """Check the line number and function co_name.""" 308 s = len(self.expect) 309 if s > 1: 310 lineno = self.lno_abs2rel() 311 self.check_equal(self.expect[1], lineno, 'Wrong line number') 312 if s > 2: 313 self.check_equal(self.expect[2], self.frame.f_code.co_name, 314 'Wrong function name') 315 316 def check_expect_max_size(self, size): 317 if len(self.expect) > size: 318 raise BdbSyntaxError('Invalid size of the %s expect tuple: %s' % 319 (self.event, self.expect)) 320 321 def lno_abs2rel(self): 322 fname = self.canonic(self.frame.f_code.co_filename) 323 lineno = self.frame.f_lineno 324 return ((lineno - self.frame.f_code.co_firstlineno + 1) 325 if fname == self.canonic(__file__) else lineno) 326 327 def lno_rel2abs(self, fname, lineno): 328 return (self.frame.f_code.co_firstlineno + lineno - 1 329 if (lineno and self.canonic(fname) == self.canonic(__file__)) 330 else lineno) 331 332 def get_state(self): 333 lineno = self.lno_abs2rel() 334 co_name = self.frame.f_code.co_name 335 state = "('%s', %d, '%s'" % (self.event, lineno, co_name) 336 if self.breakpoint_hits: 337 bps = '{' 338 for n in self.breakpoint_hits[0]: 339 if bps != '{': 340 bps += ', ' 341 bps += '%s: %s' % (n, self.get_bpbynumber(n).hits) 342 bps += '}' 343 bps = '(' + bps + ', ' + str(self.breakpoint_hits[1]) + ')' 344 state += ', ' + bps 345 elif self.event == 'exception': 346 state += ', ' + self.exc_info[0].__name__ 347 state += '), ' 348 return state.ljust(32) + str(self.set_tuple) + ',' 349 350 def print_state(self, header=None): 351 if header is not None and self.expect_set_no == 1: 352 print() 353 print(header) 354 print('%d: %s' % (self.expect_set_no, self.get_state())) 355 356 def raise_not_expected(self, msg): 357 msg += '\n' 358 msg += ' Expected: %s\n' % str(self.expect) 359 msg += ' Got: ' + self.get_state() 360 raise BdbNotExpectedError(msg) 361 362 def next_set_method(self): 363 set_type = self.set_tuple[0] 364 args = self.set_tuple[1] if len(self.set_tuple) == 2 else None 365 set_method = getattr(self, 'set_' + set_type) 366 367 # The following set methods give back control to the tracer. 368 if set_type in ('step', 'continue', 'quit'): 369 set_method() 370 return 371 elif set_type in ('next', 'return'): 372 set_method(self.frame) 373 return 374 elif set_type == 'until': 375 lineno = None 376 if args: 377 lineno = self.lno_rel2abs(self.frame.f_code.co_filename, 378 args[0]) 379 set_method(self.frame, lineno) 380 return 381 382 # The following set methods do not give back control to the tracer and 383 # next_set_method() is called recursively. 384 if (args and set_type in ('break', 'clear', 'ignore', 'enable', 385 'disable')) or set_type in ('up', 'down'): 386 if set_type in ('break', 'clear'): 387 fname, lineno, *remain = args 388 lineno = self.lno_rel2abs(fname, lineno) 389 args = [fname, lineno] 390 args.extend(remain) 391 set_method(*args) 392 elif set_type in ('ignore', 'enable', 'disable'): 393 set_method(*args) 394 elif set_type in ('up', 'down'): 395 set_method() 396 397 # Process the next expect_set item. 398 # It is not expected that a test may reach the recursion limit. 399 self.event= None 400 self.pop_next() 401 if self.dry_run: 402 self.print_state() 403 else: 404 if self.expect: 405 self.check_lno_name() 406 self.check_expect_max_size(3) 407 self.next_set_method() 408 else: 409 raise BdbSyntaxError('"%s" is an invalid set_tuple' % 410 self.set_tuple) 411 412class TracerRun(): 413 """Provide a context for running a Tracer instance with a test case.""" 414 415 def __init__(self, test_case, skip=None): 416 self.test_case = test_case 417 self.dry_run = test_case.dry_run 418 self.tracer = Tracer(test_case.expect_set, skip=skip, 419 dry_run=self.dry_run, test_case=test_case.id()) 420 self._original_tracer = None 421 422 def __enter__(self): 423 # test_pdb does not reset Breakpoint class attributes on exit :-( 424 reset_Breakpoint() 425 self._original_tracer = sys.gettrace() 426 return self.tracer 427 428 def __exit__(self, type_=None, value=None, traceback=None): 429 reset_Breakpoint() 430 sys.settrace(self._original_tracer) 431 432 not_empty = '' 433 if self.tracer.set_list: 434 not_empty += 'All paired tuples have not been processed, ' 435 not_empty += ('the last one was number %d' % 436 self.tracer.expect_set_no) 437 438 # Make a BdbNotExpectedError a unittest failure. 439 if type_ is not None and issubclass(BdbNotExpectedError, type_): 440 if isinstance(value, BaseException) and value.args: 441 err_msg = value.args[0] 442 if not_empty: 443 err_msg += '\n' + not_empty 444 if self.dry_run: 445 print(err_msg) 446 return True 447 else: 448 self.test_case.fail(err_msg) 449 else: 450 assert False, 'BdbNotExpectedError with empty args' 451 452 if not_empty: 453 if self.dry_run: 454 print(not_empty) 455 else: 456 self.test_case.fail(not_empty) 457 458def run_test(modules, set_list, skip=None): 459 """Run a test and print the dry-run results. 460 461 'modules': A dictionary mapping module names to their source code as a 462 string. The dictionary MUST include one module named 463 'test_module' with a main() function. 464 'set_list': A list of set_type tuples to be run on the module. 465 466 For example, running the following script outputs the following results: 467 468 ***************************** SCRIPT ******************************** 469 470 from test.test_bdb import run_test, break_in_func 471 472 code = ''' 473 def func(): 474 lno = 3 475 476 def main(): 477 func() 478 lno = 7 479 ''' 480 481 set_list = [ 482 break_in_func('func', 'test_module.py'), 483 ('continue', ), 484 ('step', ), 485 ('step', ), 486 ('step', ), 487 ('quit', ), 488 ] 489 490 modules = { 'test_module': code } 491 run_test(modules, set_list) 492 493 **************************** results ******************************** 494 495 1: ('line', 2, 'tfunc_import'), ('next',), 496 2: ('line', 3, 'tfunc_import'), ('step',), 497 3: ('call', 5, 'main'), ('break', ('test_module.py', None, False, None, 'func')), 498 4: ('None', 5, 'main'), ('continue',), 499 5: ('line', 3, 'func', ({1: 1}, [])), ('step',), 500 BpNum Temp Enb Hits Ignore Where 501 1 no yes 1 0 at test_module.py:2 502 6: ('return', 3, 'func'), ('step',), 503 7: ('line', 7, 'main'), ('step',), 504 8: ('return', 7, 'main'), ('quit',), 505 506 ************************************************************************* 507 508 """ 509 def gen(a, b): 510 try: 511 while 1: 512 x = next(a) 513 y = next(b) 514 yield x 515 yield y 516 except StopIteration: 517 return 518 519 # Step over the import statement in tfunc_import using 'next' and step 520 # into main() in test_module. 521 sl = [('next', ), ('step', )] 522 sl.extend(set_list) 523 524 test = BaseTestCase() 525 test.dry_run = True 526 test.id = lambda : None 527 test.expect_set = list(gen(repeat(()), iter(sl))) 528 with create_modules(modules): 529 with TracerRun(test, skip=skip) as tracer: 530 tracer.runcall(tfunc_import) 531 532@contextmanager 533def create_modules(modules): 534 with os_helper.temp_cwd(): 535 sys.path.append(os.getcwd()) 536 try: 537 for m in modules: 538 fname = m + '.py' 539 with open(fname, 'w', encoding="utf-8") as f: 540 f.write(textwrap.dedent(modules[m])) 541 linecache.checkcache(fname) 542 importlib.invalidate_caches() 543 yield 544 finally: 545 for m in modules: 546 import_helper.forget(m) 547 sys.path.pop() 548 549def break_in_func(funcname, fname=__file__, temporary=False, cond=None): 550 return 'break', (fname, None, temporary, cond, funcname) 551 552TEST_MODULE = 'test_module_for_bdb' 553TEST_MODULE_FNAME = TEST_MODULE + '.py' 554def tfunc_import(): 555 import test_module_for_bdb 556 test_module_for_bdb.main() 557 558def tfunc_main(): 559 lno = 2 560 tfunc_first() 561 tfunc_second() 562 lno = 5 563 lno = 6 564 lno = 7 565 566def tfunc_first(): 567 lno = 2 568 lno = 3 569 lno = 4 570 571def tfunc_second(): 572 lno = 2 573 574class BaseTestCase(unittest.TestCase): 575 """Base class for all tests.""" 576 577 dry_run = dry_run 578 579 def fail(self, msg=None): 580 # Override fail() to use 'raise from None' to avoid repetition of the 581 # error message and traceback. 582 raise self.failureException(msg) from None 583 584class StateTestCase(BaseTestCase): 585 """Test the step, next, return, until and quit 'set_' methods.""" 586 587 def test_step(self): 588 self.expect_set = [ 589 ('line', 2, 'tfunc_main'), ('step', ), 590 ('line', 3, 'tfunc_main'), ('step', ), 591 ('call', 1, 'tfunc_first'), ('step', ), 592 ('line', 2, 'tfunc_first'), ('quit', ), 593 ] 594 with TracerRun(self) as tracer: 595 tracer.runcall(tfunc_main) 596 597 def test_step_next_on_last_statement(self): 598 for set_type in ('step', 'next'): 599 with self.subTest(set_type=set_type): 600 self.expect_set = [ 601 ('line', 2, 'tfunc_main'), ('step', ), 602 ('line', 3, 'tfunc_main'), ('step', ), 603 ('call', 1, 'tfunc_first'), ('break', (__file__, 3)), 604 ('None', 1, 'tfunc_first'), ('continue', ), 605 ('line', 3, 'tfunc_first', ({1:1}, [])), (set_type, ), 606 ('line', 4, 'tfunc_first'), ('quit', ), 607 ] 608 with TracerRun(self) as tracer: 609 tracer.runcall(tfunc_main) 610 611 def test_next(self): 612 self.expect_set = [ 613 ('line', 2, 'tfunc_main'), ('step', ), 614 ('line', 3, 'tfunc_main'), ('next', ), 615 ('line', 4, 'tfunc_main'), ('step', ), 616 ('call', 1, 'tfunc_second'), ('step', ), 617 ('line', 2, 'tfunc_second'), ('quit', ), 618 ] 619 with TracerRun(self) as tracer: 620 tracer.runcall(tfunc_main) 621 622 def test_next_over_import(self): 623 code = """ 624 def main(): 625 lno = 3 626 """ 627 modules = { TEST_MODULE: code } 628 with create_modules(modules): 629 self.expect_set = [ 630 ('line', 2, 'tfunc_import'), ('next', ), 631 ('line', 3, 'tfunc_import'), ('quit', ), 632 ] 633 with TracerRun(self) as tracer: 634 tracer.runcall(tfunc_import) 635 636 def test_next_on_plain_statement(self): 637 # Check that set_next() is equivalent to set_step() on a plain 638 # statement. 639 self.expect_set = [ 640 ('line', 2, 'tfunc_main'), ('step', ), 641 ('line', 3, 'tfunc_main'), ('step', ), 642 ('call', 1, 'tfunc_first'), ('next', ), 643 ('line', 2, 'tfunc_first'), ('quit', ), 644 ] 645 with TracerRun(self) as tracer: 646 tracer.runcall(tfunc_main) 647 648 def test_next_in_caller_frame(self): 649 # Check that set_next() in the caller frame causes the tracer 650 # to stop next in the caller frame. 651 self.expect_set = [ 652 ('line', 2, 'tfunc_main'), ('step', ), 653 ('line', 3, 'tfunc_main'), ('step', ), 654 ('call', 1, 'tfunc_first'), ('up', ), 655 ('None', 3, 'tfunc_main'), ('next', ), 656 ('line', 4, 'tfunc_main'), ('quit', ), 657 ] 658 with TracerRun(self) as tracer: 659 tracer.runcall(tfunc_main) 660 661 def test_return(self): 662 self.expect_set = [ 663 ('line', 2, 'tfunc_main'), ('step', ), 664 ('line', 3, 'tfunc_main'), ('step', ), 665 ('call', 1, 'tfunc_first'), ('step', ), 666 ('line', 2, 'tfunc_first'), ('return', ), 667 ('return', 4, 'tfunc_first'), ('step', ), 668 ('line', 4, 'tfunc_main'), ('quit', ), 669 ] 670 with TracerRun(self) as tracer: 671 tracer.runcall(tfunc_main) 672 673 def test_return_in_caller_frame(self): 674 self.expect_set = [ 675 ('line', 2, 'tfunc_main'), ('step', ), 676 ('line', 3, 'tfunc_main'), ('step', ), 677 ('call', 1, 'tfunc_first'), ('up', ), 678 ('None', 3, 'tfunc_main'), ('return', ), 679 ('return', 7, 'tfunc_main'), ('quit', ), 680 ] 681 with TracerRun(self) as tracer: 682 tracer.runcall(tfunc_main) 683 684 def test_until(self): 685 self.expect_set = [ 686 ('line', 2, 'tfunc_main'), ('step', ), 687 ('line', 3, 'tfunc_main'), ('step', ), 688 ('call', 1, 'tfunc_first'), ('step', ), 689 ('line', 2, 'tfunc_first'), ('until', (4, )), 690 ('line', 4, 'tfunc_first'), ('quit', ), 691 ] 692 with TracerRun(self) as tracer: 693 tracer.runcall(tfunc_main) 694 695 def test_until_with_too_large_count(self): 696 self.expect_set = [ 697 ('line', 2, 'tfunc_main'), break_in_func('tfunc_first'), 698 ('None', 2, 'tfunc_main'), ('continue', ), 699 ('line', 2, 'tfunc_first', ({1:1}, [])), ('until', (9999, )), 700 ('return', 4, 'tfunc_first'), ('quit', ), 701 ] 702 with TracerRun(self) as tracer: 703 tracer.runcall(tfunc_main) 704 705 def test_until_in_caller_frame(self): 706 self.expect_set = [ 707 ('line', 2, 'tfunc_main'), ('step', ), 708 ('line', 3, 'tfunc_main'), ('step', ), 709 ('call', 1, 'tfunc_first'), ('up', ), 710 ('None', 3, 'tfunc_main'), ('until', (6, )), 711 ('line', 6, 'tfunc_main'), ('quit', ), 712 ] 713 with TracerRun(self) as tracer: 714 tracer.runcall(tfunc_main) 715 716 def test_skip(self): 717 # Check that tracing is skipped over the import statement in 718 # 'tfunc_import()'. 719 code = """ 720 def main(): 721 lno = 3 722 """ 723 modules = { TEST_MODULE: code } 724 with create_modules(modules): 725 self.expect_set = [ 726 ('line', 2, 'tfunc_import'), ('step', ), 727 ('line', 3, 'tfunc_import'), ('quit', ), 728 ] 729 skip = ('importlib*', 'zipimport', 'encodings.*', TEST_MODULE) 730 with TracerRun(self, skip=skip) as tracer: 731 tracer.runcall(tfunc_import) 732 733 def test_skip_with_no_name_module(self): 734 # some frames have `globals` with no `__name__` 735 # for instance the second frame in this traceback 736 # exec(compile('raise ValueError()', '', 'exec'), {}) 737 bdb = Bdb(skip=['anything*']) 738 self.assertIs(bdb.is_skipped_module(None), False) 739 740 def test_down(self): 741 # Check that set_down() raises BdbError at the newest frame. 742 self.expect_set = [ 743 ('line', 2, 'tfunc_main'), ('down', ), 744 ] 745 with TracerRun(self) as tracer: 746 self.assertRaises(BdbError, tracer.runcall, tfunc_main) 747 748 def test_up(self): 749 self.expect_set = [ 750 ('line', 2, 'tfunc_main'), ('step', ), 751 ('line', 3, 'tfunc_main'), ('step', ), 752 ('call', 1, 'tfunc_first'), ('up', ), 753 ('None', 3, 'tfunc_main'), ('quit', ), 754 ] 755 with TracerRun(self) as tracer: 756 tracer.runcall(tfunc_main) 757 758class BreakpointTestCase(BaseTestCase): 759 """Test the breakpoint set method.""" 760 761 def test_bp_on_non_existent_module(self): 762 self.expect_set = [ 763 ('line', 2, 'tfunc_import'), ('break', ('/non/existent/module.py', 1)) 764 ] 765 with TracerRun(self) as tracer: 766 self.assertRaises(BdbError, tracer.runcall, tfunc_import) 767 768 def test_bp_after_last_statement(self): 769 code = """ 770 def main(): 771 lno = 3 772 """ 773 modules = { TEST_MODULE: code } 774 with create_modules(modules): 775 self.expect_set = [ 776 ('line', 2, 'tfunc_import'), ('break', (TEST_MODULE_FNAME, 4)) 777 ] 778 with TracerRun(self) as tracer: 779 self.assertRaises(BdbError, tracer.runcall, tfunc_import) 780 781 def test_temporary_bp(self): 782 code = """ 783 def func(): 784 lno = 3 785 786 def main(): 787 for i in range(2): 788 func() 789 """ 790 modules = { TEST_MODULE: code } 791 with create_modules(modules): 792 self.expect_set = [ 793 ('line', 2, 'tfunc_import'), 794 break_in_func('func', TEST_MODULE_FNAME, True), 795 ('None', 2, 'tfunc_import'), 796 break_in_func('func', TEST_MODULE_FNAME, True), 797 ('None', 2, 'tfunc_import'), ('continue', ), 798 ('line', 3, 'func', ({1:1}, [1])), ('continue', ), 799 ('line', 3, 'func', ({2:1}, [2])), ('quit', ), 800 ] 801 with TracerRun(self) as tracer: 802 tracer.runcall(tfunc_import) 803 804 def test_disabled_temporary_bp(self): 805 code = """ 806 def func(): 807 lno = 3 808 809 def main(): 810 for i in range(3): 811 func() 812 """ 813 modules = { TEST_MODULE: code } 814 with create_modules(modules): 815 self.expect_set = [ 816 ('line', 2, 'tfunc_import'), 817 break_in_func('func', TEST_MODULE_FNAME), 818 ('None', 2, 'tfunc_import'), 819 break_in_func('func', TEST_MODULE_FNAME, True), 820 ('None', 2, 'tfunc_import'), ('disable', (2, )), 821 ('None', 2, 'tfunc_import'), ('continue', ), 822 ('line', 3, 'func', ({1:1}, [])), ('enable', (2, )), 823 ('None', 3, 'func'), ('disable', (1, )), 824 ('None', 3, 'func'), ('continue', ), 825 ('line', 3, 'func', ({2:1}, [2])), ('enable', (1, )), 826 ('None', 3, 'func'), ('continue', ), 827 ('line', 3, 'func', ({1:2}, [])), ('quit', ), 828 ] 829 with TracerRun(self) as tracer: 830 tracer.runcall(tfunc_import) 831 832 def test_bp_condition(self): 833 code = """ 834 def func(a): 835 lno = 3 836 837 def main(): 838 for i in range(3): 839 func(i) 840 """ 841 modules = { TEST_MODULE: code } 842 with create_modules(modules): 843 self.expect_set = [ 844 ('line', 2, 'tfunc_import'), 845 break_in_func('func', TEST_MODULE_FNAME, False, 'a == 2'), 846 ('None', 2, 'tfunc_import'), ('continue', ), 847 ('line', 3, 'func', ({1:3}, [])), ('quit', ), 848 ] 849 with TracerRun(self) as tracer: 850 tracer.runcall(tfunc_import) 851 852 def test_bp_exception_on_condition_evaluation(self): 853 code = """ 854 def func(a): 855 lno = 3 856 857 def main(): 858 func(0) 859 """ 860 modules = { TEST_MODULE: code } 861 with create_modules(modules): 862 self.expect_set = [ 863 ('line', 2, 'tfunc_import'), 864 break_in_func('func', TEST_MODULE_FNAME, False, '1 / 0'), 865 ('None', 2, 'tfunc_import'), ('continue', ), 866 ('line', 3, 'func', ({1:1}, [])), ('quit', ), 867 ] 868 with TracerRun(self) as tracer: 869 tracer.runcall(tfunc_import) 870 871 def test_bp_ignore_count(self): 872 code = """ 873 def func(): 874 lno = 3 875 876 def main(): 877 for i in range(2): 878 func() 879 """ 880 modules = { TEST_MODULE: code } 881 with create_modules(modules): 882 self.expect_set = [ 883 ('line', 2, 'tfunc_import'), 884 break_in_func('func', TEST_MODULE_FNAME), 885 ('None', 2, 'tfunc_import'), ('ignore', (1, )), 886 ('None', 2, 'tfunc_import'), ('continue', ), 887 ('line', 3, 'func', ({1:2}, [])), ('quit', ), 888 ] 889 with TracerRun(self) as tracer: 890 tracer.runcall(tfunc_import) 891 892 def test_ignore_count_on_disabled_bp(self): 893 code = """ 894 def func(): 895 lno = 3 896 897 def main(): 898 for i in range(3): 899 func() 900 """ 901 modules = { TEST_MODULE: code } 902 with create_modules(modules): 903 self.expect_set = [ 904 ('line', 2, 'tfunc_import'), 905 break_in_func('func', TEST_MODULE_FNAME), 906 ('None', 2, 'tfunc_import'), 907 break_in_func('func', TEST_MODULE_FNAME), 908 ('None', 2, 'tfunc_import'), ('ignore', (1, )), 909 ('None', 2, 'tfunc_import'), ('disable', (1, )), 910 ('None', 2, 'tfunc_import'), ('continue', ), 911 ('line', 3, 'func', ({2:1}, [])), ('enable', (1, )), 912 ('None', 3, 'func'), ('continue', ), 913 ('line', 3, 'func', ({2:2}, [])), ('continue', ), 914 ('line', 3, 'func', ({1:2}, [])), ('quit', ), 915 ] 916 with TracerRun(self) as tracer: 917 tracer.runcall(tfunc_import) 918 919 def test_clear_two_bp_on_same_line(self): 920 code = """ 921 def func(): 922 lno = 3 923 lno = 4 924 925 def main(): 926 for i in range(3): 927 func() 928 """ 929 modules = { TEST_MODULE: code } 930 with create_modules(modules): 931 self.expect_set = [ 932 ('line', 2, 'tfunc_import'), ('break', (TEST_MODULE_FNAME, 3)), 933 ('None', 2, 'tfunc_import'), ('break', (TEST_MODULE_FNAME, 3)), 934 ('None', 2, 'tfunc_import'), ('break', (TEST_MODULE_FNAME, 4)), 935 ('None', 2, 'tfunc_import'), ('continue', ), 936 ('line', 3, 'func', ({1:1}, [])), ('continue', ), 937 ('line', 4, 'func', ({3:1}, [])), ('clear', (TEST_MODULE_FNAME, 3)), 938 ('None', 4, 'func'), ('continue', ), 939 ('line', 4, 'func', ({3:2}, [])), ('quit', ), 940 ] 941 with TracerRun(self) as tracer: 942 tracer.runcall(tfunc_import) 943 944 def test_clear_at_no_bp(self): 945 self.expect_set = [ 946 ('line', 2, 'tfunc_import'), ('clear', (__file__, 1)) 947 ] 948 with TracerRun(self) as tracer: 949 self.assertRaises(BdbError, tracer.runcall, tfunc_import) 950 951 def test_load_bps_from_previous_Bdb_instance(self): 952 reset_Breakpoint() 953 db1 = Bdb() 954 fname = db1.canonic(__file__) 955 db1.set_break(__file__, 1) 956 self.assertEqual(db1.get_all_breaks(), {fname: [1]}) 957 958 db2 = Bdb() 959 db2.set_break(__file__, 2) 960 db2.set_break(__file__, 3) 961 db2.set_break(__file__, 4) 962 self.assertEqual(db1.get_all_breaks(), {fname: [1]}) 963 self.assertEqual(db2.get_all_breaks(), {fname: [1, 2, 3, 4]}) 964 db2.clear_break(__file__, 1) 965 self.assertEqual(db1.get_all_breaks(), {fname: [1]}) 966 self.assertEqual(db2.get_all_breaks(), {fname: [2, 3, 4]}) 967 968 db3 = Bdb() 969 self.assertEqual(db1.get_all_breaks(), {fname: [1]}) 970 self.assertEqual(db2.get_all_breaks(), {fname: [2, 3, 4]}) 971 self.assertEqual(db3.get_all_breaks(), {fname: [2, 3, 4]}) 972 db2.clear_break(__file__, 2) 973 self.assertEqual(db1.get_all_breaks(), {fname: [1]}) 974 self.assertEqual(db2.get_all_breaks(), {fname: [3, 4]}) 975 self.assertEqual(db3.get_all_breaks(), {fname: [2, 3, 4]}) 976 977 db4 = Bdb() 978 db4.set_break(__file__, 5) 979 self.assertEqual(db1.get_all_breaks(), {fname: [1]}) 980 self.assertEqual(db2.get_all_breaks(), {fname: [3, 4]}) 981 self.assertEqual(db3.get_all_breaks(), {fname: [2, 3, 4]}) 982 self.assertEqual(db4.get_all_breaks(), {fname: [3, 4, 5]}) 983 reset_Breakpoint() 984 985 db5 = Bdb() 986 db5.set_break(__file__, 6) 987 self.assertEqual(db1.get_all_breaks(), {fname: [1]}) 988 self.assertEqual(db2.get_all_breaks(), {fname: [3, 4]}) 989 self.assertEqual(db3.get_all_breaks(), {fname: [2, 3, 4]}) 990 self.assertEqual(db4.get_all_breaks(), {fname: [3, 4, 5]}) 991 self.assertEqual(db5.get_all_breaks(), {fname: [6]}) 992 993 994class RunTestCase(BaseTestCase): 995 """Test run, runeval and set_trace.""" 996 997 def test_run_step(self): 998 # Check that the bdb 'run' method stops at the first line event. 999 code = """ 1000 lno = 2 1001 """ 1002 self.expect_set = [ 1003 ('line', 2, '<module>'), ('step', ), 1004 ('return', 2, '<module>'), ('quit', ), 1005 ] 1006 with TracerRun(self) as tracer: 1007 tracer.run(compile(textwrap.dedent(code), '<string>', 'exec')) 1008 1009 def test_runeval_step(self): 1010 # Test bdb 'runeval'. 1011 code = """ 1012 def main(): 1013 lno = 3 1014 """ 1015 modules = { TEST_MODULE: code } 1016 with create_modules(modules): 1017 self.expect_set = [ 1018 ('line', 1, '<module>'), ('step', ), 1019 ('call', 2, 'main'), ('step', ), 1020 ('line', 3, 'main'), ('step', ), 1021 ('return', 3, 'main'), ('step', ), 1022 ('return', 1, '<module>'), ('quit', ), 1023 ] 1024 import test_module_for_bdb 1025 with TracerRun(self) as tracer: 1026 tracer.runeval('test_module_for_bdb.main()', globals(), locals()) 1027 1028class IssuesTestCase(BaseTestCase): 1029 """Test fixed bdb issues.""" 1030 1031 def test_step_at_return_with_no_trace_in_caller(self): 1032 # Issue #13183. 1033 # Check that the tracer does step into the caller frame when the 1034 # trace function is not set in that frame. 1035 code_1 = """ 1036 from test_module_for_bdb_2 import func 1037 def main(): 1038 func() 1039 lno = 5 1040 """ 1041 code_2 = """ 1042 def func(): 1043 lno = 3 1044 """ 1045 modules = { 1046 TEST_MODULE: code_1, 1047 'test_module_for_bdb_2': code_2, 1048 } 1049 with create_modules(modules): 1050 self.expect_set = [ 1051 ('line', 2, 'tfunc_import'), 1052 break_in_func('func', 'test_module_for_bdb_2.py'), 1053 ('None', 2, 'tfunc_import'), ('continue', ), 1054 ('line', 3, 'func', ({1:1}, [])), ('step', ), 1055 ('return', 3, 'func'), ('step', ), 1056 ('line', 5, 'main'), ('quit', ), 1057 ] 1058 with TracerRun(self) as tracer: 1059 tracer.runcall(tfunc_import) 1060 1061 def test_next_until_return_in_generator(self): 1062 # Issue #16596. 1063 # Check that set_next(), set_until() and set_return() do not treat the 1064 # `yield` and `yield from` statements as if they were returns and stop 1065 # instead in the current frame. 1066 code = """ 1067 def test_gen(): 1068 yield 0 1069 lno = 4 1070 return 123 1071 1072 def main(): 1073 it = test_gen() 1074 next(it) 1075 next(it) 1076 lno = 11 1077 """ 1078 modules = { TEST_MODULE: code } 1079 for set_type in ('next', 'until', 'return'): 1080 with self.subTest(set_type=set_type): 1081 with create_modules(modules): 1082 self.expect_set = [ 1083 ('line', 2, 'tfunc_import'), 1084 break_in_func('test_gen', TEST_MODULE_FNAME), 1085 ('None', 2, 'tfunc_import'), ('continue', ), 1086 ('line', 3, 'test_gen', ({1:1}, [])), (set_type, ), 1087 ] 1088 1089 if set_type == 'return': 1090 self.expect_set.extend( 1091 [('exception', 10, 'main', StopIteration), ('step',), 1092 ('return', 10, 'main'), ('quit', ), 1093 ] 1094 ) 1095 else: 1096 self.expect_set.extend( 1097 [('line', 4, 'test_gen'), ('quit', ),] 1098 ) 1099 with TracerRun(self) as tracer: 1100 tracer.runcall(tfunc_import) 1101 1102 def test_next_command_in_generator_for_loop(self): 1103 # Issue #16596. 1104 code = """ 1105 def test_gen(): 1106 yield 0 1107 lno = 4 1108 yield 1 1109 return 123 1110 1111 def main(): 1112 for i in test_gen(): 1113 lno = 10 1114 lno = 11 1115 """ 1116 modules = { TEST_MODULE: code } 1117 with create_modules(modules): 1118 self.expect_set = [ 1119 ('line', 2, 'tfunc_import'), 1120 break_in_func('test_gen', TEST_MODULE_FNAME), 1121 ('None', 2, 'tfunc_import'), ('continue', ), 1122 ('line', 3, 'test_gen', ({1:1}, [])), ('next', ), 1123 ('line', 4, 'test_gen'), ('next', ), 1124 ('line', 5, 'test_gen'), ('next', ), 1125 ('line', 6, 'test_gen'), ('next', ), 1126 ('exception', 9, 'main', StopIteration), ('step', ), 1127 ('line', 11, 'main'), ('quit', ), 1128 1129 ] 1130 with TracerRun(self) as tracer: 1131 tracer.runcall(tfunc_import) 1132 1133 def test_next_command_in_generator_with_subiterator(self): 1134 # Issue #16596. 1135 code = """ 1136 def test_subgen(): 1137 yield 0 1138 return 123 1139 1140 def test_gen(): 1141 x = yield from test_subgen() 1142 return 456 1143 1144 def main(): 1145 for i in test_gen(): 1146 lno = 12 1147 lno = 13 1148 """ 1149 modules = { TEST_MODULE: code } 1150 with create_modules(modules): 1151 self.expect_set = [ 1152 ('line', 2, 'tfunc_import'), 1153 break_in_func('test_gen', TEST_MODULE_FNAME), 1154 ('None', 2, 'tfunc_import'), ('continue', ), 1155 ('line', 7, 'test_gen', ({1:1}, [])), ('next', ), 1156 ('line', 8, 'test_gen'), ('next', ), 1157 ('exception', 11, 'main', StopIteration), ('step', ), 1158 ('line', 13, 'main'), ('quit', ), 1159 1160 ] 1161 with TracerRun(self) as tracer: 1162 tracer.runcall(tfunc_import) 1163 1164 def test_return_command_in_generator_with_subiterator(self): 1165 # Issue #16596. 1166 code = """ 1167 def test_subgen(): 1168 yield 0 1169 return 123 1170 1171 def test_gen(): 1172 x = yield from test_subgen() 1173 return 456 1174 1175 def main(): 1176 for i in test_gen(): 1177 lno = 12 1178 lno = 13 1179 """ 1180 modules = { TEST_MODULE: code } 1181 with create_modules(modules): 1182 self.expect_set = [ 1183 ('line', 2, 'tfunc_import'), 1184 break_in_func('test_subgen', TEST_MODULE_FNAME), 1185 ('None', 2, 'tfunc_import'), ('continue', ), 1186 ('line', 3, 'test_subgen', ({1:1}, [])), ('return', ), 1187 ('exception', 7, 'test_gen', StopIteration), ('return', ), 1188 ('exception', 11, 'main', StopIteration), ('step', ), 1189 ('line', 13, 'main'), ('quit', ), 1190 1191 ] 1192 with TracerRun(self) as tracer: 1193 tracer.runcall(tfunc_import) 1194 1195 1196if __name__ == "__main__": 1197 unittest.main() 1198