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