bdb.py 22.8 KB
Newer Older
1
"""Debugger basics"""
Guido van Rossum's avatar
Guido van Rossum committed
2

3
import fnmatch
Guido van Rossum's avatar
Guido van Rossum committed
4
import sys
5
import os
6
from inspect import CO_GENERATOR
Guido van Rossum's avatar
Guido van Rossum committed
7

8
__all__ = ["BdbQuit", "Bdb", "Breakpoint"]
9

10
class BdbQuit(Exception):
11
    """Exception to give up completely."""
Guido van Rossum's avatar
Guido van Rossum committed
12 13


14
class Bdb:
15 16 17 18 19 20 21
    """Generic Python debugger base class.

    This class takes care of details of the trace facility;
    a derived class should implement user interaction.
    The standard debugger class (pdb.Pdb) is an example.
    """

22 23
    def __init__(self, skip=None):
        self.skip = set(skip) if skip else None
24 25
        self.breaks = {}
        self.fncache = {}
26
        self.frame_returning = None
27 28

    def canonic(self, filename):
29 30
        if filename == "<" + filename[1:-1] + ">":
            return filename
31 32 33
        canonic = self.fncache.get(filename)
        if not canonic:
            canonic = os.path.abspath(filename)
34
            canonic = os.path.normcase(canonic)
35 36
            self.fncache[filename] = canonic
        return canonic
Tim Peters's avatar
Tim Peters committed
37

38 39 40 41
    def reset(self):
        import linecache
        linecache.checkcache()
        self.botframe = None
42
        self._set_stopinfo(None, None)
Tim Peters's avatar
Tim Peters committed
43

44 45 46 47 48 49 50 51 52 53 54
    def trace_dispatch(self, frame, event, arg):
        if self.quitting:
            return # None
        if event == 'line':
            return self.dispatch_line(frame)
        if event == 'call':
            return self.dispatch_call(frame, arg)
        if event == 'return':
            return self.dispatch_return(frame, arg)
        if event == 'exception':
            return self.dispatch_exception(frame, arg)
55 56 57 58 59 60
        if event == 'c_call':
            return self.trace_dispatch
        if event == 'c_exception':
            return self.trace_dispatch
        if event == 'c_return':
            return self.trace_dispatch
61
        print('bdb.Bdb.dispatch: unknown debugging event:', repr(event))
62
        return self.trace_dispatch
Tim Peters's avatar
Tim Peters committed
63

64 65 66 67 68
    def dispatch_line(self, frame):
        if self.stop_here(frame) or self.break_here(frame):
            self.user_line(frame)
            if self.quitting: raise BdbQuit
        return self.trace_dispatch
Tim Peters's avatar
Tim Peters committed
69

70 71 72 73
    def dispatch_call(self, frame, arg):
        # XXX 'arg' is no longer used
        if self.botframe is None:
            # First call of dispatch since reset()
74
            self.botframe = frame.f_back # (CT) Note that this may also be None!
75 76 77 78
            return self.trace_dispatch
        if not (self.stop_here(frame) or self.break_anywhere(frame)):
            # No need to trace this function
            return # None
79 80 81
        # Ignore call events in generator except when stepping.
        if self.stopframe and frame.f_code.co_flags & CO_GENERATOR:
            return self.trace_dispatch
82 83 84
        self.user_call(frame, arg)
        if self.quitting: raise BdbQuit
        return self.trace_dispatch
Tim Peters's avatar
Tim Peters committed
85

86 87
    def dispatch_return(self, frame, arg):
        if self.stop_here(frame) or frame == self.returnframe:
88 89 90
            # Ignore return events in generator except when stepping.
            if self.stopframe and frame.f_code.co_flags & CO_GENERATOR:
                return self.trace_dispatch
91 92 93 94 95
            try:
                self.frame_returning = frame
                self.user_return(frame, arg)
            finally:
                self.frame_returning = None
96
            if self.quitting: raise BdbQuit
97 98 99
            # The user issued a 'next' or 'until' command.
            if self.stopframe is frame and self.stoplineno != -1:
                self._set_stopinfo(None, None)
100
        return self.trace_dispatch
Tim Peters's avatar
Tim Peters committed
101

102 103
    def dispatch_exception(self, frame, arg):
        if self.stop_here(frame):
