* introduce pytest_pdb: plugin handling --pdb invocation

* killing some unused/unneccessary hooks

--HG--
branch : trunk
This commit is contained in:
holger krekel 2009-05-22 19:57:21 +02:00
parent def623e289
commit bcd9aed0b1
11 changed files with 186 additions and 197 deletions

View File

@ -1,77 +0,0 @@
import pdb, sys, linecache
class Pdb(pdb.Pdb):
def do_list(self, arg):
self.lastcmd = 'list'
last = None
if arg:
try:
x = eval(arg, {}, {})
if type(x) == type(()):
first, last = x
first = int(first)
last = int(last)
if last < first:
# Assume it's a count
last = first + last
else:
first = max(1, int(x) - 5)
except:
print '*** Error in argument:', repr(arg)
return
elif self.lineno is None:
first = max(1, self.curframe.f_lineno - 5)
else:
first = self.lineno + 1
if last is None:
last = first + 10
filename = self.curframe.f_code.co_filename
breaklist = self.get_file_breaks(filename)
try:
for lineno in range(first, last+1):
# start difference from normal do_line
line = self._getline(filename, lineno)
# end difference from normal do_line
if not line:
print '[EOF]'
break
else:
s = repr(lineno).rjust(3)
if len(s) < 4: s = s + ' '
if lineno in breaklist: s = s + 'B'
else: s = s + ' '
if lineno == self.curframe.f_lineno:
s = s + '->'
print s + '\t' + line,
self.lineno = lineno
except KeyboardInterrupt:
pass
do_l = do_list
def _getline(self, filename, lineno):
if hasattr(filename, "__source__"):
try:
return filename.__source__.lines[lineno - 1] + "\n"
except IndexError:
return None
return linecache.getline(filename, lineno)
def get_stack(self, f, t):
# Modified from bdb.py to be able to walk the stack beyond generators,
# which does not work in the normal pdb :-(
stack, i = pdb.Pdb.get_stack(self, f, t)
if f is None:
i = max(0, len(stack) - 1)
return stack, i
def post_mortem(t):
# modified from pdb.py for the new get_stack() implementation
p = Pdb()
p.reset()
p.interaction(None, t)
def set_trace():
# again, a copy of the version in pdb.py
Pdb().set_trace(sys._getframe().f_back)

View File

@ -62,10 +62,14 @@ class PluginHooks:
def pytest_collectreport(self, rep):
""" collector finished collecting. """
# XXX rename to item_collected()? meaning in distribution context?
def pytest_itemstart(self, item, node=None):
""" test item gets collected. """
# ------------------------------------------------------------------------------
# runtest related hooks
# ------------------------------------------------------------------------------
#
def pytest_itemrun(self, item, pdb=None):
""" run given test item and return test report. """
pytest_itemrun.firstresult = True
@ -75,21 +79,11 @@ class PluginHooks:
pytest_pyfunc_call.firstresult = True
def pytest_item_makereport(self, item, excinfo, when, outerr):
""" return ItemTestReport for the given test outcome. """
""" make ItemTestReport for the specified test outcome. """
pytest_item_makereport.firstresult = True
def pytest_itemstart(self, item, node=None):
""" test item gets collected. """
def pytest_itemtestreport(self, rep):
""" test has been run. """
# XXX pytest_runner reports
def pytest_item_runtest_finished(self, item, excinfo, outerr):
""" test has been run. """
def pytest_itemfixturereport(self, rep):
""" a report on running a fixture function. """
""" process item test report. """
# ------------------------------------------------------------------------------
# reporting hooks (invoked from pytest_terminal.py)

View File

