diff --git a/Lib/bdb.py b/Lib/bdb.py --- a/Lib/bdb.py +++ b/Lib/bdb.py @@ -39,6 +39,7 @@ linecache.checkcache() self.botframe = None self._set_stopinfo(None, None) + self.skip_first_call = True def trace_dispatch(self, frame, event, arg): if self.quitting: @@ -69,9 +70,9 @@ def dispatch_call(self, frame, arg): # XXX 'arg' is no longer used if self.botframe is None: - # First call of dispatch since reset() - self.botframe = frame.f_back # (CT) Note that this may also be None! - return self.trace_dispatch + self.botframe = frame + if self.skip_first_call: + return self.trace_dispatch if not (self.stop_here(frame) or self.break_anywhere(frame)): # No need to trace this function return # None @@ -435,6 +436,7 @@ self.reset() sys.settrace(self.trace_dispatch) res = None + self.skip_first_call = False try: res = func(*args, **kwds) except BdbQuit: diff --git a/Lib/pdb.py b/Lib/pdb.py --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -151,7 +151,6 @@ self.aliases = {} self.displaying = {} self.mainpyfile = '' - self._wait_for_mainpyfile = False self.tb_lineno = {} # Try to load readline if it exists try: @@ -193,9 +192,10 @@ raise KeyboardInterrupt self.message("\nProgram interrupted. (Use 'cont' to resume).") self.set_step() - self.set_trace(frame) # restore previous signal handler signal.signal(signal.SIGINT, self._previous_sigint_handler) + frame.f_trace = self.trace_dispatch + sys.settrace(self.trace_dispatch) def reset(self): bdb.Bdb.reset(self) @@ -249,19 +249,12 @@ 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.""" - if self._wait_for_mainpyfile: - return if self.stop_here(frame): self.message('--Call--') self.interaction(frame, None) def user_line(self, frame): """This function is called when we stop or break at this line.""" - if self._wait_for_mainpyfile: - if (self.mainpyfile != self.canonic(frame.f_code.co_filename) - or frame.f_lineno <= 0): - return - self._wait_for_mainpyfile = False if self.bp_commands(frame): self.interaction(frame, None) @@ -291,8 +284,6 @@ def user_return(self, frame, return_value): """This function is called when a return trap is set here.""" - if self._wait_for_mainpyfile: - return frame.f_locals['__return__'] = return_value self.message('--Return--') self.interaction(frame, None) @@ -300,8 +291,6 @@ def user_exception(self, frame, exc_info): """This function is called if an exception occurs, but only if we are to stop at or just below this level.""" - if self._wait_for_mainpyfile: - return exc_type, exc_value, exc_traceback = exc_info frame.f_locals['__exception__'] = exc_type, exc_value self.message(traceback.format_exception_only(exc_type, @@ -1518,18 +1507,11 @@ "__builtins__": __builtins__, }) - # When bdb sets tracing, a number of call and line events happens - # BEFORE debugger even reaches user's code (and the exact sequence of - # events depends on python version). So we take special measures to - # avoid stopping before we reach the main script (see user_line and - # user_call for details). - self._wait_for_mainpyfile = True self.mainpyfile = self.canonic(filename) self._user_requested_quit = False with open(filename, "rb") as fp: - statement = "exec(compile(%r, %r, 'exec'))" % \ - (fp.read(), self.mainpyfile) - self.run(statement) + content = fp.read() + self.run(compile(content, self.mainpyfile, 'exec')) # Collect all command help into docstring, if not run with -OO diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py --- a/Lib/test/test_pdb.py +++ b/Lib/test/test_pdb.py @@ -3,14 +3,20 @@ import imp import pdb import sys +import signal import unittest import subprocess import textwrap +try: + import threading +except ImportError: + threading = None from test import support # This little helper class is essential for testing pdb under doctest. from test.test_doctest import _FakeInput +mswindows = (sys.platform == "win32") class PdbTestInput(object): """Context manager that makes testing Pdb in doctests easier.""" @@ -597,9 +603,26 @@ """ +def test_pdb_runcall(): + """Test that runcall stops at the call event. + + >>> def foo(): + ... pass + >>> with PdbTestInput(['step', 'continue']): + ... pdb_invoke('runcall', foo) + --Call-- + > (1)foo() + -> def foo(): + (Pdb) step + > (2)foo() + -> pass + (Pdb) continue + """ + + class PdbTestCase(unittest.TestCase): - def run_pdb(self, script, commands): + def run_pdb(self, script, commands, sigint=False): """Run 'script' lines with pdb and the pdb 'commands'.""" filename = 'main.py' with open(filename, 'w') as f: @@ -611,6 +634,14 @@ stdin=subprocess.PIPE, stderr=subprocess.STDOUT, ) as proc: + # Interrupt the spawned process. + if threading and sigint: + def kill(): + # Wait for the subprocess to be fully started before + # sending the signal. + import time; time.sleep(1) + proc.send_signal(signal.SIGINT) + threading.Thread(target=kill).start() stdout, stderr = proc.communicate(str.encode(commands)) stdout = stdout and bytes.decode(stdout) stderr = stderr and bytes.decode(stderr) @@ -667,6 +698,55 @@ any('main.py(5)foo()->None' in l for l in stdout.splitlines()), 'Fail to step into the caller after a return') + def test_issue_14743_1(self): + script = "x = 1" + commands = """ + step + step + """ + stdout, stderr = self.run_pdb(script, commands) + self.assertTrue( + any('The program finished and will be restarted' + in l for l in stdout.splitlines()), + 'Debugging session not terminated after returning from initial' + ' user frame.') + + @unittest.skipIf(mswindows or not threading, "posix with threads test") + def test_issue_14743_2(self): + script = """ + import bar + + def foo(): + bar.bar() + + foo() + pass # for setting the breakpoint + """ + commands = """ + break 8 + continue + !i=0 + continue + step + step + """ + bar = """ + import time + def bar(): + i = 1 + while i: + time.sleep(.100) + """ + 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, sigint=True) + self.assertTrue( + any('The program finished and will be restarted' + in l for l in stdout.splitlines()), + 'Interrupted debugging session not terminated after returning' + ' from initial user frame.') + def tearDown(self): support.unlink(support.TESTFN)