104 105 106 107 108 109 110 111 112 113 114 115 116 117
            # When stepping with next/until/return in a generator frame, skip
            # the internal StopIteration exception (with no traceback)
            # triggered by a subiterator run with the 'yield from' statement.
            if not (frame.f_code.co_flags & CO_GENERATOR
                    and arg[0] is StopIteration and arg[2] is None):
                self.user_exception(frame, arg)
                if self.quitting: raise BdbQuit
        # Stop at the StopIteration or GeneratorExit exception when the user
        # has set stopframe in a generator by issuing a return command, or a
        # next/until command at the last statement in the generator before the
        # exception.
        elif (self.stopframe and frame is not self.stopframe
                and self.stopframe.f_code.co_flags & CO_GENERATOR
                and arg[0] in (StopIteration, GeneratorExit)):
118 119
            self.user_exception(frame, arg)
            if self.quitting: raise BdbQuit
120

121
        return self.trace_dispatch
Tim Peters's avatar
Tim Peters committed
122

123 124 125
    # Normally derived classes don't override the following
    # methods, but they may if they want to redefine the
    # definition of stopping and breakpoints.
Tim Peters's avatar
Tim Peters committed
126

127 128 129 130 131 132
    def is_skipped_module(self, module_name):
        for pattern in self.skip:
            if fnmatch.fnmatch(module_name, pattern):
                return True
        return False

133
    def stop_here(self, frame):
Neal Norwitz's avatar
Neal Norwitz committed
134 135
        # (CT) stopframe may now also be None, see dispatch_call.
        # (CT) the former test for None is therefore removed from here.
136 137 138
        if self.skip and \
               self.is_skipped_module(frame.f_globals.get('__name__')):
            return False
139
        if frame is self.stopframe:
140 141
            if self.stoplineno == -1:
                return False
142
            return frame.f_lineno >= self.stoplineno
143 144
        if not self.stopframe:
            return True
145
        return False
146 147 148

    def break_here(self, frame):
        filename = self.canonic(frame.f_code.co_filename)
149
        if filename not in self.breaks:
150
            return False
151
        lineno = frame.f_lineno
152
        if lineno not in self.breaks[filename]:
153 154 155
            # The line itself has no breakpoint, but maybe the line is the
            # first line of a function with breakpoint set by function name.
            lineno = frame.f_code.co_firstlineno
156
            if lineno not in self.breaks[filename]:
157 158
                return False

159 160 161 162 163 164
        # flag says ok to delete temp. bp
        (bp, flag) = effective(filename, lineno, frame)
        if bp:
            self.currentbp = bp.number
            if (flag and bp.temporary):
                self.do_clear(str(bp.number))
165
            return True
166
        else:
167
            return False
Tim Peters's avatar
Tim Peters committed
168

169
    def do_clear(self, arg):
170
        raise NotImplementedError("subclass of bdb must implement do_clear()")
171

172
    def break_anywhere(self, frame):
173
        return self.canonic(frame.f_code.co_filename) in self.breaks
Tim Peters's avatar
Tim Peters committed
174

175 176
    # Derived classes should override the user_* methods
    # to gain control.
Tim Peters's avatar
Tim Peters committed
177

178 179 180 181
    def user_call(self, frame, argument_list):
        """This method is called when there is the remote possibility
        that we ever need to stop in this function."""
        pass
Tim Peters's avatar
Tim Peters committed
182

183 184 185
    def user_line(self, frame):
        """This method is called when we stop or break at this line."""
        pass
Tim Peters's avatar
Tim Peters committed
186

187 188 189
    def user_return(self, frame, return_value):
        """This method is called when a return trap is set here."""
        pass
Tim Peters's avatar
Tim Peters committed
190

191
    def user_exception(self, frame, exc_info):
192 193 194
        """This method is called if an exception occurs,
        but only if we are to stop at or just below this level."""
        pass
Tim Peters's avatar
Tim Peters committed
195

196
    def _set_stopinfo(self, stopframe, returnframe, stoplineno=0):
197 198
        self.stopframe = stopframe
        self.returnframe = returnframe
199
        self.quitting = False
200 201
        # stoplineno >= 0 means: stop at line >= the stoplineno
        # stoplineno -1 means: don't stop at all
202 203
        self.stoplineno = stoplineno

204 205
    # Derived classes and clients can call the following methods
    # to affect the stepping state.
Tim Peters's avatar
Tim Peters committed
206

