xdegaye | 3fe3304 | 2018-03-18 21:02:47 +0100 | [diff] [blame] | 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 | |
| 51 | import bdb as _bdb |
| 52 | import sys |
| 53 | import os |
| 54 | import unittest |
| 55 | import textwrap |
| 56 | import importlib |
| 57 | import linecache |
| 58 | from contextlib import contextmanager |
| 59 | from itertools import islice, repeat |
| 60 | import test.support |
| 61 | |
| 62 | class BdbException(Exception): pass |
| 63 | class BdbError(BdbException): """Error raised by the Bdb instance.""" |
| 64 | class BdbSyntaxError(BdbException): """Syntax error in the test case.""" |
| 65 | class BdbNotExpectedError(BdbException): """Unexpected result.""" |
| 66 | |
| 67 | # When 'dry_run' is set to true, expect tuples are ignored and the actual |
| 68 | # state of the tracer is printed after running each set_*() method of the test |
| 69 | # case. The full list of breakpoints and their attributes is also printed |
| 70 | # after each 'line' event where a breakpoint has been hit. |
| 71 | dry_run = 0 |
| 72 | |
| 73 | def reset_Breakpoint(): |
| 74 | _bdb.Breakpoint.next = 1 |
| 75 | _bdb.Breakpoint.bplist = {} |
| 76 | _bdb.Breakpoint.bpbynumber = [None] |
| 77 | |
| 78 | def 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 | |
| 99 | class 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 | |
| 163 | class 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 | |
| 412 | class 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()) |
Steve Dower | e5f41d2 | 2018-05-16 17:50:29 -0400 | [diff] [blame] | 420 | self._original_tracer = None |
xdegaye | 3fe3304 | 2018-03-18 21:02:47 +0100 | [diff] [blame] | 421 | |
| 422 | def __enter__(self): |
| 423 | # test_pdb does not reset Breakpoint class attributes on exit :-( |
| 424 | reset_Breakpoint() |
Steve Dower | e5f41d2 | 2018-05-16 17:50:29 -0400 | [diff] [blame] | 425 | self._original_tracer = sys.gettrace() |
xdegaye | 3fe3304 | 2018-03-18 21:02:47 +0100 | [diff] [blame] | 426 | return self.tracer |
| 427 | |
| 428 | def __exit__(self, type_=None, value=None, traceback=None): |
| 429 | reset_Breakpoint() |
Steve Dower | e5f41d2 | 2018-05-16 17:50:29 -0400 | [diff] [blame] | 430 | sys.settrace(self._original_tracer) |
xdegaye | 3fe3304 | 2018-03-18 21:02:47 +0100 | [diff] [blame] | 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 | |
| 458 | def 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): |
xdegaye | 3fe3304 | 2018-03-18 21:02:47 +0100 | [diff] [blame] | 529 | with TracerRun(test, skip=skip) as tracer: |
| 530 | tracer.runcall(tfunc_import) |
| 531 | |
| 532 | @contextmanager |
| 533 | def create_modules(modules): |
| 534 | with test.support.temp_cwd(): |
Nick Coghlan | d5d9e02 | 2018-03-25 23:03:10 +1000 | [diff] [blame] | 535 | sys.path.append(os.getcwd()) |
xdegaye | 3fe3304 | 2018-03-18 21:02:47 +0100 | [diff] [blame] | 536 | try: |
| 537 | for m in modules: |
| 538 | fname = m + '.py' |
| 539 | with open(fname, 'w') 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 | test.support.forget(m) |
Nick Coghlan | d5d9e02 | 2018-03-25 23:03:10 +1000 | [diff] [blame] | 547 | sys.path.pop() |
xdegaye | 3fe3304 | 2018-03-18 21:02:47 +0100 | [diff] [blame] | 548 | |
| 549 | def break_in_func(funcname, fname=__file__, temporary=False, cond=None): |
| 550 | return 'break', (fname, None, temporary, cond, funcname) |
| 551 | |
Alex H | 54fd455 | 2018-12-05 20:32:16 +0100 | [diff] [blame] | 552 | TEST_MODULE = 'test_module_for_bdb' |
xdegaye | 3fe3304 | 2018-03-18 21:02:47 +0100 | [diff] [blame] | 553 | TEST_MODULE_FNAME = TEST_MODULE + '.py' |
| 554 | def tfunc_import(): |
Alex H | 54fd455 | 2018-12-05 20:32:16 +0100 | [diff] [blame] | 555 | import test_module_for_bdb |
| 556 | test_module_for_bdb.main() |
xdegaye | 3fe3304 | 2018-03-18 21:02:47 +0100 | [diff] [blame] | 557 | |
| 558 | def tfunc_main(): |
| 559 | lno = 2 |
| 560 | tfunc_first() |
| 561 | tfunc_second() |
| 562 | lno = 5 |
| 563 | lno = 6 |
| 564 | lno = 7 |
| 565 | |
| 566 | def tfunc_first(): |
| 567 | lno = 2 |
| 568 | lno = 3 |
| 569 | lno = 4 |
| 570 | |
| 571 | def tfunc_second(): |
| 572 | lno = 2 |
| 573 | |
| 574 | class 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 | |
| 584 | class 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 | ] |
Serhiy Storchaka | 79d1c2e | 2018-09-18 22:22:29 +0300 | [diff] [blame] | 729 | skip = ('importlib*', 'zipimport', TEST_MODULE) |
xdegaye | 3fe3304 | 2018-03-18 21:02:47 +0100 | [diff] [blame] | 730 | with TracerRun(self, skip=skip) as tracer: |
| 731 | tracer.runcall(tfunc_import) |
| 732 | |
Anthony Sottile | 86900a4 | 2019-03-12 20:57:09 -0700 | [diff] [blame] | 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 | |
xdegaye | 3fe3304 | 2018-03-18 21:02:47 +0100 | [diff] [blame] | 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 | |
| 758 | class 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 | class RunTestCase(BaseTestCase): |
| 952 | """Test run, runeval and set_trace.""" |
| 953 | |
| 954 | def test_run_step(self): |
| 955 | # Check that the bdb 'run' method stops at the first line event. |
| 956 | code = """ |
| 957 | lno = 2 |
| 958 | """ |
| 959 | self.expect_set = [ |
| 960 | ('line', 2, '<module>'), ('step', ), |
| 961 | ('return', 2, '<module>'), ('quit', ), |
| 962 | ] |
| 963 | with TracerRun(self) as tracer: |
| 964 | tracer.run(compile(textwrap.dedent(code), '<string>', 'exec')) |
| 965 | |
| 966 | def test_runeval_step(self): |
| 967 | # Test bdb 'runeval'. |
| 968 | code = """ |
| 969 | def main(): |
| 970 | lno = 3 |
| 971 | """ |
| 972 | modules = { TEST_MODULE: code } |
| 973 | with create_modules(modules): |
| 974 | self.expect_set = [ |
| 975 | ('line', 1, '<module>'), ('step', ), |
| 976 | ('call', 2, 'main'), ('step', ), |
| 977 | ('line', 3, 'main'), ('step', ), |
| 978 | ('return', 3, 'main'), ('step', ), |
| 979 | ('return', 1, '<module>'), ('quit', ), |
| 980 | ] |
Alex H | 54fd455 | 2018-12-05 20:32:16 +0100 | [diff] [blame] | 981 | import test_module_for_bdb |
xdegaye | 3fe3304 | 2018-03-18 21:02:47 +0100 | [diff] [blame] | 982 | with TracerRun(self) as tracer: |
Alex H | 54fd455 | 2018-12-05 20:32:16 +0100 | [diff] [blame] | 983 | tracer.runeval('test_module_for_bdb.main()', globals(), locals()) |
xdegaye | 3fe3304 | 2018-03-18 21:02:47 +0100 | [diff] [blame] | 984 | |
| 985 | class IssuesTestCase(BaseTestCase): |
| 986 | """Test fixed bdb issues.""" |
| 987 | |
| 988 | def test_step_at_return_with_no_trace_in_caller(self): |
| 989 | # Issue #13183. |
| 990 | # Check that the tracer does step into the caller frame when the |
| 991 | # trace function is not set in that frame. |
| 992 | code_1 = """ |
Alex H | 54fd455 | 2018-12-05 20:32:16 +0100 | [diff] [blame] | 993 | from test_module_for_bdb_2 import func |
xdegaye | 3fe3304 | 2018-03-18 21:02:47 +0100 | [diff] [blame] | 994 | def main(): |
| 995 | func() |
| 996 | lno = 5 |
| 997 | """ |
| 998 | code_2 = """ |
| 999 | def func(): |
| 1000 | lno = 3 |
| 1001 | """ |
| 1002 | modules = { |
| 1003 | TEST_MODULE: code_1, |
Alex H | 54fd455 | 2018-12-05 20:32:16 +0100 | [diff] [blame] | 1004 | 'test_module_for_bdb_2': code_2, |
xdegaye | 3fe3304 | 2018-03-18 21:02:47 +0100 | [diff] [blame] | 1005 | } |
| 1006 | with create_modules(modules): |
| 1007 | self.expect_set = [ |
| 1008 | ('line', 2, 'tfunc_import'), |
Alex H | 54fd455 | 2018-12-05 20:32:16 +0100 | [diff] [blame] | 1009 | break_in_func('func', 'test_module_for_bdb_2.py'), |
xdegaye | 3fe3304 | 2018-03-18 21:02:47 +0100 | [diff] [blame] | 1010 | ('None', 2, 'tfunc_import'), ('continue', ), |
| 1011 | ('line', 3, 'func', ({1:1}, [])), ('step', ), |
| 1012 | ('return', 3, 'func'), ('step', ), |
| 1013 | ('line', 5, 'main'), ('quit', ), |
| 1014 | ] |
| 1015 | with TracerRun(self) as tracer: |
| 1016 | tracer.runcall(tfunc_import) |
| 1017 | |
| 1018 | def test_next_until_return_in_generator(self): |
| 1019 | # Issue #16596. |
| 1020 | # Check that set_next(), set_until() and set_return() do not treat the |
| 1021 | # `yield` and `yield from` statements as if they were returns and stop |
| 1022 | # instead in the current frame. |
| 1023 | code = """ |
| 1024 | def test_gen(): |
| 1025 | yield 0 |
| 1026 | lno = 4 |
| 1027 | return 123 |
| 1028 | |
| 1029 | def main(): |
| 1030 | it = test_gen() |
| 1031 | next(it) |
| 1032 | next(it) |
| 1033 | lno = 11 |
| 1034 | """ |
| 1035 | modules = { TEST_MODULE: code } |
| 1036 | for set_type in ('next', 'until', 'return'): |
| 1037 | with self.subTest(set_type=set_type): |
| 1038 | with create_modules(modules): |
| 1039 | self.expect_set = [ |
| 1040 | ('line', 2, 'tfunc_import'), |
| 1041 | break_in_func('test_gen', TEST_MODULE_FNAME), |
| 1042 | ('None', 2, 'tfunc_import'), ('continue', ), |
| 1043 | ('line', 3, 'test_gen', ({1:1}, [])), (set_type, ), |
| 1044 | ] |
| 1045 | |
| 1046 | if set_type == 'return': |
| 1047 | self.expect_set.extend( |
| 1048 | [('exception', 10, 'main', StopIteration), ('step',), |
| 1049 | ('return', 10, 'main'), ('quit', ), |
| 1050 | ] |
| 1051 | ) |
| 1052 | else: |
| 1053 | self.expect_set.extend( |
| 1054 | [('line', 4, 'test_gen'), ('quit', ),] |
| 1055 | ) |
| 1056 | with TracerRun(self) as tracer: |
| 1057 | tracer.runcall(tfunc_import) |
| 1058 | |
| 1059 | def test_next_command_in_generator_for_loop(self): |
| 1060 | # Issue #16596. |
| 1061 | code = """ |
| 1062 | def test_gen(): |
| 1063 | yield 0 |
| 1064 | lno = 4 |
| 1065 | yield 1 |
| 1066 | return 123 |
| 1067 | |
| 1068 | def main(): |
| 1069 | for i in test_gen(): |
| 1070 | lno = 10 |
| 1071 | lno = 11 |
| 1072 | """ |
| 1073 | modules = { TEST_MODULE: code } |
| 1074 | with create_modules(modules): |
| 1075 | self.expect_set = [ |
| 1076 | ('line', 2, 'tfunc_import'), |
| 1077 | break_in_func('test_gen', TEST_MODULE_FNAME), |
| 1078 | ('None', 2, 'tfunc_import'), ('continue', ), |
| 1079 | ('line', 3, 'test_gen', ({1:1}, [])), ('next', ), |
| 1080 | ('line', 4, 'test_gen'), ('next', ), |
| 1081 | ('line', 5, 'test_gen'), ('next', ), |
| 1082 | ('line', 6, 'test_gen'), ('next', ), |
| 1083 | ('exception', 9, 'main', StopIteration), ('step', ), |
| 1084 | ('line', 11, 'main'), ('quit', ), |
| 1085 | |
| 1086 | ] |
| 1087 | with TracerRun(self) as tracer: |
| 1088 | tracer.runcall(tfunc_import) |
| 1089 | |
| 1090 | def test_next_command_in_generator_with_subiterator(self): |
| 1091 | # Issue #16596. |
| 1092 | code = """ |
| 1093 | def test_subgen(): |
| 1094 | yield 0 |
| 1095 | return 123 |
| 1096 | |
| 1097 | def test_gen(): |
| 1098 | x = yield from test_subgen() |
| 1099 | return 456 |
| 1100 | |
| 1101 | def main(): |
| 1102 | for i in test_gen(): |
| 1103 | lno = 12 |
| 1104 | lno = 13 |
| 1105 | """ |
| 1106 | modules = { TEST_MODULE: code } |
| 1107 | with create_modules(modules): |
| 1108 | self.expect_set = [ |
| 1109 | ('line', 2, 'tfunc_import'), |
| 1110 | break_in_func('test_gen', TEST_MODULE_FNAME), |
| 1111 | ('None', 2, 'tfunc_import'), ('continue', ), |
| 1112 | ('line', 7, 'test_gen', ({1:1}, [])), ('next', ), |
| 1113 | ('line', 8, 'test_gen'), ('next', ), |
| 1114 | ('exception', 11, 'main', StopIteration), ('step', ), |
| 1115 | ('line', 13, 'main'), ('quit', ), |
| 1116 | |
| 1117 | ] |
| 1118 | with TracerRun(self) as tracer: |
| 1119 | tracer.runcall(tfunc_import) |
| 1120 | |
| 1121 | def test_return_command_in_generator_with_subiterator(self): |
| 1122 | # Issue #16596. |
| 1123 | code = """ |
| 1124 | def test_subgen(): |
| 1125 | yield 0 |
| 1126 | return 123 |
| 1127 | |
| 1128 | def test_gen(): |
| 1129 | x = yield from test_subgen() |
| 1130 | return 456 |
| 1131 | |
| 1132 | def main(): |
| 1133 | for i in test_gen(): |
| 1134 | lno = 12 |
| 1135 | lno = 13 |
| 1136 | """ |
| 1137 | modules = { TEST_MODULE: code } |
| 1138 | with create_modules(modules): |
| 1139 | self.expect_set = [ |
| 1140 | ('line', 2, 'tfunc_import'), |
| 1141 | break_in_func('test_subgen', TEST_MODULE_FNAME), |
| 1142 | ('None', 2, 'tfunc_import'), ('continue', ), |
| 1143 | ('line', 3, 'test_subgen', ({1:1}, [])), ('return', ), |
| 1144 | ('exception', 7, 'test_gen', StopIteration), ('return', ), |
| 1145 | ('exception', 11, 'main', StopIteration), ('step', ), |
| 1146 | ('line', 13, 'main'), ('quit', ), |
| 1147 | |
| 1148 | ] |
| 1149 | with TracerRun(self) as tracer: |
| 1150 | tracer.runcall(tfunc_import) |
| 1151 | |
| 1152 | def test_main(): |
| 1153 | test.support.run_unittest( |
| 1154 | StateTestCase, |
| 1155 | RunTestCase, |
| 1156 | BreakpointTestCase, |
| 1157 | IssuesTestCase, |
| 1158 | ) |
| 1159 | |
| 1160 | if __name__ == "__main__": |
| 1161 | test_main() |