@ -2,13 +2,12 @@
import py
def pytest_itemrun(item, pdb=None):
def pytest_itemrun(item):
from py.__.test.runner import basic_run_report, forked_run_report
if item.config.option.boxed:
runner = forked_run_report
report = forked_run_report(item)
else:
runner = basic_run_report
report = runner(item, pdb=pdb)
report = basic_run_report(item)
item.config.hook.pytest_itemtestreport(rep=report)
return True
@ -16,11 +15,6 @@ def pytest_item_makereport(item, excinfo, when, outerr):
from py.__.test import runner
return runner.ItemTestReport(item, excinfo, when, outerr)
def pytest_item_runtest_finished(item, excinfo, outerr):
from py.__.test import runner
rep = runner.ItemTestReport(item, excinfo, "execute", outerr)
item.config.hook.pytest_itemtestreport(rep=rep)
def pytest_pyfunc_call(pyfuncitem, args, kwargs):
pyfuncitem.obj(*args, **kwargs)
@ -57,7 +51,7 @@ def pytest_report_iteminfo(item):
return item.reportinfo()
def pytest_addoption(parser):
group = parser.addgroup("general", "test collection and failure interaction options")
group = parser.getgroup("general", "test collection and failure interaction options")
group._addoption('-v', '--verbose', action="count",
dest="verbose", default=0, help="increase verbosity."),
group._addoption('-x', '--exitfirst',
@ -75,9 +69,6 @@ def pytest_addoption(parser):
#group._addoption('--showskipsummary',
# action="store_true", dest="showskipsummary", default=False,
# help="always show summary of skipped tests")
group._addoption('--pdb',
action="store_true", dest="usepdb", default=False,
help="start pdb (the Python debugger) on errors.")
group._addoption('--tb', metavar="style",
action="store", dest="tbstyle", default='long',
type="choice", choices=['long', 'short', 'no'],
@ -89,9 +80,7 @@ def pytest_addoption(parser):
action="store_true", dest="boxed", default=False,
help="box each test run in a separate process")
group._addoption('-p', action="append", dest="plugin", default = [],
help=("load the specified plugin after command line parsing. "
"Example: '-p hello' will trigger 'import pytest_hello' "
"and instantiate 'HelloPlugin' from the module."))
help=("load the specified plugin after command line parsing. "))
group._addoption('-f', '--looponfail',
action="store_true", dest="looponfail", default=False,
help="run tests, re-run failing test set until all pass.")
@ -150,11 +139,6 @@ def fixoptions(config):
config.option.tx = ['popen'] * int(config.option.numprocesses)
if config.option.distload:
config.option.dist = "load"
if config.getvalue("usepdb"):
if config.getvalue("looponfail"):
raise config.Error("--pdb incompatible with --looponfail.")
if config.option.dist != "no":
raise config.Error("--pdb incomptaible with distributing tests.")
def loadplugins(config):
for name in config.getvalue("plugin"):
@ -241,9 +225,6 @@ class TestDistOptions:
assert testdir.tmpdir.join('x') in roots
def test_dist_options(testdir):
py.test.raises(Exception, "testdir.parseconfigure('--pdb', '--looponfail')")
py.test.raises(Exception, "testdir.parseconfigure('--pdb', '-n 3')")
py.test.raises(Exception, "testdir.parseconfigure('--pdb', '-d')")
config = testdir.parseconfigure("-n 2")
assert config.option.dist == "load"
assert config.option.tx == ['popen'] * 2

View File

