diff options
-rw-r--r-- | Doc/library/pdb.rst | 6 | ||||
-rw-r--r-- | Doc/whatsnew/3.7.rst | 4 | ||||
-rwxr-xr-x | Lib/pdb.py | 33 | ||||
-rw-r--r-- | Lib/test/test_pdb.py | 161 | ||||
-rw-r--r-- | Misc/NEWS.d/next/Library/2017-12-07-13-14-40.bpo-32206.obm4OM.rst | 1 |
5 files changed, 184 insertions, 21 deletions
diff --git a/Doc/library/pdb.rst b/Doc/library/pdb.rst index 4f3148f..e81c195 100644 --- a/Doc/library/pdb.rst +++ b/Doc/library/pdb.rst @@ -61,6 +61,12 @@ useful than quitting the debugger upon program's exit. :file:`pdb.py` now accepts a ``-c`` option that executes commands as if given in a :file:`.pdbrc` file, see :ref:`debugger-commands`. +.. versionadded:: 3.7 + :file:`pdb.py` now accepts a ``-m`` option that execute modules similar to the way + ``python3 -m`` does. As with a script, the debugger will pause execution just + before the first line of the module. + + The typical usage to break into the debugger from a running program is to insert :: diff --git a/Doc/whatsnew/3.7.rst b/Doc/whatsnew/3.7.rst index 1311e9e..9785d59 100644 --- a/Doc/whatsnew/3.7.rst +++ b/Doc/whatsnew/3.7.rst @@ -426,6 +426,10 @@ pdb argument. If given, this is printed to the console just before debugging begins. (Contributed by Barry Warsaw in :issue:`31389`.) +pdb command line now accepts `-m module_name` as an alternative to +script file. (Contributed by Mario Corchero in :issue:`32206`.) + + re -- @@ -1521,6 +1521,24 @@ class Pdb(bdb.Bdb, cmd.Cmd): return fullname return None + def _runmodule(self, module_name): + self._wait_for_mainpyfile = True + self._user_requested_quit = False + import runpy + mod_name, mod_spec, code = runpy._get_module_details(module_name) + self.mainpyfile = self.canonic(code.co_filename) + import __main__ + __main__.__dict__.clear() + __main__.__dict__.update({ + "__name__": "__main__", + "__file__": self.mainpyfile, + "__package__": module_name, + "__loader__": mod_spec.loader, + "__spec__": mod_spec, + "__builtins__": __builtins__, + }) + self.run(code) + def _runscript(self, filename): # The script has to run in __main__ namespace (or imports from # __main__ will break). @@ -1635,29 +1653,33 @@ To let the script run up to a given line X in the debugged file, use def main(): import getopt - opts, args = getopt.getopt(sys.argv[1:], 'hc:', ['--help', '--command=']) + opts, args = getopt.getopt(sys.argv[1:], 'mhc:', ['--help', '--command=']) if not args: print(_usage) sys.exit(2) commands = [] + run_as_module = False for opt, optarg in opts: if opt in ['-h', '--help']: print(_usage) sys.exit() elif opt in ['-c', '--command']: commands.append(optarg) + elif opt in ['-m']: + run_as_module = True mainpyfile = args[0] # Get script filename - if not os.path.exists(mainpyfile): + if not run_as_module and not os.path.exists(mainpyfile): print('Error:', mainpyfile, 'does not exist') sys.exit(1) sys.argv[:] = args # Hide "pdb.py" and pdb options from argument list # Replace pdb's dir with script's dir in front of module search path. - sys.path[0] = os.path.dirname(mainpyfile) + if not run_as_module: + sys.path[0] = os.path.dirname(mainpyfile) # Note on saving/restoring sys.argv: it's a good idea when sys.argv was # modified by the script being debugged. It's a bad idea when it was @@ -1667,7 +1689,10 @@ def main(): pdb.rcLines.extend(commands) while True: try: - pdb._runscript(mainpyfile) + if run_as_module: + pdb._runmodule(mainpyfile) + else: + pdb._runscript(mainpyfile) if pdb._user_requested_quit: break print("The program finished and will be restarted") diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py index 71d8203..0cd235e 100644 --- a/Lib/test/test_pdb.py +++ b/Lib/test/test_pdb.py @@ -938,26 +938,47 @@ def test_pdb_issue_20766(): pdb 2: <built-in function default_int_handler> """ + class PdbTestCase(unittest.TestCase): + def tearDown(self): + support.unlink(support.TESTFN) - 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) + def _run_pdb(self, pdb_args, commands): self.addCleanup(support.rmtree, '__pycache__') - cmd = [sys.executable, '-m', 'pdb', filename] - stdout = stderr = None - with subprocess.Popen(cmd, stdout=subprocess.PIPE, - stdin=subprocess.PIPE, - stderr=subprocess.STDOUT, - ) as proc: + cmd = [sys.executable, '-m', 'pdb'] + pdb_args + 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 run_pdb_script(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) + return self._run_pdb([filename], commands) + + def run_pdb_module(self, script, commands): + """Runs the script code as part of a module""" + self.module_name = 't_main' + support.rmtree(self.module_name) + main_file = self.module_name + '/__main__.py' + init_file = self.module_name + '/__init__.py' + os.mkdir(self.module_name) + with open(init_file, 'w') as f: + pass + with open(main_file, 'w') as f: + f.write(textwrap.dedent(script)) + self.addCleanup(support.rmtree, self.module_name) + return self._run_pdb(['-m', self.module_name], commands) + def _assert_find_function(self, file_content, func_name, expected): file_content = textwrap.dedent(file_content) @@ -1034,7 +1055,7 @@ class PdbTestCase(unittest.TestCase): 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) + stdout, stderr = self.run_pdb_script(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') @@ -1071,7 +1092,7 @@ class PdbTestCase(unittest.TestCase): script = "def f: pass\n" commands = '' expected = "SyntaxError:" - stdout, stderr = self.run_pdb(script, commands) + stdout, stderr = self.run_pdb_script(script, commands) self.assertIn(expected, stdout, '\n\nExpected:\n{}\nGot:\n{}\n' 'Fail to handle a syntax error in the debuggee.' @@ -1119,13 +1140,119 @@ class PdbTestCase(unittest.TestCase): pdb.set_trace(header=header) self.assertEqual(stdout.getvalue(), header + '\n') - def tearDown(self): - support.unlink(support.TESTFN) + def test_run_module(self): + script = """print("SUCCESS")""" + commands = """ + continue + quit + """ + stdout, stderr = self.run_pdb_module(script, commands) + self.assertTrue(any("SUCCESS" in l for l in stdout.splitlines()), stdout) + + def test_module_is_run_as_main(self): + script = """ + if __name__ == '__main__': + print("SUCCESS") + """ + commands = """ + continue + quit + """ + stdout, stderr = self.run_pdb_module(script, commands) + self.assertTrue(any("SUCCESS" in l for l in stdout.splitlines()), stdout) + + def test_breakpoint(self): + script = """ + if __name__ == '__main__': + pass + print("SUCCESS") + pass + """ + commands = """ + b 3 + quit + """ + stdout, stderr = self.run_pdb_module(script, commands) + self.assertTrue(any("Breakpoint 1 at" in l for l in stdout.splitlines()), stdout) + self.assertTrue(all("SUCCESS" not in l for l in stdout.splitlines()), stdout) + + def test_run_pdb_with_pdb(self): + commands = """ + c + quit + """ + stdout, stderr = self._run_pdb(["-m", "pdb"], commands) + self.assertIn("Debug the Python program given by pyfile.", stdout.splitlines()) + + def test_module_without_a_main(self): + module_name = 't_main' + support.rmtree(module_name) + init_file = module_name + '/__init__.py' + os.mkdir(module_name) + with open(init_file, 'w') as f: + pass + self.addCleanup(support.rmtree, module_name) + stdout, stderr = self._run_pdb(['-m', module_name], "") + self.assertIn("ImportError: No module named t_main.__main__", + stdout.splitlines()) + + def test_blocks_at_first_code_line(self): + script = """ + #This is a comment, on line 2 + + print("SUCCESS") + """ + commands = """ + quit + """ + stdout, stderr = self.run_pdb_module(script, commands) + self.assertTrue(any("__main__.py(4)<module>()" + in l for l in stdout.splitlines()), stdout) + + def test_relative_imports(self): + self.module_name = 't_main' + support.rmtree(self.module_name) + main_file = self.module_name + '/__main__.py' + init_file = self.module_name + '/__init__.py' + module_file = self.module_name + '/module.py' + self.addCleanup(support.rmtree, self.module_name) + os.mkdir(self.module_name) + with open(init_file, 'w') as f: + f.write(textwrap.dedent(""" + top_var = "VAR from top" + """)) + with open(main_file, 'w') as f: + f.write(textwrap.dedent(""" + from . import top_var + from .module import var + from . import module + pass # We'll stop here and print the vars + """)) + with open(module_file, 'w') as f: + f.write(textwrap.dedent(""" + var = "VAR from module" + var2 = "second var" + """)) + commands = """ + b 5 + c + p top_var + p var + p module.var2 + quit + """ + stdout, _ = self._run_pdb(['-m', self.module_name], commands) + self.assertTrue(any("VAR from module" in l for l in stdout.splitlines())) + self.assertTrue(any("VAR from top" in l for l in stdout.splitlines())) + self.assertTrue(any("second var" in l for l in stdout.splitlines())) def load_tests(*args): from test import test_pdb - suites = [unittest.makeSuite(PdbTestCase), doctest.DocTestSuite(test_pdb)] + suites = [ + unittest.makeSuite(PdbTestCase), + doctest.DocTestSuite(test_pdb) + ] return unittest.TestSuite(suites) diff --git a/Misc/NEWS.d/next/Library/2017-12-07-13-14-40.bpo-32206.obm4OM.rst b/Misc/NEWS.d/next/Library/2017-12-07-13-14-40.bpo-32206.obm4OM.rst new file mode 100644 index 0000000..20d7eea --- /dev/null +++ b/Misc/NEWS.d/next/Library/2017-12-07-13-14-40.bpo-32206.obm4OM.rst @@ -0,0 +1 @@ +Add support to run modules with pdb |