improve error representation for missing factory definitions
in recursive funcarg reconstruction
This commit is contained in:
parent
172505f703
commit
dd268c1b2b
|
@ -1,2 +1,2 @@
|
||||||
#
|
#
|
||||||
__version__ = '2.3.0.dev9'
|
__version__ = '2.3.0.dev10'
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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)
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -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'],
|
||||||
|
|
|
@ -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("""
|
||||||
|
|
Loading…
Reference in New Issue