improve error representation for missing factory definitions

in recursive funcarg reconstruction
This commit is contained in:
holger krekel 2012-08-08 11:48:53 +02:00
parent 172505f703
commit dd268c1b2b
5 changed files with 56 additions and 22 deletions

View File

@ -1,2 +1,2 @@
#
__version__ = '2.3.0.dev9'
__version__ = '2.3.0.dev10'

View File

@ -285,6 +285,7 @@ class Node(object):
fm = self.session.funcargmanager
if excinfo.errisinstance(fm.FuncargLookupError):
function = excinfo.value.function
factblines = excinfo.value.factblines
if function is not None:
fspath, lineno = getfslineno(function)
lines, _ = inspect.getsourcelines(function)
@ -292,7 +293,7 @@ class Node(object):
if line.strip().startswith('def'):
return fm.FuncargLookupErrorRepr(fspath,
lineno, lines[:i+1],
str(excinfo.value.msg))
str(excinfo.value.msg), factblines)
if self.config.option.fulltrace:
style="long"
else:

View File

@ -935,17 +935,23 @@ class FuncargRequest:
def _getfaclist(self, argname):
facdeflist = self._name2factory.get(argname, None)
if facdeflist is None:
if self._factorystack:
function = self._factorystack[-1].func
getfactb = lambda: self._factorystack[:-1]
else:
function = self.function
getfactb = None
facdeflist = self.funcargmanager.getfactorylist(
argname, self.parentid, self.function)
argname, self.parentid, function, getfactb)
self._name2factory[argname] = facdeflist
elif not facdeflist:
self.funcargmanager._raiselookupfailed(argname, self.function,
self.parentid)
return facdeflist
def raiseerror(self, msg):
""" raise a FuncargLookupError with the given message. """
raise self.funcargmanager.FuncargLookupError(self.function, msg)
#def raiseerror(self, msg):
# """ raise a FuncargLookupError with the given message. """
# raise self.funcargmanager.FuncargLookupError(self.function, msg)
@property
def function(self):
@ -1102,14 +1108,7 @@ class FuncargRequest:
__tracebackhide__ = True
if scopemismatch(self.scope, scope):
# try to report something helpful
lines = []
for factorydef in self._factorystack:
factory = factorydef.func
fs, lineno = getfslineno(factory)
p = self._pyfuncitem.session.fspath.bestrelpath(fs)
args = inspect.formatargspec(*inspect.getargspec(factory))
lines.append("%s:%d\n def %s%s" %(
p, lineno, factory.__name__, args))
lines = self._factorytraceback()
raise ScopeMismatchError("You tried to access the %r scoped "
"funcarg %r with a %r scoped request object, "
"involved factories\n%s" %(
@ -1129,6 +1128,18 @@ class FuncargRequest:
self._funcargs[argname] = val
return val
def _factorytraceback(self):
lines = []
for factorydef in self._factorystack:
factory = factorydef.func
fs, lineno = getfslineno(factory)
p = self._pyfuncitem.session.fspath.bestrelpath(fs)
args = inspect.formatargspec(*inspect.getargspec(factory))
lines.append("%s:%d: def %s%s" %(
p, lineno, factory.__name__, args))
return lines
def _getscopeitem(self, scope):
if scope == "function":
return self._pyfuncitem
@ -1178,19 +1189,23 @@ def slice_kwargs(names, kwargs):
class FuncargLookupError(LookupError):
""" could not find a factory. """
def __init__(self, function, msg):
def __init__(self, function, msg, factblines=None):
self.function = function
self.msg = msg
self.factblines = factblines
class FuncargLookupErrorRepr(TerminalRepr):
def __init__(self, filename, firstlineno, deflines, errorstring):
def __init__(self, filename, firstlineno, deflines, errorstring, factblines):
self.deflines = deflines
self.errorstring = errorstring
self.filename = filename
self.firstlineno = firstlineno
self.factblines = factblines
def toterminal(self, tw):
tw.line()
for line in self.factblines or []:
tw.line(line)
for line in self.deflines:
tw.line(" " + line.strip())
for line in self.errorstring.split("\n"):
@ -1326,12 +1341,12 @@ class FuncargManager:
return l, allargnames
def getfactorylist(self, argname, nodeid, function, raising=True):
def getfactorylist(self, argname, nodeid, function, getfactb=None, raising=True):
try:
factorydeflist = self.arg2facspec[argname]
except KeyError:
if raising:
self._raiselookupfailed(argname, function, nodeid)
self._raiselookupfailed(argname, function, nodeid, getfactb)
else:
return self._matchfactories(factorydeflist, nodeid)
@ -1343,7 +1358,7 @@ class FuncargManager:
l.append(factorydef)
return l
def _raiselookupfailed(self, argname, function, nodeid):
def _raiselookupfailed(self, argname, function, nodeid, getfactb=None):
available = []
for name, facdef in self.arg2facspec.items():
faclist = self._matchfactories(facdef, nodeid)
@ -1352,7 +1367,8 @@ class FuncargManager:
msg = "LookupError: no factory found for argument %r" % (argname,)
msg += "\n available funcargs: %s" %(", ".join(available),)
msg += "\n use 'py.test --funcargs [testpath]' for help on them."
raise FuncargLookupError(function, msg)
lines = getfactb and getfactb() or []
raise FuncargLookupError(function, msg, lines)
def ensure_setupcalls(self, request):
setuplist, allnames = self.getsetuplist(request._pyfuncitem.nodeid)

View File

@ -24,7 +24,7 @@ def main():
name='pytest',
description='py.test: simple powerful testing with Python',
long_description = long_description,
version='2.3.0.dev9',
version='2.3.0.dev10',
url='http://pytest.org',
license='MIT license',
platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'],

View File

@ -2319,7 +2319,7 @@ class TestTestContextScopeAccess:
reprec = testdir.inline_run()
reprec.assertoutcome(passed=1)
def test_resource(self, testdir, scope, ok, error):
def test_funcarg(self, testdir, scope, ok, error):
testdir.makepyfile("""
import pytest
@pytest.factory(scope=%r)
@ -2337,6 +2337,23 @@ class TestTestContextScopeAccess:
reprec = testdir.inline_run()
reprec.assertoutcome(passed=1)
def test_illdefined_factory(testdir):
testdir.makepyfile("""
import pytest
@pytest.factory()
def gen(request):
return 1
def test_something(gen):
pass
""")
result = testdir.runpytest()
assert result.ret != 0
result.stdout.fnmatch_lines([
"*def gen(request):*",
"*no factory*request*",
])
class TestTestContextVarious:
def test_newstyle_no_request(self, testdir):
testdir.makepyfile("""