make the default non-error pass simpler and faster, refine error reporting by presenting "fixture" tracebacks

This commit is contained in:
holger krekel 2012-10-05 14:24:45 +02:00
parent bb07ba7807
commit d1a3f5c3a6
3 changed files with 59 additions and 62 deletions

View File

@ -310,16 +310,7 @@ class Node(object):
def _repr_failure_py(self, excinfo, style=None): def _repr_failure_py(self, excinfo, style=None):
fm = self.session._fixturemanager fm = self.session._fixturemanager
if excinfo.errisinstance(fm.FixtureLookupError): if excinfo.errisinstance(fm.FixtureLookupError):
function = excinfo.value.function return excinfo.value.formatrepr()
factblines = excinfo.value.factblines
if function is not None:
fspath, lineno = getfslineno(function)
lines, _ = inspect.getsourcelines(function)
for i, line in enumerate(lines):
if line.strip().startswith('def'):
return fm.FixtureLookupErrorRepr(fspath,
lineno, lines[:i+1],
str(excinfo.value.msg), factblines)
if self.config.option.fulltrace: if self.config.option.fulltrace:
style="long" style="long"
else: else:

View File

@ -993,20 +993,12 @@ class FixtureRequest(FuncargnamesCompatAttr):
def _getfixturedeflist(self, argname): def _getfixturedeflist(self, argname):
fixturedeflist = self._arg2fixturedeflist.get(argname, None) fixturedeflist = self._arg2fixturedeflist.get(argname, None)
getfixturetb = None
function = None
if fixturedeflist is None: if fixturedeflist is None:
if self._fixturestack:
function = self._fixturestack[-1].func
getfixturetb = lambda: self._fixturestack[:-1]
else:
function = self.function
fixturedeflist = self._fixturemanager.getfixturedeflist( fixturedeflist = self._fixturemanager.getfixturedeflist(
argname, self._parentid) argname, self._parentid)
self._arg2fixturedeflist[argname] = fixturedeflist self._arg2fixturedeflist[argname] = fixturedeflist
if not fixturedeflist: if not fixturedeflist:
self._fixturemanager._raiselookupfailed(argname, function, raise FixtureLookupError(argname, self)
self._parentid, getfixturetb)
return fixturedeflist return fixturedeflist
@property @property
@ -1085,8 +1077,7 @@ class FixtureRequest(FuncargnamesCompatAttr):
def raiseerror(self, msg): def raiseerror(self, msg):
""" raise a FixtureLookupError with the given message. """ """ raise a FixtureLookupError with the given message. """
raise self._fixturemanager.FixtureLookupError(self.function, msg) raise self._fixturemanager.FixtureLookupError(None, self, msg)
def _fillfixtures(self): def _fillfixtures(self):
item = self._pyfuncitem item = self._pyfuncitem
@ -1250,32 +1241,58 @@ def slice_kwargs(names, kwargs):
return new_kwargs return new_kwargs
class FixtureLookupError(LookupError): class FixtureLookupError(LookupError):
""" could not find a factory. """ """ could not return a requested Fixture (missing or invalid). """
def __init__(self, function, msg, factblines=None): def __init__(self, argname, request, msg=None):
self.function = function self.argname = argname
self.request = request
self.fixturestack = list(request._fixturestack)
self.msg = msg self.msg = msg
self.factblines = factblines
def formatrepr(self):
tblines = []
addline = tblines.append
stack = [self.request._pyfuncitem.obj]
stack.extend(map(lambda x: x.func, self.fixturestack))
msg = self.msg
if msg is not None:
stack = stack[:-1] # the last fixture raise an error, let's present
# it at the requesting side
for function in stack:
fspath, lineno = getfslineno(function)
lines, _ = inspect.getsourcelines(function)
addline("file %s, line %s" % (fspath, lineno+1))
for i, line in enumerate(lines):
line = line.rstrip()
addline(" " + line)
if line.lstrip().startswith('def'):
break
if msg is None:
fm = self.request._fixturemanager
nodeid = self.request._parentid
available = []
for name, fixturedef in fm.arg2fixturedeflist.items():
faclist = list(fm._matchfactories(fixturedef, self.request._parentid))
if faclist:
available.append(name)
msg = "fixture %r not found" % (self.argname,)
msg += "\n available fixtures: %s" %(", ".join(available),)
msg += "\n use 'py.test --fixtures [testpath]' for help on them."
return FixtureLookupErrorRepr(fspath, lineno, tblines, msg, self.argname)
class FixtureLookupErrorRepr(TerminalRepr): class FixtureLookupErrorRepr(TerminalRepr):
def __init__(self, filename, firstlineno, deflines, errorstring, factblines): def __init__(self, filename, firstlineno, tblines, errorstring, argname):
self.deflines = deflines self.tblines = tblines
self.errorstring = errorstring self.errorstring = errorstring
self.filename = filename self.filename = filename
self.firstlineno = firstlineno self.firstlineno = firstlineno
self.factblines = factblines self.argname = argname
def toterminal(self, tw): def toterminal(self, tw):
tw.line() #tw.line("FixtureLookupError: %s" %(self.argname), red=True)
if self.factblines: for tbline in self.tblines:
tw.line(' dependency of:') tw.line(tbline.rstrip())
for fixturedef in self.factblines:
tw.line(' %s in %s' % (
fixturedef.argname,
fixturedef.baseid,
))
tw.line()
for line in self.deflines:
tw.line(" " + line.strip())
for line in self.errorstring.split("\n"): for line in self.errorstring.split("\n"):
tw.line(" " + line.strip(), red=True) tw.line(" " + line.strip(), red=True)
tw.line() tw.line()
@ -1436,18 +1453,6 @@ class FixtureManager:
if nodeid.startswith(fixturedef.baseid): if nodeid.startswith(fixturedef.baseid):
yield fixturedef yield fixturedef
def _raiselookupfailed(self, argname, function, nodeid, getfixturetb=None):
available = []
for name, fixturedef in self.arg2fixturedeflist.items():
faclist = list(self._matchfactories(fixturedef, nodeid))
if faclist:
available.append(name)
msg = "LookupError: no factory found for argument %r" % (argname,)
msg += "\n available funcargs: %s" %(", ".join(available),)
msg += "\n use 'py.test --fixtures [testpath]' for help on them."
lines = getfixturetb and getfixturetb() or []
raise FixtureLookupError(function, msg, lines)
def addargfinalizer(self, finalizer, argname): def addargfinalizer(self, finalizer, argname):
l = self._arg2finish.setdefault(argname, []) l = self._arg2finish.setdefault(argname, [])
l.append(finalizer) l.append(finalizer)

