make the default non-error pass simpler and faster, refine error reporting by presenting "fixture" tracebacks
This commit is contained in:
parent
bb07ba7807
commit
d1a3f5c3a6
|
@ -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:
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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*",
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue