diff --git a/pytest/plugin/_pytest.py b/pytest/plugin/_pytest.py deleted file mode 100644 index d8f448da6..000000000 --- a/pytest/plugin/_pytest.py +++ /dev/null @@ -1,123 +0,0 @@ -import py - -from pytest._core import HookRelay - -def pytest_funcarg___pytest(request): - return PytestArg(request) - -class PytestArg: - def __init__(self, request): - self.request = request - - def gethookrecorder(self, hook): - hookrecorder = HookRecorder(hook._registry) - hookrecorder.start_recording(hook._hookspecs) - self.request.addfinalizer(hookrecorder.finish_recording) - return hookrecorder - -class ParsedCall: - def __init__(self, name, locals): - assert '_name' not in locals - self.__dict__.update(locals) - self.__dict__.pop('self') - self._name = name - - def __repr__(self): - d = self.__dict__.copy() - del d['_name'] - return "" %(self._name, d) - -class HookRecorder: - def __init__(self, registry): - self._registry = registry - self.calls = [] - self._recorders = {} - - def start_recording(self, hookspecs): - if not isinstance(hookspecs, (list, tuple)): - hookspecs = [hookspecs] - for hookspec in hookspecs: - assert hookspec not in self._recorders - class RecordCalls: - _recorder = self - for name, method in vars(hookspec).items(): - if name[0] != "_": - setattr(RecordCalls, name, self._makecallparser(method)) - recorder = RecordCalls() - self._recorders[hookspec] = recorder - self._registry.register(recorder) - self.hook = HookRelay(hookspecs, registry=self._registry, - prefix="pytest_") - - def finish_recording(self): - for recorder in self._recorders.values(): - self._registry.unregister(recorder) - self._recorders.clear() - - def _makecallparser(self, method): - name = method.__name__ - args, varargs, varkw, default = py.std.inspect.getargspec(method) - if not args or args[0] != "self": - args.insert(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 getcalls(self, names): - if isinstance(names, str): - names = names.split() - for name in names: - for cls in self._recorders: - if name in vars(cls): - break - else: - raise ValueError("callname %r not found in %r" %( - name, self._recorders.keys())) - l = [] - for call in self.calls: - if call._name in names: - l.append(call) - return l - - def contains(self, entries): - from py.builtin import print_ - i = 0 - entries = list(entries) - backlocals = py.std.sys._getframe(1).f_locals - while entries: - name, check = entries.pop(0) - for ind, call in enumerate(self.calls[i:]): - if call._name == name: - print_("NAMEMATCH", name, call) - if eval(check, backlocals, call.__dict__): - print_("CHECKERMATCH", repr(check), "->", call) - else: - print_("NOCHECKERMATCH", repr(check), "-", call) - continue - i += ind + 1 - break - print_("NONAMEMATCH", name, "with", call) - else: - raise AssertionError("could not find %r in %r" %( - name, self.calls[i:])) - - def popcall(self, name): - for i, call in enumerate(self.calls): - if call._name == name: - del self.calls[i] - return call - raise ValueError("could not find call %r" %(name, )) - - def getcall(self, name): - l = self.getcalls(name) - assert len(l) == 1, (name, l) - return l[0] - diff --git a/pytest/plugin/pytester.py b/pytest/plugin/pytester.py index cfcf5fe74..3cafb2c76 100644 --- a/pytest/plugin/pytester.py +++ b/pytest/plugin/pytester.py @@ -11,6 +11,7 @@ from fnmatch import fnmatch from pytest._core import Config as pytestConfig from pytest.plugin.session import Collection from py.builtin import print_ +from pytest._core import HookRelay def pytest_addoption(parser): group = parser.getgroup("pylib") @@ -19,7 +20,125 @@ def pytest_addoption(parser): help=("discover tools on PATH instead of going through py.cmdline.") ) -pytest_plugins = '_pytest' +def pytest_funcarg___pytest(request): + return PytestArg(request) + +class PytestArg: + def __init__(self, request): + self.request = request + + def gethookrecorder(self, hook): + hookrecorder = HookRecorder(hook._registry) + hookrecorder.start_recording(hook._hookspecs) + self.request.addfinalizer(hookrecorder.finish_recording) + return hookrecorder + +class ParsedCall: + def __init__(self, name, locals): + assert '_name' not in locals + self.__dict__.update(locals) + self.__dict__.pop('self') + self._name = name + + def __repr__(self): + d = self.__dict__.copy() + del d['_name'] + return "" %(self._name, d) + +class HookRecorder: + def __init__(self, registry): + self._registry = registry + self.calls = [] + self._recorders = {} + + def start_recording(self, hookspecs): + if not isinstance(hookspecs, (list, tuple)): + hookspecs = [hookspecs] + for hookspec in hookspecs: + assert hookspec not in self._recorders + class RecordCalls: + _recorder = self + for name, method in vars(hookspec).items(): + if name[0] != "_": + setattr(RecordCalls, name, self._makecallparser(method)) + recorder = RecordCalls() + self._recorders[hookspec] = recorder + self._registry.register(recorder) + self.hook = HookRelay(hookspecs, registry=self._registry, + prefix="pytest_") + + def finish_recording(self): + for recorder in self._recorders.values(): + self._registry.unregister(recorder) + self._recorders.clear() + + def _makecallparser(self, method): + name = method.__name__ + args, varargs, varkw, default = py.std.inspect.getargspec(method) + if not args or args[0] != "self": + args.insert(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 getcalls(self, names): + if isinstance(names, str): + names = names.split() + for name in names: + for cls in self._recorders: + if name in vars(cls): + break + else: + raise ValueError("callname %r not found in %r" %( + name, self._recorders.keys())) + l = [] + for call in self.calls: + if call._name in names: + l.append(call) + return l + + def contains(self, entries): + from py.builtin import print_ + i = 0 + entries = list(entries) + backlocals = py.std.sys._getframe(1).f_locals + while entries: + name, check = entries.pop(0) + for ind, call in enumerate(self.calls[i:]): + if call._name == name: + print_("NAMEMATCH", name, call) + if eval(check, backlocals, call.__dict__): + print_("CHECKERMATCH", repr(check), "->", call) + else: + print_("NOCHECKERMATCH", repr(check), "-", call) + continue + i += ind + 1 + break + print_("NONAMEMATCH", name, "with", call) + else: + raise AssertionError("could not find %r in %r" %( + name, self.calls[i:])) + + def popcall(self, name): + for i, call in enumerate(self.calls): + if call._name == name: + del self.calls[i] + return call + raise ValueError("could not find call %r" %(name, )) + + def getcall(self, name): + l = self.getcalls(name) + assert len(l) == 1, (name, l) + return l[0] + def pytest_funcarg__linecomp(request): return LineComp() diff --git a/testing/plugin/test__pytest.py b/testing/plugin/test__pytest.py deleted file mode 100644 index ecb30062e..000000000 --- a/testing/plugin/test__pytest.py +++ /dev/null @@ -1,46 +0,0 @@ -import py -import os, sys -from pytest.plugin._pytest import HookRecorder -from pytest._core import Registry - -def test_hookrecorder_basic(): - rec = HookRecorder(Registry()) - class ApiClass: - def pytest_xyz(self, arg): - "x" - rec.start_recording(ApiClass) - rec.hook.pytest_xyz(arg=123) - call = rec.popcall("pytest_xyz") - assert call.arg == 123 - assert call._name == "pytest_xyz" - py.test.raises(ValueError, "rec.popcall('abc')") - -def test_hookrecorder_basic_no_args_hook(): - rec = HookRecorder(Registry()) - apimod = type(os)('api') - def pytest_xyz(): - "x" - apimod.pytest_xyz = pytest_xyz - rec.start_recording(apimod) - rec.hook.pytest_xyz() - call = rec.popcall("pytest_xyz") - assert call._name == "pytest_xyz" - -def test_functional(testdir, linecomp): - reprec = testdir.inline_runsource(""" - import py - from pytest._core import HookRelay, Registry - pytest_plugins="_pytest" - def test_func(_pytest): - class ApiClass: - def pytest_xyz(self, arg): "x" - hook = HookRelay([ApiClass], Registry()) - rec = _pytest.gethookrecorder(hook) - class Plugin: - def pytest_xyz(self, arg): - return arg + 1 - rec._registry.register(Plugin()) - res = rec.hook.pytest_xyz(arg=41) - assert res == [42] - """) - reprec.assertoutcome(passed=1) diff --git a/testing/plugin/test_pytester.py b/testing/plugin/test_pytester.py index 9090a896c..cd6f8ea70 100644 --- a/testing/plugin/test_pytester.py +++ b/testing/plugin/test_pytester.py @@ -1,5 +1,7 @@ import py -from pytest.plugin.pytester import LineMatcher, LineComp +import os, sys +from pytest.plugin.pytester import LineMatcher, LineComp, HookRecorder +from pytest._core import Registry def test_reportrecorder(testdir): item = testdir.getitem("def test_func(): pass") @@ -69,3 +71,44 @@ def test_testdir_runs_with_plugin(testdir): "*1 passed*" ]) +def test_hookrecorder_basic(): + rec = HookRecorder(Registry()) + class ApiClass: + def pytest_xyz(self, arg): + "x" + rec.start_recording(ApiClass) + rec.hook.pytest_xyz(arg=123) + call = rec.popcall("pytest_xyz") + assert call.arg == 123 + assert call._name == "pytest_xyz" + py.test.raises(ValueError, "rec.popcall('abc')") + +def test_hookrecorder_basic_no_args_hook(): + rec = HookRecorder(Registry()) + apimod = type(os)('api') + def pytest_xyz(): + "x" + apimod.pytest_xyz = pytest_xyz + rec.start_recording(apimod) + rec.hook.pytest_xyz() + call = rec.popcall("pytest_xyz") + assert call._name == "pytest_xyz" + +def test_functional(testdir, linecomp): + reprec = testdir.inline_runsource(""" + import py + from pytest._core import HookRelay, Registry + pytest_plugins="pytester" + def test_func(_pytest): + class ApiClass: + def pytest_xyz(self, arg): "x" + hook = HookRelay([ApiClass], Registry()) + rec = _pytest.gethookrecorder(hook) + class Plugin: + def pytest_xyz(self, arg): + return arg + 1 + rec._registry.register(Plugin()) + res = rec.hook.pytest_xyz(arg=41) + assert res == [42] + """) + reprec.assertoutcome(passed=1)