@ -1,9 +1,155 @@
""" XXX should be used sometime. """
from py.__.test.custompdb import post_mortem
"""
interactive debugging with a PDB prompt.
"""
import py
import pdb, sys, linecache
from py.__.test.outcome import Skipped
def pytest_addoption(parser):
group = parser.getgroup("general")
group._addoption('--pdb',
action="store_true", dest="usepdb", default=False,
help="start pdb (the Python debugger) on errors.")
def pytest_configure(config):
if config.option.usepdb:
if config.getvalue("looponfail"):
raise config.Error("--pdb incompatible with --looponfail.")
if config.option.dist != "no":
raise config.Error("--pdb incomptaible with distributing tests.")
config.pluginmanager.register(PdbInvoke())
class PdbInvoke:
def pytest_item_makereport(self, item, excinfo, when, outerr):
if excinfo and not excinfo.errisinstance(Skipped):
tw = py.io.TerminalWriter()
repr = excinfo.getrepr()
repr.toterminal(tw)
post_mortem(excinfo._excinfo[2])
class Pdb(py.std.pdb.Pdb):
def do_list(self, arg):
self.lastcmd = 'list'
last = None
if arg:
try:
x = eval(arg, {}, {})
if type(x) == type(()):
first, last = x
first = int(first)
last = int(last)
if last < first:
# Assume it's a count
last = first + last
else:
first = max(1, int(x) - 5)
except:
print '*** Error in argument:', repr(arg)
return
elif self.lineno is None:
first = max(1, self.curframe.f_lineno - 5)
else:
first = self.lineno + 1
if last is None:
last = first + 10
filename = self.curframe.f_code.co_filename
breaklist = self.get_file_breaks(filename)
try:
for lineno in range(first, last+1):
# start difference from normal do_line
line = self._getline(filename, lineno)
# end difference from normal do_line
if not line:
print '[EOF]'
break
else:
s = repr(lineno).rjust(3)
if len(s) < 4: s = s + ' '
if lineno in breaklist: s = s + 'B'
else: s = s + ' '
if lineno == self.curframe.f_lineno:
s = s + '->'
print s + '\t' + line,
self.lineno = lineno
except KeyboardInterrupt:
pass
do_l = do_list
def _getline(self, filename, lineno):
if hasattr(filename, "__source__"):
try:
return filename.__source__.lines[lineno - 1] + "\n"
except IndexError:
return None
return linecache.getline(filename, lineno)
def get_stack(self, f, t):
# Modified from bdb.py to be able to walk the stack beyond generators,
# which does not work in the normal pdb :-(
stack, i = pdb.Pdb.get_stack(self, f, t)
if f is None:
i = max(0, len(stack) - 1)
return stack, i
def post_mortem(t):
# modified from pdb.py for the new get_stack() implementation
p = Pdb()
p.reset()
p.interaction(None, t)
def set_trace():
# again, a copy of the version in pdb.py
Pdb().set_trace(sys._getframe().f_back)
class TestPDB:
def pytest_funcarg__pdblist(self, request):
monkeypatch = request.getfuncargvalue("monkeypatch")
pdblist = []
def mypdb(*args):
pdblist.append(args)
monkeypatch.setitem(globals(), 'post_mortem', mypdb)
return pdblist
def test_incompatibility_messages(self, testdir):
Error = py.test.config.Error
py.test.raises(Error, "testdir.parseconfigure('--pdb', '--looponfail')")
py.test.raises(Error, "testdir.parseconfigure('--pdb', '-n 3')")
py.test.raises(Error, "testdir.parseconfigure('--pdb', '-d')")
def test_pdb_on_fail(self, testdir, pdblist):
rep = testdir.inline_runsource1('--pdb', """
def test_func():
assert 0
""")
assert rep.failed
assert len(pdblist) == 1
tb = py.code.Traceback(pdblist[0][0])
assert tb[-1].name == "test_func"
def test_pdb_on_skip(self, testdir, pdblist):
rep = testdir.inline_runsource1('--pdb', """
import py
def test_func():
py.test.skip("hello")
""")
assert rep.skipped
assert len(pdblist) == 0
def test_pdb_interaction(self, testdir):
p1 = testdir.makepyfile("""
def test_1():
i = 0
assert i == 1
""")
child = testdir.spawn_pytest("--pdb %s" % p1)
#child.expect(".*def test_1.*")
child.expect(".*i = 0.*")
child.expect("(Pdb)")
child.sendeof()
child.expect("1 failed")
if child.isalive():
child.wait()
def pytest_item_runtest_finished(item, excinfo, outerr):
if excinfo and item.config.option.usepdb:
tw = py.io.TerminalWriter()
repr = excinfo.getrepr()
repr.toterminal(tw)
post_mortem(excinfo._excinfo[2])

View File

@ -145,19 +145,29 @@ class TmpTestdir:
items = list(session.genitems(colitems))
return items, rec
def runitem(self, source, **runnerargs):
def runitem(self, source):
# used from runner functional tests
item = self.getitem(source)
# the test class where we are called from wants to provide the runner
testclassinstance = self.request.function.im_self
runner = testclassinstance.getrunner()
return runner(item, **runnerargs)
return runner(item)
def inline_runsource(self, source, *cmdlineargs):
p = self.makepyfile(source)
l = list(cmdlineargs) + [p]
return self.inline_run(*l)
def inline_runsource1(self, *args):
args = list(args)
source = args.pop()
p = self.makepyfile(source)
l = list(args) + [p]
reprec = self.inline_run(*l)
reports = reprec.getreports("pytest_itemtestreport")
assert len(reports) == 1, reports
return reports[0]
def inline_run(self, *args):
config = self.parseconfig(*args)
config.pluginmanager.do_configure(config)

View File

@ -140,8 +140,8 @@ class PluginManager(object):
config.hook.pytest_unconfigure(config=config)
config.pluginmanager.unregister(self)
def do_itemrun(self, item, pdb=None):
res = self.hook.pytest_itemrun(item=item, pdb=pdb)
def do_itemrun(self, item):
res = self.hook.pytest_itemrun(item=item)
if res is None:
raise ValueError("could not run %r" %(item,))

View File

