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

View File

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

View File

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

View File

@ -2319,7 +2319,7 @@ class TestTestContextScopeAccess:
reprec = testdir.inline_run() reprec = testdir.inline_run()
reprec.assertoutcome(passed=1) reprec.assertoutcome(passed=1)
def test_resource(self, testdir, scope, ok, error): def test_funcarg(self, testdir, scope, ok, error):
testdir.makepyfile(""" testdir.makepyfile("""
import pytest import pytest
@pytest.factory(scope=%r) @pytest.factory(scope=%r)
@ -2337,6 +2337,23 @@ class TestTestContextScopeAccess:
reprec = testdir.inline_run() reprec = testdir.inline_run()
reprec.assertoutcome(passed=1) 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: class TestTestContextVarious:
def test_newstyle_no_request(self, testdir): def test_newstyle_no_request(self, testdir):
testdir.makepyfile(""" testdir.makepyfile("""