Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Add p/pp and command line tests
  • Loading branch information
gaogaotiantian committed May 13, 2023
commit c97f7284a308f609d7f5164722f89e648014c8ba
67 changes: 61 additions & 6 deletions Lib/pdbx.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
import io
import linecache
import os
import pprint
import runpy
import sys
import traceback

from bdbx import Bdbx, BdbxQuit
from types import CodeType
Expand Down Expand Up @@ -115,10 +117,7 @@ def _run_target(self, target:_ExecuteTarget):
__main__.__dict__.clear()
__main__.__dict__.update(globals)
self.break_code(target.code)
try:
exec(target.code, globals, locals)
except BdbxQuit:
pass
exec(target.code, globals, locals)
__main__.__dict__.clear()
__main__.__dict__.update(main_dict)

Expand All @@ -130,6 +129,7 @@ def _default_file(self):
filename = self._main_pyfile
return filename

# ======================= Formatting Helpers ============================
def _format_stack_entry(self, frame, lineno, stack_prefix="> ", code_prefix="-> "):
"""Return a string with information about a stack entry.

Expand All @@ -153,6 +153,9 @@ def _format_stack_entry(self, frame, lineno, stack_prefix="> ", code_prefix="->
code = ""
return f"{stack_prefix}{func_name}() @ {filename}:{lineno}{code}"

def _format_exception(exc):
return traceback.format_exception_only(exc)[-1].strip()

def _print_stack_entry(self,
frame,
line_number=None,
Expand Down Expand Up @@ -286,6 +289,14 @@ def _parse_breakpoint_arg(self, arg):

return filename, line_number, function, condition

def _getval(self, arg):
try:
frame = self.get_current_frame()
return eval(arg, frame.f_globals, frame.f_locals)
except:
self.error(self._format_exception(sys.exception()))
raise

# ======================================================================
# The following methods are called by the cmd.Cmd base class
# All the commands are in alphabetic order
Expand Down Expand Up @@ -339,6 +350,7 @@ def do_down(self, arg):
do_d = do_down

def do_EOF(self, arg):
self.message('')
raise BdbxQuit("quit")

def do_next(self, arg):
Expand All @@ -347,6 +359,32 @@ def do_next(self, arg):

do_n = do_next

def do_p(self, arg):
"""p expression

Print the value of the expression.
"""
try:
val = self._getval(arg)
self.message(repr(val))
except:
# error message is printed
pass
return False

def do_pp(self, arg):
"""pp expression

Pretty-print the value of the expression.
"""
try:
val = self._getval(arg)
self.message(pprint.pformat(val))
except:
# error message is printed
pass
return False

def do_quit(self, arg):
raise BdbxQuit("quit")

Expand Down Expand Up @@ -406,6 +444,14 @@ def break_here():
pdb = Pdbx()
pdb.break_here(sys._getframe().f_back)

_usage = """\
usage: pdbx.py [-m module | pyfile] [arg] ...

