[svn r63845] introduce new _pytest plugin that allows to selectively record

plugin calls and do assertions about them.

--HG--
branch : trunk
This commit is contained in:
hpk 2009-04-08 17:15:56 +02:00
parent 0300b2109c
commit b6b060c1d0
8 changed files with 161 additions and 22 deletions

View File

@ -160,26 +160,32 @@ class PyPlugins:
class PluginAPI:
def __init__(self, apiclass, plugins):
def __init__(self, apiclass, plugins=None):
self._apiclass = apiclass
if plugins is None:
plugins = pyplugins
self._plugins = plugins
for name in vars(apiclass):
for name, method in vars(apiclass).items():
if name[:2] != "__":
mm = CallMaker(plugins, name)
firstresult = getattr(method, 'firstresult', False)
mm = ApiCall(plugins, name, firstresult=firstresult)
setattr(self, name, mm)
def __repr__(self):
return "<PluginAPI %r %r>" %(self._apiclass, self._plugins)
class CallMaker:
def __init__(self, plugins, name):
class ApiCall:
def __init__(self, plugins, name, firstresult):
self.plugins = plugins
self.name = name
self.firstresult = firstresult
def __repr__(self):
return "<MulticallMaker %r %s>" %(self.name, self.plugins)
mode = self.firstresult and "firstresult" or "each"
return "<ApiCall %r mode=%s %s>" %(self.name, mode, self.plugins)
def __call__(self, *args, **kwargs):
mc = MultiCall(self.plugins.listattr(self.name), *args, **kwargs)
return mc.execute()
#print "making multicall", self
return mc.execute(firstresult=self.firstresult)
pyplugins = PyPlugins()

View File

@ -1,4 +1,5 @@
pytest_plugins = 'pytest_doctest', 'pytest_pytester' # , 'pytest_restdoc'
pytest_plugins = '_pytest doctest pytester'.split()
rsyncdirs = ['../doc']
rsyncignore = ['c-extension/greenlet/build']

View File

@ -52,6 +52,12 @@ class GatewayCleanup:
gw.exit()
#gw.join() # should work as well
class ExecnetAPI:
def pyexecnet_gateway_init(self, gateway):
""" signal initialisation of new gateway. """
def pyexecnet_gateway_exit(self, gateway):
""" signal exitting of gateway. """
# ----------------------------------------------------------
# Base Gateway (used for both remote and local side)
# ----------------------------------------------------------
@ -70,6 +76,12 @@ class Gateway(object):
self._io = io
self._channelfactory = ChannelFactory(self, _startcount)
self._cleanup.register(self)
try:
from py._com import PluginAPI
except ImportError:
self.api = ExecnetAPI()
else:
self.api = PluginAPI(ExecnetAPI)
def _initreceive(self, requestqueue=False):
if requestqueue:
@ -331,12 +343,7 @@ class Gateway(object):
self._cleanup.unregister(self)
self._stopexec()
self._stopsend()
try:
py._com.pyplugins.notify("gateway_exit", self)
except NameError:
# XXX on the remote side 'py' is not imported
# and so we can't notify
pass
self.api.pyexecnet_gateway_exit(gateway=self)
def _remote_redirect(self, stdout=None, stderr=None):
""" return a handle representing a redirection of a remote

View File

@ -41,7 +41,7 @@ class InstallableGateway(gateway.Gateway):
super(InstallableGateway, self).__init__(io=io, _startcount=1)
# XXX we dissallow execution form the other side
self._initreceive(requestqueue=False)
py._com.pyplugins.notify("gateway_init", self)
self.api.pyexecnet_gateway_init(gateway=self)
def _remote_bootstrap_gateway(self, io, extra=''):
""" return Gateway with a asynchronously remotely

View File

@ -1,11 +1,13 @@
import py
pytest_plugins = "pytester"
from py.__.execnet.gateway import ExecnetAPI
class TestExecnetEvents:
def test_popengateway(self, eventrecorder):
def test_popengateway_events(self, _pytest):
rec = _pytest.getcallrecorder(ExecnetAPI)
gw = py.execnet.PopenGateway()
event = eventrecorder.popevent("gateway_init")
assert event.args[0] == gw
call = rec.popcall("pyexecnet_gateway_init")
assert call.gateway == gw
gw.exit()
event = eventrecorder.popevent("gateway_exit")
assert event.args[0] == gw
call = rec.popcall("pyexecnet_gateway_exit")
assert call.gateway == gw

View File

