From dd268c1b2bccd0813528c40ceefc4ee0dbe3b766 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Wed, 8 Aug 2012 11:48:53 +0200 Subject: [PATCH] improve error representation for missing factory definitions in recursive funcarg reconstruction --- _pytest/__init__.py | 2 +- _pytest/main.py | 3 ++- _pytest/python.py | 52 +++++++++++++++++++++++++++--------------- setup.py | 2 +- testing/test_python.py | 19 ++++++++++++++- 5 files changed, 56 insertions(+), 22 deletions(-) diff --git a/_pytest/__init__.py b/_pytest/__init__.py index 996169286..fba95d1c0 100644 --- a/_pytest/__init__.py +++ b/_pytest/__init__.py @@ -1,2 +1,2 @@ # -__version__ = '2.3.0.dev9' +__version__ = '2.3.0.dev10' diff --git a/_pytest/main.py b/_pytest/main.py index e94ab30d1..52bff5bc6 100644 --- a/_pytest/main.py +++ b/_pytest/main.py @@ -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: diff --git a/_pytest/python.py b/_pytest/python.py index 4155a8366..7e48023f5 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -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) diff --git a/setup.py b/setup.py index d9c0685fa..b7d70161f 100644 --- a/setup.py +++ b/setup.py @@ -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'], diff --git a/testing/test_python.py b/testing/test_python.py index 5f0d73f36..34cb871c1 100644 --- a/testing/test_python.py +++ b/testing/test_python.py @@ -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("""