207
    def set_until(self, frame, lineno=None):
208 209
        """Stop when the line with the line no greater than the current one is
        reached or when returning from current frame"""
210 211 212 213
        # the name "until" is borrowed from gdb
        if lineno is None:
            lineno = frame.f_lineno + 1
        self._set_stopinfo(frame, frame, lineno)
214

215 216
    def set_step(self):
        """Stop after one line of code."""
217 218 219 220 221 222 223 224
        # Issue #13183: pdb skips frames after hitting a breakpoint and running
        # step commands.
        # Restore the trace function in the caller (that may not have been set
        # for performance reasons) when returning from the current frame.
        if self.frame_returning:
            caller_frame = self.frame_returning.f_back
            if caller_frame and not caller_frame.f_trace:
                caller_frame.f_trace = self.trace_dispatch
225
        self._set_stopinfo(None, None)
Tim Peters's avatar
Tim Peters committed
226

227 228
    def set_next(self, frame):
        """Stop on the next line in or below the given frame."""
229
        self._set_stopinfo(frame, None)
Tim Peters's avatar
Tim Peters committed
230

231 232
    def set_return(self, frame):
        """Stop when returning from the given frame."""
233 234 235 236
        if frame.f_code.co_flags & CO_GENERATOR:
            self._set_stopinfo(frame, None, -1)
        else:
            self._set_stopinfo(frame.f_back, frame)
Tim Peters's avatar
Tim Peters committed
237

238 239 240 241 242 243 244
    def set_trace(self, frame=None):
        """Start debugging from `frame`.

        If frame is not specified, debugging starts from caller's frame.
        """
        if frame is None:
            frame = sys._getframe().f_back
245 246 247 248 249 250 251 252 253 254
        self.reset()
        while frame:
            frame.f_trace = self.trace_dispatch
            self.botframe = frame
            frame = frame.f_back
        self.set_step()
        sys.settrace(self.trace_dispatch)

    def set_continue(self):
        # Don't stop except at breakpoints or when finished
255
        self._set_stopinfo(self.botframe, None, -1)
Georg Brandl's avatar
Georg Brandl committed
256
        if not self.breaks:
257 258
            # no breakpoints; run without debugger overhead
            sys.settrace(None)
259
            frame = sys._getframe().f_back
260 261 262
            while frame and frame is not self.botframe:
                del frame.f_trace
                frame = frame.f_back
Tim Peters's avatar
Tim Peters committed
263

264 265 266
    def set_quit(self):
        self.stopframe = self.botframe
        self.returnframe = None
267
        self.quitting = True
268
        sys.settrace(None)
Tim Peters's avatar
Tim Peters committed
269

270 271 272 273 274 275
    # Derived classes and clients can call the following methods
    # to manipulate breakpoints.  These methods return an
    # error message is something went wrong, None if all is well.
    # Set_break prints out the breakpoint line and file:lineno.
    # Call self.get_*break*() to see the breakpoints or better
    # for bp in Breakpoint.bpbynumber: if bp: bp.bpprint().
Tim Peters's avatar
Tim Peters committed
276

277
    def set_break(self, filename, lineno, temporary=False, cond=None,
278
                  funcname=None):
279 280 281 282
        filename = self.canonic(filename)
        import linecache # Import as late as possible
        line = linecache.getline(filename, lineno)
        if not line:
283 284 285
            return 'Line %s:%d does not exist' % (filename, lineno)
        list = self.breaks.setdefault(filename, [])
        if lineno not in list:
286
            list.append(lineno)
287
        bp = Breakpoint(filename, lineno, temporary, cond, funcname)
288

289 290 291 292 293 294
    def _prune_breaks(self, filename, lineno):
        if (filename, lineno) not in Breakpoint.bplist:
            self.breaks[filename].remove(lineno)
        if not self.breaks[filename]:
            del self.breaks[filename]

295 296
    def clear_break(self, filename, lineno):
        filename = self.canonic(filename)
297
        if filename not in self.breaks:
298 299
            return 'There are no breakpoints in %s' % filename
        if lineno not in self.breaks[filename]:
300
            return 'There is no breakpoint at %s:%d' % (filename, lineno)
301 302 303 304
        # If there's only one bp in the list for that file,line
        # pair, then remove the breaks entry
        for bp in Breakpoint.bplist[filename, lineno][:]:
            bp.deleteMe()