View File

@ -559,7 +559,7 @@ class TestFillFixtures:
assert result.ret != 0 assert result.ret != 0
result.stdout.fnmatch_lines([ result.stdout.fnmatch_lines([
"*def test_func(some)*", "*def test_func(some)*",
"*LookupError*", "*fixture*some*not found*",
"*xyzsomething*", "*xyzsomething*",
]) ])
@ -1416,8 +1416,8 @@ def test_funcarg_lookup_error(testdir):
result.stdout.fnmatch_lines([ result.stdout.fnmatch_lines([
"*ERROR*test_lookup_error*", "*ERROR*test_lookup_error*",
"*def test_lookup_error(unknown):*", "*def test_lookup_error(unknown):*",
"*LookupError: no factory found*unknown*", "*fixture*unknown*not found*",
"*available funcargs*", "*available fixtures*",
"*1 error*", "*1 error*",
]) ])
assert "INTERNAL" not in result.stdout.str() assert "INTERNAL" not in result.stdout.str()
@ -1750,12 +1750,13 @@ class TestFixtureFactory:
pass pass
""") """)
result = testdir.runpytest() result = testdir.runpytest()
result.stdout.fnmatch_lines([ result.stdout.fnmatch_lines("""
"*dependency of:*", *pytest.fixture()*
"*call_fail*", *def call_fail(fail)*
"*def fail(*", *pytest.fixture()*
"*LookupError: no factory found for argument 'missing'", *def fail*
]) *fixture*'missing'*not found*
""")
def test_factory_setup_as_classes(self, testdir): def test_factory_setup_as_classes(self, testdir):
testdir.makepyfile(""" testdir.makepyfile("""
@ -2585,7 +2586,7 @@ class TestErrors:
assert result.ret != 0 assert result.ret != 0
result.stdout.fnmatch_lines([ result.stdout.fnmatch_lines([
"*def gen(qwe123):*", "*def gen(qwe123):*",
"*no factory*qwe123*", "*fixture*qwe123*not found*",
"*1 error*", "*1 error*",
]) ])
@ -2602,7 +2603,7 @@ class TestErrors:
assert result.ret != 0 assert result.ret != 0
result.stdout.fnmatch_lines([ result.stdout.fnmatch_lines([
"*def gen(qwe123):*", "*def gen(qwe123):*",
"*no factory*qwe123*", "*fixture*qwe123*not found*",
"*1 error*", "*1 error*",
]) ])