@ -9,7 +9,6 @@
import py
from py.__.test.outcome import Skipped
from py.__.test.custompdb import post_mortem
class Call:
excinfo = None
@ -26,7 +25,7 @@ def runtest_with_deprecated_check(item):
if not item._deprecated_testexecution():
item.runtest()
def basic_run_report(item, pdb=None):
def basic_run_report(item):
""" return report about setting up and running a test item. """
setupstate = item.config._setupstate
capture = item.config._getcapture()
@ -38,13 +37,9 @@ def basic_run_report(item, pdb=None):
call = Call("teardown", lambda: setupstate.teardown_exact(item))
finally:
outerr = capture.reset()
testrep = item.config.hook.pytest_item_makereport(
item=item, excinfo=call.excinfo, when=call.when, outerr=outerr)
if pdb and testrep.failed:
tw = py.io.TerminalWriter()
testrep.toterminal(tw)
pdb(call.excinfo)
return testrep
return item.config.hook.pytest_item_makereport(
item=item, excinfo=call.excinfo,
when=call.when, outerr=outerr)
def basic_collect_report(collector):
call = collector.config.guardedcall(
@ -55,7 +50,7 @@ def basic_collect_report(collector):
result = call.result
return CollectReport(collector, result, call.excinfo, call.outerr)
def forked_run_report(item, pdb=None):
def forked_run_report(item):
EXITSTATUS_TESTEXIT = 4
from py.__.test.dist.mypickle import ImmutablePickler
ipickle = ImmutablePickler(uneven=0)

View File

@ -112,8 +112,7 @@ class Session(object):
if self.shouldstop:
break
if not self.config.option.collectonly:
self.runtest(item)
item.config.pluginmanager.do_itemrun(item)
self.config._setupstate.teardown_all()
except KeyboardInterrupt:
captured_excinfo = py.code.ExceptionInfo()
@ -126,11 +125,3 @@ class Session(object):
exitstatus = outcome.EXIT_TESTSFAILED
self.sessionfinishes(exitstatus=exitstatus, excinfo=captured_excinfo)
return exitstatus
def runpdb(self, excinfo):
from py.__.test.custompdb import post_mortem
post_mortem(excinfo._excinfo[2])
def runtest(self, item):
pdb = self.config.option.usepdb and self.runpdb or None
item.config.pluginmanager.do_itemrun(item, pdb=pdb)

View File

@ -441,21 +441,6 @@ class TestDistribution:
class TestInteractive:
def test_pdb_interaction(self, testdir):
p1 = testdir.makepyfile("""
def test_1():
i = 0
assert i == 1
""")
child = testdir.spawn_pytest("--pdb %s" % p1)
#child.expect(".*def test_1.*")
child.expect(".*i = 0.*")
child.expect("(Pdb)")
child.sendeof()
child.expect("1 failed")
if child.isalive():
child.wait()
def test_simple_looponfail_interaction(self, testdir):
p1 = testdir.makepyfile("""
def test_1():

View File

@ -222,26 +222,6 @@ class TestExecutionNonForked(BaseFunctionalTests):
else:
py.test.fail("did not raise")
def test_pdb_on_fail(self, testdir):
l = []
rep = testdir.runitem("""
def test_func():
assert 0
""", pdb=l.append)
assert rep.failed
assert rep.when == "runtest"
assert len(l) == 1
def test_pdb_on_skip(self, testdir):
l = []
rep = testdir.runitem("""
import py
def test_func():
py.test.skip("hello")
""", pdb=l.append)
assert len(l) == 0
assert rep.skipped
class TestExecutionForked(BaseFunctionalTests):
def getrunner(self):
if not hasattr(py.std.os, 'fork'):

View File

@ -135,22 +135,6 @@ class SessionTests:
assert reports[0].skipped
class TestNewSession(SessionTests):
def test_pdb_run(self, testdir, monkeypatch):
import py.__.test.custompdb
tfile = testdir.makepyfile("""
def test_usepdb():
assert 0
""")
l = []
def mypdb(*args):
l.append(args)
monkeypatch.setattr(py.__.test.custompdb, 'post_mortem', mypdb)
reprec = testdir.inline_run('--pdb', tfile)
rep = reprec.matchreport("test_usepdb")
assert rep.failed
assert len(l) == 1
tb = py.code.Traceback(l[0][0])
assert tb[-1].name == "test_usepdb"
def test_order_of_execution(self, testdir):
reprec = testdir.inline_runsource("""