summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/library/pdb.rst6
-rw-r--r--Doc/whatsnew/3.7.rst4
-rwxr-xr-xLib/pdb.py33
-rw-r--r--Lib/test/test_pdb.py161
-rw-r--r--Misc/NEWS.d/next/Library/2017-12-07-13-14-40.bpo-32206.obm4OM.rst1
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
--
diff --git a/Lib/pdb.py b/Lib/pdb.py
index 8dd4ded..d1a74bb 100755
--- a/Lib/pdb.py
+++ b/Lib/pdb.py
@@ -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