305
        self._prune_breaks(filename, lineno)
Tim Peters's avatar
Tim Peters committed
306

307 308
    def clear_bpbynumber(self, arg):
        try:
309 310 311
            bp = self.get_bpbynumber(arg)
        except ValueError as err:
            return str(err)
312 313
        bp.deleteMe()
        self._prune_breaks(bp.file, bp.line)
314 315 316

    def clear_all_file_breaks(self, filename):
        filename = self.canonic(filename)
317
        if filename not in self.breaks:
318 319 320 321 322 323
            return 'There are no breakpoints in %s' % filename
        for line in self.breaks[filename]:
            blist = Breakpoint.bplist[filename, line]
            for bp in blist:
                bp.deleteMe()
        del self.breaks[filename]
Tim Peters's avatar
Tim Peters committed
324

325 326 327 328 329 330 331
    def clear_all_breaks(self):
        if not self.breaks:
            return 'There are no breakpoints'
        for bp in Breakpoint.bpbynumber:
            if bp:
                bp.deleteMe()
        self.breaks = {}
Tim Peters's avatar
Tim Peters committed
332

333 334 335 336 337 338 339 340 341 342 343 344 345 346 347
    def get_bpbynumber(self, arg):
        if not arg:
            raise ValueError('Breakpoint number expected')
        try:
            number = int(arg)
        except ValueError:
            raise ValueError('Non-numeric breakpoint number %s' % arg)
        try:
            bp = Breakpoint.bpbynumber[number]
        except IndexError:
            raise ValueError('Breakpoint number %d out of range' % number)
        if bp is None:
            raise ValueError('Breakpoint %d already deleted' % number)
        return bp

348 349
    def get_break(self, filename, lineno):
        filename = self.canonic(filename)
350
        return filename in self.breaks and \
351
            lineno in self.breaks[filename]
Tim Peters's avatar
Tim Peters committed
352

353 354
    def get_breaks(self, filename, lineno):
        filename = self.canonic(filename)
355
        return filename in self.breaks and \
356 357
            lineno in self.breaks[filename] and \
            Breakpoint.bplist[filename, lineno] or []
Tim Peters's avatar
Tim Peters committed
358

359 360
    def get_file_breaks(self, filename):
        filename = self.canonic(filename)
361
        if filename in self.breaks:
362 363 364
            return self.breaks[filename]
        else:
            return []
Tim Peters's avatar
Tim Peters committed
365

366 367
    def get_all_breaks(self):
        return self.breaks
Tim Peters's avatar
Tim Peters committed
368

369 370
    # Derived classes and clients can call the following method
    # to get a data structure representing a stack trace.
Tim Peters's avatar
Tim Peters committed
371

372 373 374 375 376 377 378 379 380 381 382 383 384 385
    def get_stack(self, f, t):
        stack = []
        if t and t.tb_frame is f:
            t = t.tb_next
        while f is not None:
            stack.append((f, f.f_lineno))
            if f is self.botframe:
                break
            f = f.f_back
        stack.reverse()
        i = max(0, len(stack) - 1)
        while t is not None:
            stack.append((t.tb_frame, t.tb_lineno))
            t = t.tb_next
Benjamin Peterson's avatar
Benjamin Peterson committed
386 387
        if f is None:
            i = max(0, len(stack) - 1)
388
        return stack, i
Tim Peters's avatar
Tim Peters committed
389

390
    def format_stack_entry(self, frame_lineno, lprefix=': '):
391
        import linecache, reprlib
392 393
        frame, lineno = frame_lineno
        filename = self.canonic(frame.f_code.co_filename)
394
        s = '%s(%r)' % (filename, lineno)
395
        if frame.f_code.co_name:
396
            s += frame.f_code.co_name
397
        else:
398
            s += "<lambda>"
399
        if '__args__' in frame.f_locals:
400 401 402 403
            args = frame.f_locals['__args__']
        else:
            args = None
        if args:
404
            s += reprlib.repr(args)
405
        else:
406
            s += '()'
407
        if '__return__' in frame.f_locals:
408
            rv = frame.f_locals['__return__']
409 410
            s += '->'
            s += reprlib.repr(rv)
411
        line = linecache.getline(filename, lineno, frame.f_globals)
412 413
        if line:
            s += lprefix + line.strip()
414
        return s
Tim Peters's avatar
Tim Peters committed
415

