1 new commit in pytest: https://bitbucket.org/pytest-dev/pytest/commits/af98ffcfb0fb/ Changeset: af98ffcfb0fb User: hpk42 Date: 2015-05-06 13:02:05+00:00 Summary: Merged in hpk42/pytest-patches/pluggy1 (pull request #290)
integrate pluggy as external plugin manager Affected #: 29 files diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -27,8 +27,8 @@ details like especially the pluginmanager.add_shutdown() API. Thanks Holger Krekel. -- pluginmanagement: introduce ``pytest.hookimpl_opts`` and - ``pytest.hookspec_opts`` decorators for setting impl/spec +- pluginmanagement: introduce ``pytest.hookimpl`` and + ``pytest.hookspec`` decorators for setting impl/spec specific parameters. This substitutes the previous now deprecated use of ``pytest.mark`` which is meant to contain markers for test functions only. diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 _pytest/__init__.py --- a/_pytest/__init__.py +++ b/_pytest/__init__.py @@ -1,2 +1,2 @@ # -__version__ = '2.8.0.dev2' +__version__ = '2.8.0.dev3' diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 _pytest/capture.py --- a/_pytest/capture.py +++ b/_pytest/capture.py @@ -29,7 +29,7 @@ help="shortcut for --capture=no.") -@pytest.hookimpl_opts(hookwrapper=True) +@pytest.hookimpl(hookwrapper=True) def pytest_load_initial_conftests(early_config, parser, args): ns = early_config.known_args_namespace pluginmanager = early_config.pluginmanager @@ -101,7 +101,7 @@ if capfuncarg is not None: capfuncarg.close() - @pytest.hookimpl_opts(hookwrapper=True) + @pytest.hookimpl(hookwrapper=True) def pytest_make_collect_report(self, collector): if isinstance(collector, pytest.File): self.resumecapture() @@ -115,13 +115,13 @@ else: yield - @pytest.hookimpl_opts(hookwrapper=True) + @pytest.hookimpl(hookwrapper=True) def pytest_runtest_setup(self, item): self.resumecapture() yield self.suspendcapture_item(item, "setup") - @pytest.hookimpl_opts(hookwrapper=True) + @pytest.hookimpl(hookwrapper=True) def pytest_runtest_call(self, item): self.resumecapture() self.activate_funcargs(item) @@ -129,17 +129,17 @@ #self.deactivate_funcargs() called from suspendcapture() self.suspendcapture_item(item, "call") - @pytest.hookimpl_opts(hookwrapper=True) + @pytest.hookimpl(hookwrapper=True) def pytest_runtest_teardown(self, item): self.resumecapture() yield self.suspendcapture_item(item, "teardown") - @pytest.hookimpl_opts(tryfirst=True) + @pytest.hookimpl(tryfirst=True) def pytest_keyboard_interrupt(self, excinfo): self.reset_capturings() - @pytest.hookimpl_opts(tryfirst=True) + @pytest.hookimpl(tryfirst=True) def pytest_internalerror(self, excinfo): self.reset_capturings() diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 _pytest/config.py --- a/_pytest/config.py +++ b/_pytest/config.py @@ -8,8 +8,11 @@ import py # DON't import pytest here because it causes import cycle troubles import sys, os -from _pytest import hookspec # the extension point definitions -from _pytest.core import PluginManager, hookimpl_opts, varnames +import _pytest.hookspec # the extension point definitions +from pluggy import PluginManager, HookimplMarker, HookspecMarker + +hookimpl = HookimplMarker("pytest") +hookspec = HookspecMarker("pytest") # pytest startup # @@ -106,10 +109,10 @@ name.startswith("pytest_funcarg__") + class PytestPluginManager(PluginManager): def __init__(self): - super(PytestPluginManager, self).__init__(prefix="pytest_", - excludefunc=exclude_pytest_names) + super(PytestPluginManager, self).__init__("pytest", implprefix="pytest_") self._warnings = [] self._conftest_plugins = set() @@ -118,7 +121,7 @@ self._conftestpath2mod = {} self._confcutdir = None - self.addhooks(hookspec) + self.add_hookspecs(_pytest.hookspec) self.register(self) if os.environ.get('PYTEST_DEBUG'): err = sys.stderr @@ -130,12 +133,39 @@ self.trace.root.setwriter(err.write) self.enable_tracing() + def addhooks(self, module_or_class): + warning = dict(code="I2", + fslocation=py.code.getfslineno(sys._getframe(1)), + message="use pluginmanager.add_hookspecs instead of " + "deprecated addhooks() method.") + self._warnings.append(warning) + return self.add_hookspecs(module_or_class) - def _verify_hook(self, hook, plugin): - super(PytestPluginManager, self)._verify_hook(hook, plugin) - method = getattr(plugin, hook.name) - if "__multicall__" in varnames(method): - fslineno = py.code.getfslineno(method) + def parse_hookimpl_opts(self, plugin, name): + if exclude_pytest_names(name): + return None + + method = getattr(plugin, name) + opts = super(PytestPluginManager, self).parse_hookimpl_opts(plugin, name) + if opts is not None: + for name in ("tryfirst", "trylast", "optionalhook", "hookwrapper"): + opts.setdefault(name, hasattr(method, name)) + return opts + + def parse_hookspec_opts(self, module_or_class, name): + opts = super(PytestPluginManager, self).parse_hookspec_opts( + module_or_class, name) + if opts is None: + method = getattr(module_or_class, name) + if name.startswith("pytest_"): + opts = {"firstresult": hasattr(method, "firstresult"), + "historic": hasattr(method, "historic")} + return opts + + def _verify_hook(self, hook, hookmethod): + super(PytestPluginManager, self)._verify_hook(hook, hookmethod) + if "__multicall__" in hookmethod.argnames: + fslineno = py.code.getfslineno(hookmethod.function) warning = dict(code="I1", fslocation=fslineno, message="%r hook uses deprecated __multicall__ " @@ -154,7 +184,7 @@ return self.get_plugin(name) def pytest_configure(self, config): - # XXX now that the pluginmanager exposes hookimpl_opts(tryfirst...) + # XXX now that the pluginmanager exposes hookimpl(tryfirst...) # we should remove tryfirst/trylast as markers config.addinivalue_line("markers", "tryfirst: mark a hook implementation function such that the " @@ -797,7 +827,7 @@ if not hasattr(self.option, opt.dest): setattr(self.option, opt.dest, opt.default) - @hookimpl_opts(trylast=True) + @hookimpl(trylast=True) def pytest_load_initial_conftests(self, early_config): self.pluginmanager._set_initial_conftests(early_config.known_args_namespace) diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 _pytest/core.py --- a/_pytest/core.py +++ /dev/null @@ -1,590 +0,0 @@ -""" -PluginManager, basic initialization and tracing. -""" -import sys -from inspect import isfunction, ismethod, isclass, formatargspec, getargspec -import py - -py3 = sys.version_info > (3,0) - -def hookspec_opts(firstresult=False, historic=False): - """ returns a decorator which will define a function as a hook specfication. - - If firstresult is True the 1:N hook call (N being the number of registered - hook implementation functions) will stop at I<=N when the I'th function - returns a non-None result. - - If historic is True calls to a hook will be memorized and replayed - on later registered plugins. - """ - def setattr_hookspec_opts(func): - if historic and firstresult: - raise ValueError("cannot have a historic firstresult hook") - if firstresult: - func.firstresult = firstresult - if historic: - func.historic = historic - return func - return setattr_hookspec_opts - - -def hookimpl_opts(hookwrapper=False, optionalhook=False, - tryfirst=False, trylast=False): - """ Return a decorator which marks a function as a hook implementation. - - If optionalhook is True a missing matching hook specification will not result - in an error (by default it is an error if no matching spec is found). - - If tryfirst is True this hook implementation will run as early as possible - in the chain of N hook implementations for a specfication. - - If trylast is True this hook implementation will run as late as possible - in the chain of N hook implementations. - - If hookwrapper is True the hook implementations needs to execute exactly - one "yield". The code before the yield is run early before any non-hookwrapper - function is run. The code after the yield is run after all non-hookwrapper - function have run. The yield receives an ``CallOutcome`` object representing - the exception or result outcome of the inner calls (including other hookwrapper - calls). - """ - def setattr_hookimpl_opts(func): - if hookwrapper: - func.hookwrapper = True - if optionalhook: - func.optionalhook = True - if tryfirst: - func.tryfirst = True - if trylast: - func.trylast = True - return func - return setattr_hookimpl_opts - - -class TagTracer: - def __init__(self): - self._tag2proc = {} - self.writer = None - self.indent = 0 - - def get(self, name): - return TagTracerSub(self, (name,)) - - def format_message(self, tags, args): - if isinstance(args[-1], dict): - extra = args[-1] - args = args[:-1] - else: - extra = {} - - content = " ".join(map(str, args)) - indent = " " * self.indent - - lines = [ - "%s%s [%s]\n" %(indent, content, ":".join(tags)) - ] - - for name, value in extra.items(): - lines.append("%s %s: %s\n" % (indent, name, value)) - return lines - - def processmessage(self, tags, args): - if self.writer is not None and args: - lines = self.format_message(tags, args) - self.writer(''.join(lines)) - try: - self._tag2proc[tags](tags, args) - except KeyError: - pass - - def setwriter(self, writer): - self.writer = writer - - def setprocessor(self, tags, processor): - if isinstance(tags, str): - tags = tuple(tags.split(":")) - else: - assert isinstance(tags, tuple) - self._tag2proc[tags] = processor - - -class TagTracerSub: - def __init__(self, root, tags): - self.root = root - self.tags = tags - - def __call__(self, *args): - self.root.processmessage(self.tags, args) - - def setmyprocessor(self, processor): - self.root.setprocessor(self.tags, processor) - - def get(self, name): - return self.__class__(self.root, self.tags + (name,)) - - -def raise_wrapfail(wrap_controller, msg): - co = wrap_controller.gi_code - raise RuntimeError("wrap_controller at %r %s:%d %s" % - (co.co_name, co.co_filename, co.co_firstlineno, msg)) - - -def wrapped_call(wrap_controller, func): - """ Wrap calling to a function with a generator which needs to yield - exactly once. The yield point will trigger calling the wrapped function - and return its CallOutcome to the yield point. The generator then needs - to finish (raise StopIteration) in order for the wrapped call to complete. - """ - try: - next(wrap_controller) # first yield - except StopIteration: - raise_wrapfail(wrap_controller, "did not yield") - call_outcome = CallOutcome(func) - try: - wrap_controller.send(call_outcome) - raise_wrapfail(wrap_controller, "has second yield") - except StopIteration: - pass - return call_outcome.get_result() - - -class CallOutcome: - """ Outcome of a function call, either an exception or a proper result. - Calling the ``get_result`` method will return the result or reraise - the exception raised when the function was called. """ - excinfo = None - def __init__(self, func): - try: - self.result = func() - except BaseException: - self.excinfo = sys.exc_info() - - def force_result(self, result): - self.result = result - self.excinfo = None - - def get_result(self): - if self.excinfo is None: - return self.result - else: - ex = self.excinfo - if py3: - raise ex[1].with_traceback(ex[2]) - py.builtin._reraise(*ex) - - -class TracedHookExecution: - def __init__(self, pluginmanager, before, after): - self.pluginmanager = pluginmanager - self.before = before - self.after = after - self.oldcall = pluginmanager._inner_hookexec - assert not isinstance(self.oldcall, TracedHookExecution) - self.pluginmanager._inner_hookexec = self - - def __call__(self, hook, methods, kwargs): - self.before(hook, methods, kwargs) - outcome = CallOutcome(lambda: self.oldcall(hook, methods, kwargs)) - self.after(outcome, hook, methods, kwargs) - return outcome.get_result() - - def undo(self): - self.pluginmanager._inner_hookexec = self.oldcall - - -class PluginManager(object): - """ Core Pluginmanager class which manages registration - of plugin objects and 1:N hook calling. - - You can register new hooks by calling ``addhooks(module_or_class)``. - You can register plugin objects (which contain hooks) by calling - ``register(plugin)``. The Pluginmanager is initialized with a - prefix that is searched for in the names of the dict of registered - plugin objects. An optional excludefunc allows to blacklist names which - are not considered as hooks despite a matching prefix. - - For debugging purposes you can call ``enable_tracing()`` - which will subsequently send debug information to the trace helper. - """ - - def __init__(self, prefix, excludefunc=None): - self._prefix = prefix - self._excludefunc = excludefunc - self._name2plugin = {} - self._plugin2hookcallers = {} - self._plugin_distinfo = [] - self.trace = TagTracer().get("pluginmanage") - self.hook = HookRelay(self.trace.root.get("hook")) - self._inner_hookexec = lambda hook, methods, kwargs: \ - MultiCall(methods, kwargs, hook.firstresult).execute() - - def _hookexec(self, hook, methods, kwargs): - # called from all hookcaller instances. - # enable_tracing will set its own wrapping function at self._inner_hookexec - return self._inner_hookexec(hook, methods, kwargs) - - def enable_tracing(self): - """ enable tracing of hook calls and return an undo function. """ - hooktrace = self.hook._trace - - def before(hook, methods, kwargs): - hooktrace.root.indent += 1 - hooktrace(hook.name, kwargs) - - def after(outcome, hook, methods, kwargs): - if outcome.excinfo is None: - hooktrace("finish", hook.name, "-->", outcome.result) - hooktrace.root.indent -= 1 - - return TracedHookExecution(self, before, after).undo - - def subset_hook_caller(self, name, remove_plugins): - """ Return a new HookCaller instance for the named method - which manages calls to all registered plugins except the - ones from remove_plugins. """ - orig = getattr(self.hook, name) - plugins_to_remove = [plugin for plugin in remove_plugins - if hasattr(plugin, name)] - if plugins_to_remove: - hc = HookCaller(orig.name, orig._hookexec, orig._specmodule_or_class) - for plugin in orig._plugins: - if plugin not in plugins_to_remove: - hc._add_plugin(plugin) - # we also keep track of this hook caller so it - # gets properly removed on plugin unregistration - self._plugin2hookcallers.setdefault(plugin, []).append(hc) - return hc - return orig - - def register(self, plugin, name=None): - """ Register a plugin and return its canonical name or None if the name - is blocked from registering. Raise a ValueError if the plugin is already - registered. """ - plugin_name = name or self.get_canonical_name(plugin) - - if plugin_name in self._name2plugin or plugin in self._plugin2hookcallers: - if self._name2plugin.get(plugin_name, -1) is None: - return # blocked plugin, return None to indicate no registration - raise ValueError("Plugin already registered: %s=%s\n%s" %( - plugin_name, plugin, self._name2plugin)) - - self._name2plugin[plugin_name] = plugin - - # register prefix-matching hook specs of the plugin - self._plugin2hookcallers[plugin] = hookcallers = [] - for name in dir(plugin): - if name.startswith(self._prefix): - hook = getattr(self.hook, name, None) - if hook is None: - if self._excludefunc is not None and self._excludefunc(name): - continue - hook = HookCaller(name, self._hookexec) - setattr(self.hook, name, hook) - elif hook.has_spec(): - self._verify_hook(hook, plugin) - hook._maybe_apply_history(getattr(plugin, name)) - hookcallers.append(hook) - hook._add_plugin(plugin) - return plugin_name - - def unregister(self, plugin=None, name=None): - """ unregister a plugin object and all its contained hook implementations - from internal data structures. """ - if name is None: - assert plugin is not None, "one of name or plugin needs to be specified" - name = self.get_name(plugin) - - if plugin is None: - plugin = self.get_plugin(name) - - # if self._name2plugin[name] == None registration was blocked: ignore - if self._name2plugin.get(name): - del self._name2plugin[name] - - for hookcaller in self._plugin2hookcallers.pop(plugin, []): - hookcaller._remove_plugin(plugin) - - return plugin - - def set_blocked(self, name): - """ block registrations of the given name, unregister if already registered. """ - self.unregister(name=name) - self._name2plugin[name] = None - - def addhooks(self, module_or_class): - """ add new hook definitions from the given module_or_class using - the prefix/excludefunc with which the PluginManager was initialized. """ - names = [] - for name in dir(module_or_class): - if name.startswith(self._prefix): - hc = getattr(self.hook, name, None) - if hc is None: - hc = HookCaller(name, self._hookexec, module_or_class) - setattr(self.hook, name, hc) - else: - # plugins registered this hook without knowing the spec - hc.set_specification(module_or_class) - for plugin in hc._plugins: - self._verify_hook(hc, plugin) - names.append(name) - - if not names: - raise ValueError("did not find new %r hooks in %r" - %(self._prefix, module_or_class)) - - def get_plugins(self): - """ return the set of registered plugins. """ - return set(self._plugin2hookcallers) - - def is_registered(self, plugin): - """ Return True if the plugin is already registered. """ - return plugin in self._plugin2hookcallers - - def get_canonical_name(self, plugin): - """ Return canonical name for a plugin object. Note that a plugin - may be registered under a different name which was specified - by the caller of register(plugin, name). To obtain the name - of an registered plugin use ``get_name(plugin)`` instead.""" - return getattr(plugin, "__name__", None) or str(id(plugin)) - - def get_plugin(self, name): - """ Return a plugin or None for the given name. """ - return self._name2plugin.get(name) - - def get_name(self, plugin): - """ Return name for registered plugin or None if not registered. """ - for name, val in self._name2plugin.items(): - if plugin == val: - return name - - def _verify_hook(self, hook, plugin): - method = getattr(plugin, hook.name) - pluginname = self.get_name(plugin) - - if hook.is_historic() and hasattr(method, "hookwrapper"): - raise PluginValidationError( - "Plugin %r\nhook %r\nhistoric incompatible to hookwrapper" %( - pluginname, hook.name)) - - for arg in varnames(method): - if arg not in hook.argnames: - raise PluginValidationError( - "Plugin %r\nhook %r\nargument %r not available\n" - "plugin definition: %s\n" - "available hookargs: %s" %( - pluginname, hook.name, arg, formatdef(method), - ", ".join(hook.argnames))) - - def check_pending(self): - """ Verify that all hooks which have not been verified against - a hook specification are optional, otherwise raise PluginValidationError""" - for name in self.hook.__dict__: - if name.startswith(self._prefix): - hook = getattr(self.hook, name) - if not hook.has_spec(): - for plugin in hook._plugins: - method = getattr(plugin, hook.name) - if not getattr(method, "optionalhook", False): - raise PluginValidationError( - "unknown hook %r in plugin %r" %(name, plugin)) - - def load_setuptools_entrypoints(self, entrypoint_name): - """ Load modules from querying the specified setuptools entrypoint name. - Return the number of loaded plugins. """ - from pkg_resources import iter_entry_points, DistributionNotFound - for ep in iter_entry_points(entrypoint_name): - # is the plugin registered or blocked? - if self.get_plugin(ep.name) or ep.name in self._name2plugin: - continue - try: - plugin = ep.load() - except DistributionNotFound: - continue - self.register(plugin, name=ep.name) - self._plugin_distinfo.append((ep.dist, plugin)) - return len(self._plugin_distinfo) - - -class MultiCall: - """ execute a call into multiple python functions/methods. """ - - # XXX note that the __multicall__ argument is supported only - # for pytest compatibility reasons. It was never officially - # supported there and is explicitely deprecated since 2.8 - # so we can remove it soon, allowing to avoid the below recursion - # in execute() and simplify/speed up the execute loop. - - def __init__(self, methods, kwargs, firstresult=False): - self.methods = methods - self.kwargs = kwargs - self.kwargs["__multicall__"] = self - self.firstresult = firstresult - - def execute(self): - all_kwargs = self.kwargs - self.results = results = [] - firstresult = self.firstresult - - while self.methods: - method = self.methods.pop() - args = [all_kwargs[argname] for argname in varnames(method)] - if hasattr(method, "hookwrapper"): - return wrapped_call(method(*args), self.execute) - res = method(*args) - if res is not None: - if firstresult: - return res - results.append(res) - - if not firstresult: - return results - - def __repr__(self): - status = "%d meths" % (len(self.methods),) - if hasattr(self, "results"): - status = ("%d results, " % len(self.results)) + status - return "<MultiCall %s, kwargs=%r>" %(status, self.kwargs) - - - -def varnames(func, startindex=None): - """ return argument name tuple for a function, method, class or callable. - - In case of a class, its "__init__" method is considered. - For methods the "self" parameter is not included unless you are passing - an unbound method with Python3 (which has no supports for unbound methods) - """ - cache = getattr(func, "__dict__", {}) - try: - return cache["_varnames"] - except KeyError: - pass - if isclass(func): - try: - func = func.__init__ - except AttributeError: - return () - startindex = 1 - else: - if not isfunction(func) and not ismethod(func): - func = getattr(func, '__call__', func) - if startindex is None: - startindex = int(ismethod(func)) - - rawcode = py.code.getrawcode(func) - try: - x = rawcode.co_varnames[startindex:rawcode.co_argcount] - except AttributeError: - x = () - else: - defaults = func.__defaults__ - if defaults: - x = x[:-len(defaults)] - try: - cache["_varnames"] = x - except TypeError: - pass - return x - - -class HookRelay: - def __init__(self, trace): - self._trace = trace - - -class HookCaller(object): - def __init__(self, name, hook_execute, specmodule_or_class=None): - self.name = name - self._plugins = [] - self._wrappers = [] - self._nonwrappers = [] - self._hookexec = hook_execute - if specmodule_or_class is not None: - self.set_specification(specmodule_or_class) - - def has_spec(self): - return hasattr(self, "_specmodule_or_class") - - def set_specification(self, specmodule_or_class): - assert not self.has_spec() - self._specmodule_or_class = specmodule_or_class - specfunc = getattr(specmodule_or_class, self.name) - argnames = varnames(specfunc, startindex=isclass(specmodule_or_class)) - assert "self" not in argnames # sanity check - self.argnames = ["__multicall__"] + list(argnames) - self.firstresult = getattr(specfunc, 'firstresult', False) - if hasattr(specfunc, "historic"): - self._call_history = [] - - def is_historic(self): - return hasattr(self, "_call_history") - - def _remove_plugin(self, plugin): - self._plugins.remove(plugin) - meth = getattr(plugin, self.name) - try: - self._nonwrappers.remove(meth) - except ValueError: - self._wrappers.remove(meth) - - def _add_plugin(self, plugin): - self._plugins.append(plugin) - self._add_method(getattr(plugin, self.name)) - - def _add_method(self, meth): - if hasattr(meth, 'hookwrapper'): - methods = self._wrappers - else: - methods = self._nonwrappers - - if hasattr(meth, 'trylast'): - methods.insert(0, meth) - elif hasattr(meth, 'tryfirst'): - methods.append(meth) - else: - # find last non-tryfirst method - i = len(methods) - 1 - while i >= 0 and hasattr(methods[i], "tryfirst"): - i -= 1 - methods.insert(i + 1, meth) - - def __repr__(self): - return "<HookCaller %r>" %(self.name,) - - def __call__(self, **kwargs): - assert not self.is_historic() - return self._hookexec(self, self._nonwrappers + self._wrappers, kwargs) - - def call_historic(self, proc=None, kwargs=None): - self._call_history.append((kwargs or {}, proc)) - # historizing hooks don't return results - self._hookexec(self, self._nonwrappers + self._wrappers, kwargs) - - def call_extra(self, methods, kwargs): - """ Call the hook with some additional temporarily participating - methods using the specified kwargs as call parameters. """ - old = list(self._nonwrappers), list(self._wrappers) - for method in methods: - self._add_method(method) - try: - return self(**kwargs) - finally: - self._nonwrappers, self._wrappers = old - - def _maybe_apply_history(self, method): - if self.is_historic(): - for kwargs, proc in self._call_history: - res = self._hookexec(self, [method], kwargs) - if res and proc is not None: - proc(res[0]) - - -class PluginValidationError(Exception): - """ plugin failed validation. """ - - -def formatdef(func): - return "%s%s" % ( - func.__name__, - formatargspec(*getargspec(func)) - ) diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 _pytest/genscript.py --- a/_pytest/genscript.py +++ b/_pytest/genscript.py @@ -4,7 +4,6 @@ import pkgutil import py - import _pytest @@ -33,6 +32,9 @@ for pyfile in toplevel.visit('*.py'): pkg = pkgname(name, toplevel, pyfile) name2src[pkg] = pyfile.read() + # with wheels py source code might be not be installed + # and the resulting genscript is useless, just bail out. + assert name2src, "no source code found for %r at %r" %(name, toplevel) return name2src def compress_mapping(mapping): @@ -69,7 +71,7 @@ genscript = config.getvalue("genscript") if genscript: tw = py.io.TerminalWriter() - deps = ['py', '_pytest', 'pytest'] + deps = ['py', 'pluggy', '_pytest', 'pytest'] if sys.version_info < (2,7): deps.append("argparse") tw.line("generated script will run on python2.6-python3.3++") diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 _pytest/helpconfig.py --- a/_pytest/helpconfig.py +++ b/_pytest/helpconfig.py @@ -22,7 +22,7 @@ help="store internal tracing debug information in 'pytestdebug.log'.") -@pytest.hookimpl_opts(hookwrapper=True) +@pytest.hookimpl(hookwrapper=True) def pytest_cmdline_parse(): outcome = yield config = outcome.get_result() @@ -96,10 +96,10 @@ def getpluginversioninfo(config): lines = [] - plugininfo = config.pluginmanager._plugin_distinfo + plugininfo = config.pluginmanager.list_plugin_distinfo() if plugininfo: lines.append("setuptools registered plugins:") - for dist, plugin in plugininfo: + for plugin, dist in plugininfo: loc = getattr(plugin, '__file__', repr(plugin)) content = "%s-%s at %s" % (dist.project_name, dist.version, loc) lines.append(" " + content) @@ -117,7 +117,7 @@ if config.option.traceconfig: lines.append("active plugins:") - items = config.pluginmanager._name2plugin.items() + items = config.pluginmanager.list_name_plugin() for name, plugin in items: if hasattr(plugin, '__file__'): r = plugin.__file__ diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 _pytest/hookspec.py --- a/_pytest/hookspec.py +++ b/_pytest/hookspec.py @@ -1,30 +1,32 @@ """ hook specifications for pytest plugins, invoked from main.py and builtin plugins. """ -from _pytest.core import hookspec_opts +from pluggy import HookspecMarker + +hookspec = HookspecMarker("pytest") # ------------------------------------------------------------------------- # Initialization hooks called for every plugin # ------------------------------------------------------------------------- -@hookspec_opts(historic=True) +@hookspec(historic=True) def pytest_addhooks(pluginmanager): """called at plugin registration time to allow adding new hooks via a call to - pluginmanager.addhooks(module_or_class, prefix).""" + pluginmanager.add_hookspecs(module_or_class, prefix).""" -@hookspec_opts(historic=True) +@hookspec(historic=True) def pytest_namespace(): """return dict of name->object to be made globally available in the pytest namespace. This hook is called at plugin registration time. """ -@hookspec_opts(historic=True) +@hookspec(historic=True) def pytest_plugin_registered(plugin, manager): """ a new pytest plugin got registered. """ -@hookspec_opts(historic=True) +@hookspec(historic=True) def pytest_addoption(parser): """register argparse-style options and ini-style config values. @@ -50,7 +52,7 @@ via (deprecated) ``pytest.config``. """ -@hookspec_opts(historic=True) +@hookspec(historic=True) def pytest_configure(config): """ called after command line options have been parsed and all plugins and initial conftest files been loaded. @@ -63,14 +65,14 @@ # discoverable conftest.py local plugins. # ------------------------------------------------------------------------- -@hookspec_opts(firstresult=True) +@hookspec(firstresult=True) def pytest_cmdline_parse(pluginmanager, args): """return initialized config object, parsing the specified args. """ def pytest_cmdline_preparse(config, args): """(deprecated) modify command line arguments before option parsing. """ -@hookspec_opts(firstresult=True) +@hookspec(firstresult=True) def pytest_cmdline_main(config): """ called for performing the main command line action. The default implementation will invoke the configure hooks and runtest_mainloop. """ @@ -84,7 +86,7 @@ # collection hooks # ------------------------------------------------------------------------- -@hookspec_opts(firstresult=True) +@hookspec(firstresult=True) def pytest_collection(session): """ perform the collection protocol for the given session. """ @@ -95,14 +97,14 @@ def pytest_collection_finish(session): """ called after collection has been performed and modified. """ -@hookspec_opts(firstresult=True) +@hookspec(firstresult=True) def pytest_ignore_collect(path, config): """ return True to prevent considering this path for collection. This hook is consulted for all files and directories prior to calling more specific hooks. """ -@hookspec_opts(firstresult=True) +@hookspec(firstresult=True) def pytest_collect_directory(path, parent): """ called before traversing a directory for collection files. """ @@ -123,7 +125,7 @@ def pytest_deselected(items): """ called for test items deselected by keyword. """ -@hookspec_opts(firstresult=True) +@hookspec(firstresult=True) def pytest_make_collect_report(collector): """ perform ``collector.collect()`` and return a CollectReport. """ @@ -131,7 +133,7 @@ # Python test function related hooks # ------------------------------------------------------------------------- -@hookspec_opts(firstresult=True) +@hookspec(firstresult=True) def pytest_pycollect_makemodule(path, parent): """ return a Module collector or None for the given path. This hook will be called for each matching test module path. @@ -139,11 +141,11 @@ create test modules for files that do not match as a test module. """ -@hookspec_opts(firstresult=True) +@hookspec(firstresult=True) def pytest_pycollect_makeitem(collector, name, obj): """ return custom item/collector for a python object in a module, or None. """ -@hookspec_opts(firstresult=True) +@hookspec(firstresult=True) def pytest_pyfunc_call(pyfuncitem): """ call underlying test function. """ @@ -154,7 +156,7 @@ # generic runtest related hooks # ------------------------------------------------------------------------- -@hookspec_opts(firstresult=True) +@hookspec(firstresult=True) def pytest_runtestloop(session): """ called for performing the main runtest loop (after collection finished). """ @@ -162,7 +164,7 @@ def pytest_itemstart(item, node): """ (deprecated, use pytest_runtest_logstart). """ -@hookspec_opts(firstresult=True) +@hookspec(firstresult=True) def pytest_runtest_protocol(item, nextitem): """ implements the runtest_setup/call/teardown protocol for the given test item, including capturing exceptions and calling @@ -195,7 +197,7 @@ so that nextitem only needs to call setup-functions. """ -@hookspec_opts(firstresult=True) +@hookspec(firstresult=True) def pytest_runtest_makereport(item, call): """ return a :py:class:`_pytest.runner.TestReport` object for the given :py:class:`pytest.Item` and @@ -240,7 +242,7 @@ def pytest_report_header(config, startdir): """ return a string to be displayed as header info for terminal reporting.""" -@hookspec_opts(firstresult=True) +@hookspec(firstresult=True) def pytest_report_teststatus(report): """ return result-category, shortletter and verbose word for reporting.""" @@ -256,7 +258,7 @@ # doctest hooks # ------------------------------------------------------------------------- -@hookspec_opts(firstresult=True) +@hookspec(firstresult=True) def pytest_doctest_prepare_content(content): """ return processed content for a given doctest""" diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 _pytest/main.py --- a/_pytest/main.py +++ b/_pytest/main.py @@ -501,23 +501,23 @@ def __init__(self, config): FSCollector.__init__(self, config.rootdir, parent=None, config=config, session=self) - self.config.pluginmanager.register(self, name="session") + self._fs2hookproxy = {} self._testsfailed = 0 self.shouldstop = False self.trace = config.trace.root.get("collection") self._norecursepatterns = config.getini("norecursedirs") self.startdir = py.path.local() - self._fs2hookproxy = {} + self.config.pluginmanager.register(self, name="session") def _makeid(self): return "" - @pytest.hookimpl_opts(tryfirst=True) + @pytest.hookimpl(tryfirst=True) def pytest_collectstart(self): if self.shouldstop: raise self.Interrupted(self.shouldstop) - @pytest.hookimpl_opts(tryfirst=True) + @pytest.hookimpl(tryfirst=True) def pytest_runtest_logreport(self, report): if report.failed and not hasattr(report, 'wasxfail'): self._testsfailed += 1 diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 _pytest/nose.py --- a/_pytest/nose.py +++ b/_pytest/nose.py @@ -24,7 +24,7 @@ call.excinfo = call2.excinfo -@pytest.hookimpl_opts(trylast=True) +@pytest.hookimpl(trylast=True) def pytest_runtest_setup(item): if is_potential_nosetest(item): if isinstance(item.parent, pytest.Generator): diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 _pytest/pastebin.py --- a/_pytest/pastebin.py +++ b/_pytest/pastebin.py @@ -11,7 +11,7 @@ choices=['failed', 'all'], help="send failed|all info to bpaste.net pastebin service.") -@pytest.hookimpl_opts(trylast=True) +@pytest.hookimpl(trylast=True) def pytest_configure(config): if config.option.pastebin == "all": tr = config.pluginmanager.getplugin('terminalreporter') diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 _pytest/pytester.py --- a/_pytest/pytester.py +++ b/_pytest/pytester.py @@ -13,7 +13,7 @@ import py import pytest from py.builtin import print_ -from _pytest.core import TracedHookExecution +from pluggy import _TracedHookExecution from _pytest.main import Session, EXIT_OK @@ -80,7 +80,7 @@ else: return True - @pytest.hookimpl_opts(hookwrapper=True, tryfirst=True) + @pytest.hookimpl(hookwrapper=True, tryfirst=True) def pytest_runtest_item(self, item): lines1 = self.get_open_files() yield @@ -198,7 +198,7 @@ self.calls.append(ParsedCall(hook.name, kwargs)) def after(outcome, hook, method, kwargs): pass - executor = TracedHookExecution(pluginmanager, before, after) + executor = _TracedHookExecution(pluginmanager, before, after) self._undo_wrapping = executor.undo def finish_recording(self): @@ -712,8 +712,20 @@ option "--runpytest" and return a :py:class:`RunResult`. """ + args = self._ensure_basetemp(args) return self._runpytest_method(*args, **kwargs) + def _ensure_basetemp(self, args): + args = [str(x) for x in args] + for x in args: + if str(x).startswith('--basetemp'): + #print ("basedtemp exists: %s" %(args,)) + break + else: + args.append("--basetemp=%s" % self.tmpdir.dirpath('basetemp')) + #print ("added basetemp: %s" %(args,)) + return args + def parseconfig(self, *args): """Return a new py.test Config instance from given commandline args. @@ -726,12 +738,8 @@ modules which will be registered with the PluginManager. """ - args = [str(x) for x in args] - for x in args: - if str(x).startswith('--basetemp'): - break - else: - args.append("--basetemp=%s" % self.tmpdir.dirpath('basetemp')) + args = self._ensure_basetemp(args) + import _pytest.config config = _pytest.config._prepareconfig(args, self.plugins) # we don't know what the test will do with this half-setup config diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 _pytest/python.py --- a/_pytest/python.py +++ b/_pytest/python.py @@ -8,7 +8,11 @@ from py._code.code import TerminalRepr import _pytest -cutdir = py.path.local(_pytest.__file__).dirpath() +import pluggy + +cutdir2 = py.path.local(_pytest.__file__).dirpath() +cutdir1 = py.path.local(pluggy.__file__.rstrip("oc")) + NoneType = type(None) NOTSET = object() @@ -18,6 +22,11 @@ # used to work around a python2 exception info leak exc_clear = getattr(sys, 'exc_clear', lambda: None) + +def filter_traceback(entry): + return entry.path != cutdir1 and not entry.path.relto(cutdir2) + + def getfslineno(obj): # xxx let decorators etc specify a sane ordering while hasattr(obj, "__wrapped__"): @@ -172,7 +181,7 @@ def pytest_sessionstart(session): session._fixturemanager = FixtureManager(session) -@pytest.hookimpl_opts(trylast=True) +@pytest.hookimpl(trylast=True) def pytest_namespace(): raises.Exception = pytest.fail.Exception return { @@ -191,7 +200,7 @@ return request.config -@pytest.hookimpl_opts(trylast=True) +@pytest.hookimpl(trylast=True) def pytest_pyfunc_call(pyfuncitem): testfunction = pyfuncitem.obj if pyfuncitem._isyieldedfunction(): @@ -219,7 +228,7 @@ def pytest_pycollect_makemodule(path, parent): return Module(path, parent) -@pytest.hookimpl_opts(hookwrapper=True) +@pytest.hookimpl(hookwrapper=True) def pytest_pycollect_makeitem(collector, name, obj): outcome = yield res = outcome.get_result() @@ -604,7 +613,11 @@ if ntraceback == traceback: ntraceback = ntraceback.cut(path=path) if ntraceback == traceback: - ntraceback = ntraceback.cut(excludepath=cutdir) + #ntraceback = ntraceback.cut(excludepath=cutdir2) + ntraceback = ntraceback.filter(filter_traceback) + if not ntraceback: + ntraceback = traceback + excinfo.traceback = ntraceback.filter() # issue364: mark all but first and last frames to # only show a single-line message for each frame diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 _pytest/skipping.py --- a/_pytest/skipping.py +++ b/_pytest/skipping.py @@ -133,7 +133,7 @@ return expl -@pytest.hookimpl_opts(tryfirst=True) +@pytest.hookimpl(tryfirst=True) def pytest_runtest_setup(item): evalskip = MarkEvaluator(item, 'skipif') if evalskip.istrue(): @@ -151,7 +151,7 @@ if not evalxfail.get('run', True): pytest.xfail("[NOTRUN] " + evalxfail.getexplanation()) -@pytest.hookimpl_opts(hookwrapper=True) +@pytest.hookimpl(hookwrapper=True) def pytest_runtest_makereport(item, call): outcome = yield rep = outcome.get_result() diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 _pytest/terminal.py --- a/_pytest/terminal.py +++ b/_pytest/terminal.py @@ -3,6 +3,7 @@ This is a good source for looking at the various reporting hooks. """ import pytest +import pluggy import py import sys import time @@ -267,7 +268,7 @@ def pytest_collection_modifyitems(self): self.report_collect(True) - @pytest.hookimpl_opts(trylast=True) + @pytest.hookimpl(trylast=True) def pytest_sessionstart(self, session): self._sessionstarttime = time.time() if not self.showheader: @@ -278,7 +279,8 @@ if hasattr(sys, 'pypy_version_info'): verinfo = ".".join(map(str, sys.pypy_version_info[:3])) msg += "[pypy-%s-%s]" % (verinfo, sys.pypy_version_info[3]) - msg += " -- py-%s -- pytest-%s" % (py.__version__, pytest.__version__) + msg += ", pytest-%s, py-%s, pluggy-%s" % ( + pytest.__version__, py.__version__, pluggy.__version__) if self.verbosity > 0 or self.config.option.debug or \ getattr(self.config.option, 'pastebin', None): msg += " -- " + str(sys.executable) @@ -294,10 +296,11 @@ if config.inifile: inifile = config.rootdir.bestrelpath(config.inifile) lines = ["rootdir: %s, inifile: %s" %(config.rootdir, inifile)] - plugininfo = config.pluginmanager._plugin_distinfo + + plugininfo = config.pluginmanager.list_plugin_distinfo() if plugininfo: l = [] - for dist, plugin in plugininfo: + for plugin, dist in plugininfo: name = dist.project_name if name.startswith("pytest-"): name = name[7:] @@ -352,7 +355,7 @@ indent = (len(stack) - 1) * " " self._tw.line("%s%s" % (indent, col)) - @pytest.hookimpl_opts(hookwrapper=True) + @pytest.hookimpl(hookwrapper=True) def pytest_sessionfinish(self, exitstatus): outcome = yield outcome.get_result() diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 _pytest/unittest.py --- a/_pytest/unittest.py +++ b/_pytest/unittest.py @@ -140,7 +140,7 @@ if traceback: excinfo.traceback = traceback -@pytest.hookimpl_opts(tryfirst=True) +@pytest.hookimpl(tryfirst=True) def pytest_runtest_makereport(item, call): if isinstance(item, TestCaseFunction): if item._excinfo: @@ -152,7 +152,7 @@ # twisted trial support -@pytest.hookimpl_opts(hookwrapper=True) +@pytest.hookimpl(hookwrapper=True) def pytest_runtest_protocol(item): if isinstance(item, TestCaseFunction) and \ 'twisted.trial.unittest' in sys.modules: diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 doc/en/example/markers.txt --- a/doc/en/example/markers.txt +++ b/doc/en/example/markers.txt @@ -201,9 +201,9 @@ @pytest.mark.usefixtures(fixturename1, fixturename2, ...): mark tests as needing all of the specified fixtures. see http://pytest.org/latest/fixture.html#usefixtures - @pytest.hookimpl_opts(tryfirst=True): mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible. + @pytest.hookimpl(tryfirst=True): mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible. - @pytest.hookimpl_opts(trylast=True): mark a hook implementation function such that the plugin machinery will try to call it last/as late as possible. + @pytest.hookimpl(trylast=True): mark a hook implementation function such that the plugin machinery will try to call it last/as late as possible. For an example on how to add and work with markers from a plugin, see @@ -375,9 +375,9 @@ @pytest.mark.usefixtures(fixturename1, fixturename2, ...): mark tests as needing all of the specified fixtures. see http://pytest.org/latest/fixture.html#usefixtures - @pytest.hookimpl_opts(tryfirst=True): mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible. + @pytest.hookimpl(tryfirst=True): mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible. - @pytest.hookimpl_opts(trylast=True): mark a hook implementation function such that the plugin machinery will try to call it last/as late as possible. + @pytest.hookimpl(trylast=True): mark a hook implementation function such that the plugin machinery will try to call it last/as late as possible. Reading markers which were set from multiple places diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 doc/en/example/simple.txt --- a/doc/en/example/simple.txt +++ b/doc/en/example/simple.txt @@ -534,7 +534,7 @@ import pytest import os.path - @pytest.hookimpl_opts(tryfirst=True) + @pytest.hookimpl(tryfirst=True) def pytest_runtest_makereport(item, call, __multicall__): # execute all other hooks to obtain the report object rep = __multicall__.execute() @@ -607,7 +607,7 @@ import pytest - @pytest.hookimpl_opts(tryfirst=True) + @pytest.hookimpl(tryfirst=True) def pytest_runtest_makereport(item, call, __multicall__): # execute all other hooks to obtain the report object rep = __multicall__.execute() diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 doc/en/goodpractises.txt --- a/doc/en/goodpractises.txt +++ b/doc/en/goodpractises.txt @@ -169,6 +169,14 @@ python runtests.py +.. note:: + + You must have pytest and its dependencies installed as an sdist, not + as wheels because genscript need the source code for generating a + standalone script. + + + Integrating with distutils / ``python setup.py test`` -------------------------------------------------------- diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 doc/en/writing_plugins.txt --- a/doc/en/writing_plugins.txt +++ b/doc/en/writing_plugins.txt @@ -292,7 +292,7 @@ import pytest - @pytest.hookimpl_opts(hookwrapper=True) + @pytest.hookimpl(hookwrapper=True) def pytest_pyfunc_call(pyfuncitem): # do whatever you want before the next hook executes @@ -305,8 +305,7 @@ Note that hook wrappers don't return results themselves, they merely perform tracing or other side effects around the actual hook implementations. If the result of the underlying hook is a mutable object, they may modify -that result, however. - +that result but it's probably better to avoid it. Hook function ordering / call example @@ -338,16 +337,24 @@ Here is the order of execution: 1. Plugin3's pytest_collection_modifyitems called until the yield point -2. Plugin1's pytest_collection_modifyitems is called -3. Plugin2's pytest_collection_modifyitems is called -4. Plugin3's pytest_collection_modifyitems called for executing after the yield - The yield receives a :py:class:`CallOutcome` instance which encapsulates - the result from calling the non-wrappers. Wrappers cannot modify the result. + because it is a hook wrapper. + +2. Plugin1's pytest_collection_modifyitems is called because it is marked + with ``tryfirst=True``. + +3. Plugin2's pytest_collection_modifyitems is called because it is marked + with ``trylast=True`` (but even without this mark it would come after + Plugin1). + +4. Plugin3's pytest_collection_modifyitems then executing the code after the yield + point. The yield receives a :py:class:`CallOutcome` instance which encapsulates + the result from calling the non-wrappers. Wrappers shall not modify the result. It's possible to use ``tryfirst`` and ``trylast`` also in conjunction with ``hookwrapper=True`` in which case it will influence the ordering of hookwrappers among each other. + Declaring new hooks ------------------------ @@ -368,11 +375,11 @@ .. _`newhooks.py`: https://bitbucket.org/pytest-dev/pytest-xdist/src/52082f70e7dd04b00361091b8af906c60fd6700f/xdist/newhooks.py?at=default -Using hooks from 3rd party plugins -------------------------------------- +Optionally using hooks from 3rd party plugins +--------------------------------------------- Using new hooks from plugins as explained above might be a little tricky -because the standard :ref:`validation mechanism <validation>`: +because of the standard :ref:`validation mechanism <validation>`: if you depend on a plugin that is not installed, validation will fail and the error message will not make much sense to your users. @@ -395,7 +402,6 @@ This has the added benefit of allowing you to conditionally install hooks depending on which plugins are installed. - .. _`well specified hooks`: .. currentmodule:: _pytest.hookspec diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 pytest.py --- a/pytest.py +++ b/pytest.py @@ -11,8 +11,10 @@ # else we are imported -from _pytest.config import main, UsageError, _preloadplugins, cmdline -from _pytest.core import hookspec_opts, hookimpl_opts +from _pytest.config import ( + main, UsageError, _preloadplugins, cmdline, + hookspec, hookimpl +) from _pytest import __version__ _preloadplugins() # to populate pytest.* namespace so help(pytest) works diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 setup.py --- a/setup.py +++ b/setup.py @@ -48,7 +48,7 @@ def main(): - install_requires = ['py>=1.4.27.dev2'] + install_requires = ['py>=1.4.27.dev2', 'pluggy>=0.2.0,<0.3.0'] extras_require = {} if has_environment_marker_support(): extras_require[':python_version=="2.6" or python_version=="3.0" or python_version=="3.1"'] = ['argparse'] diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 testing/python/collect.py --- a/testing/python/collect.py +++ b/testing/python/collect.py @@ -559,7 +559,7 @@ b = testdir.mkdir("a").mkdir("b") b.join("conftest.py").write(py.code.Source(""" import pytest - @pytest.hookimpl_opts(hookwrapper=True) + @pytest.hookimpl(hookwrapper=True) def pytest_pycollect_makeitem(): outcome = yield if outcome.excinfo is None: diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 testing/test_config.py --- a/testing/test_config.py +++ b/testing/test_config.py @@ -357,9 +357,9 @@ pm.register(m) hc = pm.hook.pytest_load_initial_conftests l = hc._nonwrappers + hc._wrappers - assert l[-1].__module__ == "_pytest.capture" - assert l[-2] == m.pytest_load_initial_conftests - assert l[-3].__module__ == "_pytest.config" + assert l[-1].function.__module__ == "_pytest.capture" + assert l[-2].function == m.pytest_load_initial_conftests + assert l[-3].function.__module__ == "_pytest.config" class TestWarning: def test_warn_config(self, testdir): This diff is so big that we needed to truncate the remainder. Repository URL: https://bitbucket.org/pytest-dev/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. _______________________________________________ pytest-commit mailing list pytest-commit@python.org https://mail.python.org/mailman/listinfo/pytest-commit