* introduce pytest_pdb: plugin handling --pdb invocation
* killing some unused/unneccessary hooks --HG-- branch : trunk
This commit is contained in:
parent
def623e289
commit
bcd9aed0b1
|
@ -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)
|
||||
|
||||
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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])
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,))
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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():
|
||||
|
|
|
@ -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'):
|
||||
|
|
|
@ -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("""
|
||||
|
|
Loading…
Reference in New Issue