416 417 418
    # The following methods can be called by clients to use
    # a debugger to debug a statement or an expression.
    # Both can be given as a string, or a code object.
Tim Peters's avatar
Tim Peters committed
419

420 421 422 423 424 425 426
    def run(self, cmd, globals=None, locals=None):
        if globals is None:
            import __main__
            globals = __main__.__dict__
        if locals is None:
            locals = globals
        self.reset()
427 428
        if isinstance(cmd, str):
            cmd = compile(cmd, "<string>", "exec")
429 430
        sys.settrace(self.trace_dispatch)
        try:
431 432 433
            exec(cmd, globals, locals)
        except BdbQuit:
            pass
434
        finally:
435
            self.quitting = True
436
            sys.settrace(None)
Tim Peters's avatar
Tim Peters committed
437

438 439 440 441 442 443 444 445 446
    def runeval(self, expr, globals=None, locals=None):
        if globals is None:
            import __main__
            globals = __main__.__dict__
        if locals is None:
            locals = globals
        self.reset()
        sys.settrace(self.trace_dispatch)
        try:
447 448 449
            return eval(expr, globals, locals)
        except BdbQuit:
            pass
450
        finally:
451
            self.quitting = True
452 453 454 455 456 457 458 459
            sys.settrace(None)

    def runctx(self, cmd, globals, locals):
        # B/W compatibility
        self.run(cmd, globals, locals)

    # This method is more useful to debug a single function call.

460
    def runcall(self, func, *args, **kwds):
461 462 463 464
        self.reset()
        sys.settrace(self.trace_dispatch)
        res = None
        try:
465 466 467
            res = func(*args, **kwds)
        except BdbQuit:
            pass
468
        finally:
469
            self.quitting = True
470 471
            sys.settrace(None)
        return res
Guido van Rossum's avatar
Guido van Rossum committed
472 473


474
def set_trace():
475
    Bdb().set_trace()
476

477 478

class Breakpoint:
479
    """Breakpoint class.
480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500

    Implements temporary breakpoints, ignore counts, disabling and
    (re)-enabling, and conditionals.

    Breakpoints are indexed by number through bpbynumber and by
    the file,line tuple using bplist.  The former points to a
    single instance of class Breakpoint.  The latter points to a
    list of such instances since there may be more than one
    breakpoint per line.

    """

    # XXX Keeping state in the class is a mistake -- this means
    # you cannot have more than one active Bdb instance.

    next = 1        # Next bp to be assigned
    bplist = {}     # indexed by (file, lineno) tuple
    bpbynumber = [None] # Each entry is None or an instance of Bpt
                # index 0 is unused, except for marking an
                # effective break .... see effective()

501
    def __init__(self, file, line, temporary=False, cond=None, funcname=None):
502 503 504
        self.funcname = funcname
        # Needed if funcname is not None.
        self.func_first_executable_line = None
505 506 507 508
        self.file = file    # This better be in canonical form!
        self.line = line
        self.temporary = temporary
        self.cond = cond
509
        self.enabled = True
510 511 512
        self.ignore = 0
        self.hits = 0
        self.number = Breakpoint.next
513
        Breakpoint.next += 1
514 515
        # Build the two lists
        self.bpbynumber.append(self)
516
        if (file, line) in self.bplist:
517 518 519 520 521 522 523 524 525 526 527 528 529
            self.bplist[file, line].append(self)
        else:
            self.bplist[file, line] = [self]

    def deleteMe(self):
        index = (self.file, self.line)
        self.bpbynumber[self.number] = None   # No longer in list
        self.bplist[index].remove(self)
        if not self.bplist[index]:
            # No more bp for this f:l combo
            del self.bplist[index]

    def enable(self):
530
        self.enabled = True
531 532

    def disable(self):
533
        self.enabled = False
534

535 536 537
    def bpprint(self, out=None):
        if out is None:
            out = sys.stdout
538 539 540
        print(self.bpformat(), file=out)

    def bpformat(self):
541
        if self.temporary:
Tim Peters's avatar
Tim Peters committed
542
            disp = 'del  '
543
        else:
Tim Peters's avatar
Tim Peters committed
544
            disp = 'keep '
545
        if self.enabled:
546
            disp = disp + 'yes  '
547
        else:
548
            disp = disp + 'no   '
