parent
b552f6eb46
commit
37976be529
|
@ -18,8 +18,8 @@ Changes between 1.0.0 and 1.0.1
|
|||
|
||||
* terser reporting of collection error tracebacks
|
||||
|
||||
* streamlined internal plugin arch code, renamed of internal methods
|
||||
and argnames (related to py/_com.py multicall/plugin)
|
||||
* simplified multicall mechanism and plugin architecture,
|
||||
renamed some internal methods and argnames
|
||||
|
||||
Changes between 1.0.0b9 and 1.0.0
|
||||
=====================================
|
||||
|
|
|
@ -90,13 +90,13 @@ Available py.test hooks
|
|||
====================================
|
||||
|
||||
py.test calls hooks functions to implement its `test collection`_, running and
|
||||
reporting process. Upon loading of a plugin py.test performs
|
||||
strict checking on contained hook functions. Function and argument names
|
||||
need to match exactly one of `hook definition specification`_. It thus
|
||||
provides useful error reporting on mistyped hook or argument names
|
||||
and minimizes version incompatibilites. Below you find some introductory
|
||||
information on particular hooks. It's sensible to look at existing
|
||||
plugins so see example usages and start off with your own plugin.
|
||||
reporting process. When py.test loads a plugin it validates that all hook functions
|
||||
conform to the `hook definition specification`_. The hook function name and its
|
||||
argument names need to match exactly but it is allowed for an implementation
|
||||
to accept *less* parameters. You'll get useful errors on mistyped hook or
|
||||
argument names. Read on for some introductory information on particular
|
||||
hooks. It's sensible to look at existing plugins so see example usages
|
||||
and start off with your own plugin.
|
||||
|
||||
.. _`hook definition specification`: plugin/hookspec.html
|
||||
|
||||
|
|
36
py/_com.py
36
py/_com.py
|
@ -9,7 +9,8 @@ class MultiCall:
|
|||
|
||||
def __init__(self, methods, kwargs, firstresult=False):
|
||||
self.methods = methods[:]
|
||||
self.kwargs = kwargs
|
||||
self.kwargs = kwargs.copy()
|
||||
self.kwargs['__multicall__'] = self
|
||||
self.results = []
|
||||
self.firstresult = firstresult
|
||||
|
||||
|
@ -20,28 +21,30 @@ class MultiCall:
|
|||
def execute(self):
|
||||
while self.methods:
|
||||
method = self.methods.pop()
|
||||
res = self._call1(method)
|
||||
kwargs = self.getkwargs(method)
|
||||
res = method(**kwargs)
|
||||
if res is not None:
|
||||
self.results.append(res)
|
||||
if self.firstresult:
|
||||
break
|
||||
return res
|
||||
if not self.firstresult:
|
||||
return self.results
|
||||
if self.results:
|
||||
return self.results[-1]
|
||||
|
||||
def _call1(self, method):
|
||||
kwargs = self.kwargs
|
||||
if '__call__' in varnames(method):
|
||||
kwargs = kwargs.copy()
|
||||
kwargs['__call__'] = self
|
||||
return method(**kwargs)
|
||||
def getkwargs(self, method):
|
||||
kwargs = {}
|
||||
for argname in varnames(method):
|
||||
try:
|
||||
kwargs[argname] = self.kwargs[argname]
|
||||
except KeyError:
|
||||
pass # might be optional param
|
||||
return kwargs
|
||||
|
||||
def varnames(rawcode):
|
||||
ismethod = hasattr(rawcode, 'im_self')
|
||||
rawcode = getattr(rawcode, 'im_func', rawcode)
|
||||
rawcode = getattr(rawcode, 'func_code', rawcode)
|
||||
try:
|
||||
return rawcode.co_varnames
|
||||
return rawcode.co_varnames[ismethod:]
|
||||
except AttributeError:
|
||||
return ()
|
||||
|
||||
|
@ -101,9 +104,6 @@ class HookRelay:
|
|||
def _performcall(self, name, multicall):
|
||||
return multicall.execute()
|
||||
|
||||
def __repr__(self):
|
||||
return "<HookRelay %r %r>" %(self._hookspecs, self._registry)
|
||||
|
||||
class HookCaller:
|
||||
def __init__(self, hookrelay, name, firstresult, extralookup=()):
|
||||
self.hookrelay = hookrelay
|
||||
|
@ -112,12 +112,10 @@ class HookCaller:
|
|||
self.extralookup = extralookup and [extralookup] or ()
|
||||
|
||||
def __repr__(self):
|
||||
return "<HookCaller %r firstresult=%s %s>" %(
|
||||
self.name, self.firstresult, self.hookrelay)
|
||||
return "<HookCaller %r>" %(self.name,)
|
||||
|
||||
def __call__(self, **kwargs):
|
||||
methods = self.hookrelay._getmethods(self.name,
|
||||
extralookup=self.extralookup)
|
||||
methods = self.hookrelay._getmethods(self.name, self.extralookup)
|
||||
mc = MultiCall(methods, kwargs, firstresult=self.firstresult)
|
||||
return self.hookrelay._performcall(self.name, mc)
|
||||
|
||||
|
|
|
@ -1,8 +1,18 @@
|
|||
|
||||
import py
|
||||
import os
|
||||
from py._com import Registry, MultiCall, HookRelay
|
||||
from py.__._com import Registry, MultiCall, HookRelay, varnames
|
||||
|
||||
def test_varnames():
|
||||
def f(x):
|
||||
pass
|
||||
class A:
|
||||
def f(self, y):
|
||||
pass
|
||||
assert varnames(f) == ("x",)
|
||||
assert varnames(A.f) == ('y',)
|
||||
assert varnames(A().f) == ('y',)
|
||||
|
||||
class TestMultiCall:
|
||||
def test_uses_copy_of_methods(self):
|
||||
l = [lambda: 42]
|
||||
|
@ -14,15 +24,15 @@ class TestMultiCall:
|
|||
|
||||
def test_call_passing(self):
|
||||
class P1:
|
||||
def m(self, __call__, x):
|
||||
assert len(__call__.results) == 1
|
||||
assert not __call__.methods
|
||||
def m(self, __multicall__, x):
|
||||
assert len(__multicall__.results) == 1
|
||||
assert not __multicall__.methods
|
||||
return 17
|
||||
|
||||
class P2:
|
||||
def m(self, __call__, x):
|
||||
assert __call__.results == []
|
||||
assert __call__.methods
|
||||
def m(self, __multicall__, x):
|
||||
assert __multicall__.results == []
|
||||
assert __multicall__.methods
|
||||
return 23
|
||||
|
||||
p1 = P1()
|
||||
|
@ -37,24 +47,23 @@ class TestMultiCall:
|
|||
def test_keyword_args(self):
|
||||
def f(x):
|
||||
return x + 1
|
||||
multicall = MultiCall([f], dict(x=23))
|
||||
class A:
|
||||
def f(self, x, y):
|
||||
return x + y
|
||||
multicall = MultiCall([f, A().f], dict(x=23, y=24))
|
||||
assert "'x': 23" in repr(multicall)
|
||||
assert "'y': 24" in repr(multicall)
|
||||
reslist = multicall.execute()
|
||||
assert reslist == [24]
|
||||
assert "1 results" in repr(multicall)
|
||||
assert reslist == [24+23, 24]
|
||||
assert "2 results" in repr(multicall)
|
||||
|
||||
def test_keywords_call_error(self):
|
||||
multicall = MultiCall([lambda x: x], {})
|
||||
py.test.raises(TypeError, "multicall.execute()")
|
||||
|
||||
def test_optionalcallarg(self):
|
||||
class P1:
|
||||
def m(self, x):
|
||||
return x
|
||||
call = MultiCall([P1().m], dict(x=23))
|
||||
assert "23" in repr(call)
|
||||
assert call.execute() == [23]
|
||||
call = MultiCall([P1().m], dict(x=23), firstresult=True)
|
||||
|
||||
def test_call_subexecute(self):
|
||||
def m(__call__):
|
||||
subresult = __call__.execute()
|
||||
def m(__multicall__):
|
||||
subresult = __multicall__.execute()
|
||||
return subresult + 1
|
||||
|
||||
def n():
|
||||
|
|
|
@ -77,8 +77,8 @@ class DSession(Session):
|
|||
self.item2nodes = {}
|
||||
super(DSession, self).__init__(config=config)
|
||||
|
||||
#def pytest_configure(self, __call__, config):
|
||||
# __call__.execute()
|
||||
#def pytest_configure(self, __multicall__, config):
|
||||
# __multicall__.execute()
|
||||
# try:
|
||||
# config.getxspecs()
|
||||
# except config.Error:
|
||||
|
|
|
@ -181,11 +181,11 @@ class CaptureManager:
|
|||
capfuncarg._finalize()
|
||||
del self._capturing_funcargs
|
||||
|
||||
def pytest_make_collect_report(self, __call__, collector):
|
||||
def pytest_make_collect_report(self, __multicall__, collector):
|
||||
method = self._getmethod(collector.config, collector.fspath)
|
||||
self.resumecapture(method)
|
||||
try:
|
||||
rep = __call__.execute()
|
||||
rep = __multicall__.execute()
|
||||
finally:
|
||||
outerr = self.suspendcapture()
|
||||
addouterr(rep, outerr)
|
||||
|
@ -204,11 +204,11 @@ class CaptureManager:
|
|||
def pytest_runtest_teardown(self, item):
|
||||
self.resumecapture_item(item)
|
||||
|
||||
def pytest__teardown_final(self, __call__, session):
|
||||
def pytest__teardown_final(self, __multicall__, session):
|
||||
method = self._getmethod(session.config, None)
|
||||
self.resumecapture(method)
|
||||
try:
|
||||
rep = __call__.execute()
|
||||
rep = __multicall__.execute()
|
||||
finally:
|
||||
outerr = self.suspendcapture()
|
||||
if rep:
|
||||
|
@ -219,9 +219,9 @@ class CaptureManager:
|
|||
if hasattr(self, '_capturing'):
|
||||
self.suspendcapture()
|
||||
|
||||
def pytest_runtest_makereport(self, __call__, item, call):
|
||||
def pytest_runtest_makereport(self, __multicall__, item, call):
|
||||
self.deactivate_funcargs()
|
||||
rep = __call__.execute()
|
||||
rep = __multicall__.execute()
|
||||
outerr = self.suspendcapture()
|
||||
outerr = (item.outerr[0] + outerr[0], item.outerr[1] + outerr[1])
|
||||
if not rep.passed:
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
import py
|
||||
|
||||
def pytest_pyfunc_call(__call__, pyfuncitem):
|
||||
if not __call__.execute():
|
||||
def pytest_pyfunc_call(__multicall__, pyfuncitem):
|
||||
if not __multicall__.execute():
|
||||
testfunction = pyfuncitem.obj
|
||||
if pyfuncitem._isyieldedfunction():
|
||||
testfunction(*pyfuncitem._args)
|
||||
|
|
|
@ -32,10 +32,10 @@ class Execnetcleanup:
|
|||
#for gw in l:
|
||||
# gw.join()
|
||||
|
||||
def pytest_pyfunc_call(self, __call__, pyfuncitem):
|
||||
def pytest_pyfunc_call(self, __multicall__, pyfuncitem):
|
||||
if self._gateways is not None:
|
||||
gateways = self._gateways[:]
|
||||
res = __call__.execute()
|
||||
res = __multicall__.execute()
|
||||
while len(self._gateways) > len(gateways):
|
||||
self._gateways[-1].exit()
|
||||
return res
|
||||
|
|
|
@ -47,7 +47,7 @@ import py
|
|||
import inspect
|
||||
import sys
|
||||
|
||||
def pytest_runtest_makereport(__call__, item, call):
|
||||
def pytest_runtest_makereport(__multicall__, item, call):
|
||||
SkipTest = getattr(sys.modules.get('nose', None), 'SkipTest', None)
|
||||
if SkipTest:
|
||||
if call.excinfo and call.excinfo.errisinstance(SkipTest):
|
||||
|
|
|
@ -33,9 +33,9 @@ def pytest_addoption(parser):
|
|||
type="choice", choices=['failed', 'all'],
|
||||
help="send failed|all info to Pocoo pastebin service.")
|
||||
|
||||
def pytest_configure(__call__, config):
|
||||
def pytest_configure(__multicall__, config):
|
||||
import tempfile
|
||||
__call__.execute()
|
||||
__multicall__.execute()
|
||||
if config.option.pastebin == "all":
|
||||
config._pastebinfile = tempfile.TemporaryFile()
|
||||
tr = config.pluginmanager.impname2plugin['terminalreporter']
|
||||
|
|
|
@ -250,8 +250,8 @@ class TerminalReporter:
|
|||
for i, testarg in py.builtin.enumerate(self.config.args):
|
||||
self.write_line("test object %d: %s" %(i+1, testarg))
|
||||
|
||||
def pytest_sessionfinish(self, __call__, session, exitstatus):
|
||||
__call__.execute()
|
||||
def pytest_sessionfinish(self, exitstatus, __multicall__):
|
||||
__multicall__.execute()
|
||||
self._tw.line("")
|
||||
if exitstatus in (0, 1, 2):
|
||||
self.summary_errors()
|
||||
|
|
|
@ -19,12 +19,12 @@ when it fails. Instead terminal reporting will list it in the
|
|||
|
||||
import py
|
||||
|
||||
def pytest_runtest_makereport(__call__, item, call):
|
||||
def pytest_runtest_makereport(__multicall__, item, call):
|
||||
if call.when != "call":
|
||||
return
|
||||
if hasattr(item, 'obj') and hasattr(item.obj, 'func_dict'):
|
||||
if 'xfail' in item.obj.func_dict:
|
||||
res = __call__.execute()
|
||||
res = __multicall__.execute()
|
||||
if call.excinfo:
|
||||
res.skipped = True
|
||||
res.failed = res.passed = False
|
||||
|
|
|
@ -134,15 +134,16 @@ class PluginManager(object):
|
|||
fail = True
|
||||
else:
|
||||
method_args = getargs(method)
|
||||
if '__call__' in method_args:
|
||||
method_args.remove('__call__')
|
||||
if '__multicall__' in method_args:
|
||||
method_args.remove('__multicall__')
|
||||
hook = hooks[name]
|
||||
hookargs = getargs(hook)
|
||||
for arg, hookarg in zip(method_args, hookargs):
|
||||
if arg != hookarg:
|
||||
Print("argument mismatch: %r != %r" %(arg, hookarg))
|
||||
Print("actual : %s" %(formatdef(method)))
|
||||
Print("required:", formatdef(hook))
|
||||
for arg in method_args:
|
||||
if arg not in hookargs:
|
||||
Print("argument %r not available" %(arg, ))
|
||||
Print("actual definition: %s" %(formatdef(method)))
|
||||
Print("available hook arguments: %s" %
|
||||
", ".join(hookargs))
|
||||
fail = True
|
||||
break
|
||||
#if not fail:
|
||||
|
|
Loading…
Reference in New Issue