Debug the Python program given by pyfile. Alternatively,
an executable module or package to debug can be specified using
the -m switch.
"""


def main():
parser = argparse.ArgumentParser()
Expand All @@ -422,10 +468,19 @@ def main():
sys.argv[:] = commands
else:
# Show help message
parser.print_help()
print(_usage)
sys.exit(2)

pdbx = Pdbx()
pdbx._run_target(target)
while True:
try:
pdbx._run_target(target)
except SystemExit as e:
# In most cases SystemExit does not warrant a post-mortem session.
print("The program exited via sys.exit(). Exit status:", end=' ')
print(e)
except BdbxQuit:
break


if __name__ == '__main__':
Expand Down
173 changes: 172 additions & 1 deletion Lib/test/test_pdbx.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
from pdbx import Pdbx
import doctest
import os
import pdbx
import subprocess
import sys
import textwrap
import unittest

from test import support
from test.support import os_helper
from test.test_doctest import _FakeInput

class PdbxTestInput(object):
Expand Down Expand Up @@ -208,3 +215,167 @@ def load_tests(loader, tests, pattern):
from test import test_pdbx
tests.addTest(doctest.DocTestSuite(test_pdbx))
return tests


@support.requires_subprocess()
class PdbxTestCase(unittest.TestCase):
def tearDown(self):
os_helper.unlink(os_helper.TESTFN)

@unittest.skipIf(sys.flags.safe_path,
'PYTHONSAFEPATH changes default sys.path')
def _run_pdbx(self, pdbx_args, commands, expected_returncode=0):
self.addCleanup(os_helper.rmtree, '__pycache__')
cmd = [sys.executable, '-m', 'pdbx'] + pdbx_args
with subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stdin=subprocess.PIPE,
stderr=subprocess.STDOUT,
env = {**os.environ, 'PYTHONIOENCODING': 'utf-8'}
) as proc:
stdout, stderr = proc.communicate(str.encode(commands))
stdout = stdout and bytes.decode(stdout)
stderr = stderr and bytes.decode(stderr)
self.assertEqual(
proc.returncode,
expected_returncode,
f"Unexpected return code\nstdout: {stdout}\nstderr: {stderr}"
)
return stdout, stderr

def run_pdbx_script(self, script, commands, expected_returncode=0):
"""Run 'script' lines with pdbx and the pdbx 'commands'."""
filename = 'main.py'
with open(filename, 'w') as f:
f.write(textwrap.dedent(script))
self.addCleanup(os_helper.unlink, filename)
return self._run_pdbx([filename], commands, expected_returncode)

def run_pdbx_module(self, script, commands):
"""Runs the script code as part of a module"""
self.module_name = 't_main'
os_helper.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(os_helper.rmtree, self.module_name)
return self._run_pdbx(['-m', self.module_name], commands)

def test_run_module(self):
script = """print("SUCCESS")"""
commands = """
continue
quit
"""
stdout, stderr = self.run_pdbx_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_pdbx_module(script, commands)
self.assertTrue(any("SUCCESS" in l for l in stdout.splitlines()), stdout)

def test_run_pdbx_with_pdbx(self):
commands = """
c
quit
"""
stdout, stderr = self._run_pdbx(["-m", "pdbx"], commands)
self.assertIn(
pdbx._usage,
stdout.replace('\r', '') # remove \r for windows
)

def test_module_without_a_main(self):
module_name = 't_main'
os_helper.rmtree(module_name)
init_file = module_name + '/__init__.py'
os.mkdir(module_name)
with open(init_file, 'w'):
pass
self.addCleanup(os_helper.rmtree, module_name)
stdout, stderr = self._run_pdbx(
['-m', module_name], "", expected_returncode=1
)
self.assertIn("ImportError: No module named t_main.__main__",
stdout.splitlines())

def test_package_without_a_main(self):
pkg_name = 't_pkg'
module_name = 't_main'
os_helper.rmtree(pkg_name)
modpath = pkg_name + '/' + module_name
os.makedirs(modpath)
with open(modpath + '/__init__.py', 'w'):
pass
self.addCleanup(os_helper.rmtree, pkg_name)
stdout, stderr = self._run_pdbx(
['-m', modpath.replace('/', '.')], "", expected_returncode=1
)
self.assertIn(
"'t_pkg.t_main' is a package and cannot be directly executed",
stdout)

def test_blocks_at_first_code_line(self):
script = """
#This is a comment, on line 2

print("SUCCESS")
"""
commands = """
quit
"""
stdout, stderr = self.run_pdbx_module(script, commands)
self.assertTrue(any("__main__.py:4"
in l for l in stdout.splitlines()), stdout)
self.assertTrue(any("<module>"
in l for l in stdout.splitlines()), stdout)

def test_relative_imports(self):
self.module_name = 't_main'
os_helper.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(os_helper.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_pdbx(['-m', self.module_name], commands)
self.assertTrue(any("VAR from module" in l for l in stdout.splitlines()), stdout)
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()))