# A test suite for pdb; not very comprehensive at the moment. import imp import pdb import sys import unittest import subprocess import textwrap from test import support # This little helper class is essential for testing pdb under doctest. from test.test_doctest import _FakeInput class PdbTestInput(object): """Context manager that makes testing Pdb in doctests easier.""" def __init__(self, input): self.input = input def __enter__(self): self.real_stdin = sys.stdin sys.stdin = _FakeInput(self.input) def __exit__(self, *exc): sys.stdin = self.real_stdin def test_pdb_displayhook(): """This tests the custom displayhook for pdb. >>> def test_function(foo, bar): ... import pdb; pdb.Pdb(nosigint=True).set_trace() ... pass >>> with PdbTestInput([ ... 'foo', ... 'bar', ... 'for i in range(5): print(i)', ... 'continue', ... ]): ... test_function(1, None) > (3)test_function() -> pass (Pdb) foo 1 (Pdb) bar (Pdb) for i in range(5): print(i) 0 1 2 3 4 (Pdb) continue """ def test_pdb_basic_commands(): """Test the basic commands of pdb. >>> def test_function_2(foo, bar='default'): ... print(foo) ... for i in range(5): ... print(i) ... print(bar) ... for i in range(10): ... never_executed ... print('after for') ... print('...') ... return foo.upper() >>> def test_function(): ... import pdb; pdb.Pdb(nosigint=True).set_trace() ... ret = test_function_2('baz') ... print(ret) >>> with PdbTestInput([ # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE ... 'step', # entering the function call ... 'args', # display function args ... 'list', # list function source ... 'bt', # display backtrace ... 'up', # step up to test_function() ... 'down', # step down to test_function_2() again ... 'next', # stepping to print(foo) ... 'next', # stepping to the for loop ... 'step', # stepping into the for loop ... 'until', # continuing until out of the for loop ... 'next', # executing the print(bar) ... 'jump 8', # jump over second for loop ... 'return', # return out of function ... 'retval', # display return value ... 'continue', ... ]): ... test_function() > (3)test_function() -> ret = test_function_2('baz') (Pdb) step --Call-- > (1)test_function_2() -> def test_function_2(foo, bar='default'): (Pdb) args foo = 'baz' bar = 'default' (Pdb) list 1 -> def test_function_2(foo, bar='default'): 2 print(foo) 3 for i in range(5): 4 print(i) 5 print(bar) 6 for i in range(10): 7 never_executed 8 print('after for') 9 print('...') 10 return foo.upper() [EOF] (Pdb) bt ... (18)() -> test_function() (3)test_function() -> ret = test_function_2('baz') > (1)test_function_2() -> def test_function_2(foo, bar='default'): (Pdb) up > (3)test_function() -> ret = test_function_2('baz') (Pdb) down > (1)test_function_2() -> def test_function_2(foo, bar='default'): (Pdb) next > (2)test_function_2() -> print(foo) (Pdb) next baz > (3)test_function_2() -> for i in range(5): (Pdb) step > (4)test_function_2() -> print(i) (Pdb) until 0 1 2 3 4 > (5)test_function_2() -> print(bar) (Pdb) next default > (6)test_function_2() -> for i in range(10): (Pdb) jump 8 > (8)test_function_2() -> print('after for') (Pdb) return after for ... --Return-- > (10)test_function_2()->'BAZ' -> return foo.upper() (Pdb) retval 'BAZ' (Pdb) continue BAZ """ def test_pdb_breakpoint_commands(): """Test basic commands related to breakpoints. >>> def test_function(): ... import pdb; pdb.Pdb(nosigint=True).set_trace() ... print(1) ... print(2) ... print(3) ... print(4) First, need to clear bdb state that might be left over from previous tests. Otherwise, the new breakpoints might get assigned different numbers. >>> from bdb import Breakpoint >>> Breakpoint.next = 1 >>> Breakpoint.bplist = {} >>> Breakpoint.bpbynumber = [None] Now test the breakpoint commands. NORMALIZE_WHITESPACE is needed because the breakpoint list outputs a tab for the "stop only" and "ignore next" lines, which we don't want to put in here. >>> with PdbTestInput([ # doctest: +NORMALIZE_WHITESPACE ... 'break 3', ... 'disable 1', ... 'ignore 1 10', ... 'condition 1 1 < 2', ... 'break 4', ... 'break 4', ... 'break', ... 'clear 3', ... 'break', ... 'condition 1', ... 'enable 1', ... 'clear 1', ... 'commands 2', ... 'print 42', ... 'end', ... 'continue', # will stop at breakpoint 2 (line 4) ... 'clear', # clear all! ... 'y', ... 'tbreak 5', ... 'continue', # will stop at temporary breakpoint ... 'break', # make sure breakpoint is gone ... 'continue', ... ]): ... test_function() > (3)test_function() -> print(1) (Pdb) break 3 Breakpoint 1 at :3 (Pdb) disable 1 Disabled breakpoint 1 at :3 (Pdb) ignore 1 10 Will ignore next 10 crossings of breakpoint 1. (Pdb) condition 1 1 < 2 New condition set for breakpoint 1. (Pdb) break 4 Breakpoint 2 at :4 (Pdb) break 4 Breakpoint 3 at :4 (Pdb) break Num Type Disp Enb Where 1 breakpoint keep no at :3 stop only if 1 < 2 ignore next 10 hits 2 breakpoint keep yes at :4 3 breakpoint keep yes at :4 (Pdb) clear 3 Deleted breakpoint 3 at :4 (Pdb) break Num Type Disp Enb Where 1 breakpoint keep no at :3 stop only if 1 < 2 ignore next 10 hits 2 breakpoint keep yes at :4 (Pdb) condition 1 Breakpoint 1 is now unconditional. (Pdb) enable 1 Enabled breakpoint 1 at :3 (Pdb) clear 1 Deleted breakpoint 1 at :3 (Pdb) commands 2 (com) print 42 (com) end (Pdb) continue 1 42 > (4)test_function() -> print(2) (Pdb) clear Clear all breaks? y Deleted breakpoint 2 at :4 (Pdb) tbreak 5 Breakpoint 4 at :5 (Pdb) continue 2 Deleted breakpoint 4 at :5 > (5)test_function() -> print(3) (Pdb) break (Pdb) continue 3 4 """ def do_nothing(): pass def do_something(): print(42) def test_list_commands(): """Test the list and source commands of pdb. >>> def test_function_2(foo): ... import test.test_pdb ... test.test_pdb.do_nothing() ... 'some...' ... 'more...' ... 'code...' ... 'to...' ... 'make...' ... 'a...' ... 'long...' ... 'listing...' ... 'useful...' ... '...' ... '...' ... return foo >>> def test_function(): ... import pdb; pdb.Pdb(nosigint=True).set_trace() ... ret = test_function_2('baz') >>> with PdbTestInput([ # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE ... 'list', # list first function ... 'step', # step into second function ... 'list', # list second function ... 'list', # continue listing to EOF ... 'list 1,3', # list specific lines ... 'list x', # invalid argument ... 'next', # step to import ... 'next', # step over import ... 'step', # step into do_nothing ... 'longlist', # list all lines ... 'source do_something', # list all lines of function ... 'source fooxxx', # something that doesn't exit ... 'continue', ... ]): ... test_function() > (3)test_function() -> ret = test_function_2('baz') (Pdb) list 1 def test_function(): 2 import pdb; pdb.Pdb(nosigint=True).set_trace() 3 -> ret = test_function_2('baz') [EOF] (Pdb) step --Call-- > (1)test_function_2() -> def test_function_2(foo): (Pdb) list 1 -> def test_function_2(foo): 2 import test.test_pdb 3 test.test_pdb.do_nothing() 4 'some...' 5 'more...' 6 'code...' 7 'to...' 8 'make...' 9 'a...' 10 'long...' 11 'listing...' (Pdb) list 12 'useful...' 13 '...' 14 '...' 15 return foo [EOF] (Pdb) list 1,3 1 -> def test_function_2(foo): 2 import test.test_pdb 3 test.test_pdb.do_nothing() (Pdb) list x *** ... (Pdb) next > (2)test_function_2() -> import test.test_pdb (Pdb) next > (3)test_function_2() -> test.test_pdb.do_nothing() (Pdb) step --Call-- > ...test_pdb.py(...)do_nothing() -> def do_nothing(): (Pdb) longlist ... -> def do_nothing(): ... pass (Pdb) source do_something ... def do_something(): ... print(42) (Pdb) source fooxxx *** ... (Pdb) continue """ def test_post_mortem(): """Test post mortem traceback debugging. >>> def test_function_2(): ... try: ... 1/0 ... finally: ... print('Exception!') >>> def test_function(): ... import pdb; pdb.Pdb(nosigint=True).set_trace() ... test_function_2() ... print('Not reached.') >>> with PdbTestInput([ # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE ... 'next', # step over exception-raising call ... 'bt', # get a backtrace ... 'list', # list code of test_function() ... 'down', # step into test_function_2() ... 'list', # list code of test_function_2() ... 'continue', ... ]): ... try: ... test_function() ... except ZeroDivisionError: ... print('Correctly reraised.') > (3)test_function() -> test_function_2() (Pdb) next Exception! ZeroDivisionError: division by zero > (3)test_function() -> test_function_2() (Pdb) bt ... (10)() -> test_function() > (3)test_function() -> test_function_2() (3)test_function_2() -> 1/0 (Pdb) list 1 def test_function(): 2 import pdb; pdb.Pdb(nosigint=True).set_trace() 3 -> test_function_2() 4 print('Not reached.') [EOF] (Pdb) down > (3)test_function_2() -> 1/0 (Pdb) list 1 def test_function_2(): 2 try: 3 >> 1/0 4 finally: 5 -> print('Exception!') [EOF] (Pdb) continue Correctly reraised. """ def test_pdb_skip_modules(): """This illustrates the simple case of module skipping. >>> def skip_module(): ... import string ... import pdb; pdb.Pdb(skip=['stri*'], nosigint=True).set_trace() ... string.capwords('FOO') >>> with PdbTestInput([ ... 'step', ... 'continue', ... ]): ... skip_module() > (4)skip_module() -> string.capwords('FOO') (Pdb) step --Return-- > (4)skip_module()->None -> string.capwords('FOO') (Pdb) continue """ # Module for testing skipping of module that makes a callback mod = imp.new_module('module_to_skip') exec('def foo_pony(callback): x = 1; callback(); return None', mod.__dict__) def test_pdb_skip_modules_with_callback(): """This illustrates skipping of modules that call into other code. >>> def skip_module(): ... def callback(): ... return None ... import pdb; pdb.Pdb(skip=['module_to_skip*'], nosigint=True).set_trace() ... mod.foo_pony(callback) >>> with PdbTestInput([ ... 'step', ... 'step', ... 'step', ... 'step', ... 'step', ... 'continue', ... ]): ... skip_module() ... pass # provides something to "step" to > (5)skip_module() -> mod.foo_pony(callback) (Pdb) step --Call-- > (2)callback() -> def callback(): (Pdb) step > (3)callback() -> return None (Pdb) step --Return-- > (3)callback()->None -> return None (Pdb) step --Return-- > (5)skip_module()->None -> mod.foo_pony(callback) (Pdb) step > (10)() -> pass # provides something to "step" to (Pdb) continue """ def test_pdb_continue_in_bottomframe(): """Test that "continue" and "next" work properly in bottom frame (issue #5294). >>> def test_function(): ... import pdb, sys; inst = pdb.Pdb(nosigint=True) ... inst.set_trace() ... inst.botframe = sys._getframe() # hackery to get the right botframe ... print(1) ... print(2) ... print(3) ... print(4) >>> with PdbTestInput([ # doctest: +ELLIPSIS ... 'next', ... 'break 7', ... 'continue', ... 'next', ... 'continue', ... 'continue', ... ]): ... test_function() > (4)test_function() -> inst.botframe = sys._getframe() # hackery to get the right botframe (Pdb) next > (5)test_function() -> print(1) (Pdb) break 7 Breakpoint ... at :7 (Pdb) continue 1 2 > (7)test_function() -> print(3) (Pdb) next 3 > (8)test_function() -> print(4) (Pdb) continue 4 """ def pdb_invoke(method, arg): """Run pdb.method(arg).""" import pdb getattr(pdb.Pdb(nosigint=True), method)(arg) def test_pdb_run_with_incorrect_argument(): """Testing run and runeval with incorrect first argument. >>> pti = PdbTestInput(['continue',]) >>> with pti: ... pdb_invoke('run', lambda x: x) Traceback (most recent call last): TypeError: exec() arg 1 must be a string, bytes or code object >>> with pti: ... pdb_invoke('runeval', lambda x: x) Traceback (most recent call last): TypeError: eval() arg 1 must be a string, bytes or code object """ def test_pdb_run_with_code_object(): """Testing run and runeval with code object as a first argument. >>> with PdbTestInput(['step','x', 'continue']): # doctest: +ELLIPSIS ... pdb_invoke('run', compile('x=1', '', 'exec')) > (1)()... (Pdb) step --Return-- > (1)()->None (Pdb) x 1 (Pdb) continue >>> with PdbTestInput(['x', 'continue']): ... x=0 ... pdb_invoke('runeval', compile('x+1', '', 'eval')) > (1)()->None (Pdb) x 1 (Pdb) continue """ class PdbTestCase(unittest.TestCase): def run_pdb(self, script, commands): """Run 'script' lines with pdb and the pdb 'commands'.""" filename = 'main.py' with open(filename, 'w') as f: f.write(textwrap.dedent(script)) self.addCleanup(support.unlink, filename) cmd = [sys.executable, '-m', 'pdb', filename] stdout = stderr = None with subprocess.Popen(cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.STDOUT, ) as proc: stdout, stderr = proc.communicate(str.encode(commands)) stdout = stdout and bytes.decode(stdout) stderr = stderr and bytes.decode(stderr) return stdout, stderr def test_issue7964(self): # open the file as binary so we can force \r\n newline with open(support.TESTFN, 'wb') as f: f.write(b'print("testing my pdb")\r\n') cmd = [sys.executable, '-m', 'pdb', support.TESTFN] proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.STDOUT, ) self.addCleanup(proc.stdout.close) stdout, stderr = proc.communicate(b'quit\n') self.assertNotIn(b'SyntaxError', stdout, "Got a syntax error running test script under PDB") def test_issue13183(self): script = """ from bar import bar def foo(): bar() def nope(): pass def foobar(): foo() nope() foobar() """ commands = """ from bar import bar break bar continue step step quit """ bar = """ def bar(): pass """ with open('bar.py', 'w') as f: f.write(textwrap.dedent(bar)) self.addCleanup(support.unlink, 'bar.py') stdout, stderr = self.run_pdb(script, commands) self.assertTrue( any('main.py(5)foo()->None' in l for l in stdout.splitlines()), 'Fail to step into the caller after a return') def test_issue13210(self): # invoking "continue" on a non-main thread triggered an exception # inside signal.signal # raises SkipTest if python was built without threads support.import_module('threading') with open(support.TESTFN, 'wb') as f: f.write(textwrap.dedent(""" import threading import pdb def start_pdb(): pdb.Pdb().set_trace() x = 1 y = 1 t = threading.Thread(target=start_pdb) t.start()""").encode('ascii')) cmd = [sys.executable, '-u', support.TESTFN] proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.STDOUT, ) self.addCleanup(proc.stdout.close) stdout, stderr = proc.communicate(b'cont\n') self.assertNotIn('Error', stdout.decode(), "Got an error running test script under PDB") def tearDown(self): support.unlink(support.TESTFN) def test_main(): from test import test_pdb support.run_doctest(test_pdb, verbosity=True) support.run_unittest(PdbTestCase) if __name__ == '__main__': test_main()