549 550
        ret = '%-4dbreakpoint   %s at %s:%d' % (self.number, disp,
                                                self.file, self.line)
551
        if self.cond:
552
            ret += '\n\tstop only if %s' % (self.cond,)
553
        if self.ignore:
554 555 556 557 558 559 560 561
            ret += '\n\tignore next %d hits' % (self.ignore,)
        if self.hits:
            if self.hits > 1:
                ss = 's'
            else:
                ss = ''
            ret += '\n\tbreakpoint already hit %d time%s' % (self.hits, ss)
        return ret
562

563 564 565
    def __str__(self):
        return 'breakpoint %s at %s:%s' % (self.number, self.file, self.line)

566 567
# -----------end of Breakpoint class----------

568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593
def checkfuncname(b, frame):
    """Check whether we should break here because of `b.funcname`."""
    if not b.funcname:
        # Breakpoint was set via line number.
        if b.line != frame.f_lineno:
            # Breakpoint was set at a line with a def statement and the function
            # defined is called: don't break.
            return False
        return True

    # Breakpoint set via function name.

    if frame.f_code.co_name != b.funcname:
        # It's not a function call, but rather execution of def statement.
        return False

    # We are in the right frame.
    if not b.func_first_executable_line:
        # The function is entered for the 1st time.
        b.func_first_executable_line = frame.f_lineno

    if  b.func_first_executable_line != frame.f_lineno:
        # But we are not at the first line number: don't break.
        return False
    return True

594 595 596
# Determines if there is an effective (active) breakpoint at this
# line of code.  Returns breakpoint number or 0 if none
def effective(file, line, frame):
597 598 599 600 601 602 603
    """Determine which breakpoint for this file:line is to be acted upon.

    Called only if we know there is a bpt at this
    location.  Returns breakpoint that was triggered and a flag
    that indicates if it is ok to delete a temporary bp.

    """
604 605 606
    possibles = Breakpoint.bplist[file, line]
    for b in possibles:
        if not b.enabled:
607
            continue
608 609
        if not checkfuncname(b, frame):
            continue
610
        # Count every hit when bp is enabled
611
        b.hits += 1
612
        if not b.cond:
613
            # If unconditional, and ignoring go on to next, else break
614
            if b.ignore > 0:
615
                b.ignore -= 1
616 617
                continue
            else:
618 619
                # breakpoint and marker that it's ok to delete if temporary
                return (b, True)
620 621 622 623 624
        else:
            # Conditional bp.
            # Ignore count applies only to those bpt hits where the
            # condition evaluates to true.
            try:
625
                val = eval(b.cond, frame.f_globals, frame.f_locals)
626 627
                if val:
                    if b.ignore > 0:
628
                        b.ignore -= 1
629 630
                        # continue
                    else:
631
                        return (b, True)
632 633 634
                # else:
                #   continue
            except:
635 636 637 638
                # if eval fails, most conservative thing is to stop on
                # breakpoint regardless of ignore count.  Don't delete
                # temporary, as another hint to user.
                return (b, False)
639
    return (None, None)
640

641

Guido van Rossum's avatar
Guido van Rossum committed
642 643 644
# -------------------- testing --------------------

class Tdb(Bdb):
645 646 647
    def user_call(self, frame, args):
        name = frame.f_code.co_name
        if not name: name = '???'
648
        print('+++ call', name, args)
649
    def user_line(self, frame):
650
        import linecache
651 652 653
        name = frame.f_code.co_name
        if not name: name = '???'
        fn = self.canonic(frame.f_code.co_filename)
654
        line = linecache.getline(fn, frame.f_lineno, frame.f_globals)
655
        print('+++', fn, frame.f_lineno, name, ':', line.strip())
656
    def user_return(self, frame, retval):
657
        print('+++ return', retval)
658
    def user_exception(self, frame, exc_stuff):
659
        print('+++ exception', exc_stuff)
660
        self.set_continue()
Guido van Rossum's avatar
Guido van Rossum committed
661 662

def foo(n):
663
    print('foo(', n, ')')
664
    x = bar(n*10)
665
    print('bar returned', x)
Guido van Rossum's avatar
Guido van Rossum committed
666 667

def bar(a):
668
    print('bar(', a, ')')
669
    return a/2
Guido van Rossum's avatar
Guido van Rossum committed
670 671

def test():
672 673
    t = Tdb()
    t.run('import bdb; bdb.foo(10)')