@ -250,7 +250,7 @@ class TestPyPluginsEvents:
assert l == [(13, ), {'x':15}]
class TestMulticallMaker:
class TestPluginAPI:
def test_happypath(self):
plugins = PyPlugins()
class Api:
@ -267,3 +267,22 @@ class TestMulticallMaker:
l = mcm.hello(3)
assert l == [4]
assert not hasattr(mcm, 'world')
def test_firstresult(self):
plugins = PyPlugins()
class Api:
def hello(self, arg): pass
hello.firstresult = True
mcm = PluginAPI(apiclass=Api, plugins=plugins)
class Plugin:
def hello(self, arg):
return arg + 1
plugins.register(Plugin())
res = mcm.hello(3)
assert res == 4
def test_default_plugins(self):
class Api: pass
mcm = PluginAPI(apiclass=Api)
assert mcm._plugins == py._com.pyplugins

View File

@ -48,7 +48,7 @@ class PluginHooks:
# runtest related hooks
# ------------------------------------------------------------------------------
def pytest_pyfunc_call(self, pyfuncitem, args, kwargs):
def pytest_pyfunc_call(self, call, pyfuncitem, args, kwargs):
""" return True if we consumed/did the call to the python function item. """
def pytest_item_makereport(self, item, excinfo, when, outerr):

View File

@ -0,0 +1,104 @@
import py
class _pytestPlugin:
def pytest_funcarg___pytest(self, pyfuncitem):
return PytestArg(pyfuncitem)
class PytestArg:
def __init__(self, pyfuncitem):
self.pyfuncitem = pyfuncitem
def getcallrecorder(self, apiclass, pyplugins=None):
if pyplugins is None:
pyplugins = self.pyfuncitem.config.pytestplugins.pyplugins
callrecorder = CallRecorder(pyplugins)
callrecorder.start_recording(apiclass)
self.pyfuncitem.addfinalizer(callrecorder.finalize)
return callrecorder
class ParsedCall:
def __init__(self, name, locals):
assert '_name' not in locals
self.__dict__.update(locals)
self._name = name
def __repr__(self):
return "<ParsedCall %r>" %(self.__dict__,)
class CallRecorder:
def __init__(self, pyplugins):
self._pyplugins = pyplugins
self.calls = []
self._recorders = {}
def start_recording(self, apiclass):
assert apiclass not in self._recorders
class RecordCalls:
_recorder = self
for name, method in vars(apiclass).items():
if name[0] != "_":
setattr(RecordCalls, name, self._getcallparser(method))
recorder = RecordCalls()
self._recorders[apiclass] = recorder
self._pyplugins.register(recorder)
def finalize(self):
for recorder in self._recorders.values():
self._pyplugins.unregister(recorder)
def _getcallparser(self, method):
name = method.__name__
args, varargs, varkw, default = py.std.inspect.getargspec(method)
assert args[0] == "self"
fspec = py.std.inspect.formatargspec(args, varargs, varkw, default)
# we use exec because we want to have early type
# errors on wrong input arguments, using
# *args/**kwargs delays this and gives errors
# elsewhere
exec py.code.compile("""
def %(name)s%(fspec)s:
self._recorder.calls.append(
ParsedCall(%(name)r, locals()))
""" % locals())
return locals()[name]
def popcall(self, name):
for i, call in py.builtin.enumerate(self.calls):
if call._name == name:
del self.calls[i]
return call
raise ValueError("could not find call %r in %r" %(name, self.calls))
def test_generic(plugintester):
plugintester.apicheck(_pytestPlugin)
def test_callrecorder_basic():
pyplugins = py._com.PyPlugins()
rec = CallRecorder(pyplugins)
class ApiClass:
def xyz(self, arg):
pass
rec.start_recording(ApiClass)
pyplugins.call_each("xyz", 123)
call = rec.popcall("xyz")
assert call.arg == 123
assert call._name == "xyz"
py.test.raises(ValueError, "rec.popcall('abc')")
def test_functional(testdir, linecomp):
sorter = testdir.inline_runsource("""
import py
pytest_plugins="_pytest"
def test_func(_pytest):
class ApiClass:
def xyz(self, arg): pass
rec = _pytest.getcallrecorder(ApiClass)
class Plugin:
def xyz(self, arg):
return arg + 1
rec._pyplugins.register(Plugin())
res = rec._pyplugins.call_firstresult("xyz", 41)
assert res == 42
""")
sorter.assertoutcome(passed=1)