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