[py-svn] py-trunk commit 70c81c004272: refine and test new hook registration, now it is called "pytest_addhooks"

commits-noreply at bitbucket.org commits-noreply at bitbucket.org
Sun May 2 16:36:29 CEST 2010


# HG changeset patch -- Bitbucket.org
# Project py-trunk
# URL http://bitbucket.org/hpk42/py-trunk/overview
# User holger krekel <holger at merlinux.eu>
# Date 1272811013 -7200
# Node ID 70c81c0042722c892a653199e024ccf948afda5c
# Parent  2e8ad1a4fd49b2f6c8b8140ee4581e4ca89e3684
refine and test new hook registration, now it is called "pytest_addhooks"
similar to pytest_addoption and raises on bogus input.

--- a/py/_test/pluginmanager.py
+++ b/py/_test/pluginmanager.py
@@ -39,8 +39,7 @@ class PluginManager(object):
         if name in self._name2plugin:
             return False
         self._name2plugin[name] = plugin
-        self.call_plugin(plugin, "pytest_registerhooks", 
-            {'pluginmanager': self})
+        self.call_plugin(plugin, "pytest_addhooks", {'pluginmanager': self})
         self.hook.pytest_plugin_registered(manager=self, plugin=plugin)
         self.registry.register(plugin)
         return True
@@ -59,8 +58,8 @@ class PluginManager(object):
             if plugin == val:
                 return True
 
-    def registerhooks(self, spec):
-        self.hook._registerhooks(spec)
+    def addhooks(self, spec):
+        self.hook._addhooks(spec, prefix="pytest_")
 
     def getplugins(self):
         return list(self.registry)
@@ -304,22 +303,31 @@ class Registry:
         return l
 
 class HookRelay: 
-    def __init__(self, hookspecs, registry):
+    def __init__(self, hookspecs, registry, prefix="pytest_"):
         if not isinstance(hookspecs, list):
             hookspecs = [hookspecs]
         self._hookspecs = []
         self._registry = registry
         for hookspec in hookspecs:
-            self._registerhooks(hookspec)
+            self._addhooks(hookspec, prefix)
 
-    def _registerhooks(self, hookspecs):
+    def _addhooks(self, hookspecs, prefix):
         self._hookspecs.append(hookspecs)
+        added = False
         for name, method in vars(hookspecs).items():
-            if name[:1] != "_":
+            if name.startswith(prefix):
+                if not method.__doc__:
+                    raise ValueError("docstring required for hook %r, in %r"
+                        % (method, hookspecs))
                 firstresult = getattr(method, 'firstresult', False)
                 hc = HookCaller(self, name, firstresult=firstresult)
                 setattr(self, name, hc)
+                added = True
                 #print ("setting new hook", name)
+        if not added:
+            raise ValueError("did not find new %r hooks in %r" %(
+                prefix, hookspecs,))
+            
 
     def _performcall(self, name, multicall):
         return multicall.execute()

--- a/py/_test/cmdline.py
+++ b/py/_test/cmdline.py
@@ -21,4 +21,3 @@ def main(args=None):
         e = sys.exc_info()[1]
         sys.stderr.write("ERROR: %s\n" %(e.args[0],))
         raise SystemExit(3)
-

--- a/testing/test_pluginmanager.py
+++ b/testing/test_pluginmanager.py
@@ -202,6 +202,58 @@ class TestBootstrapping:
             impname = canonical_importname(name)
 
 class TestPytestPluginInteractions:
+
+    def test_addhooks_conftestplugin(self, testdir):
+        from py._test.config import Config 
+        newhooks = testdir.makepyfile(newhooks="""
+            def pytest_myhook(xyz):
+                "new hook"
+        """)
+        conf = testdir.makeconftest("""
+            import sys ; sys.path.insert(0, '.')
+            import newhooks
+            def pytest_addhooks(pluginmanager):
+                pluginmanager.addhooks(newhooks)
+            def pytest_myhook(xyz):
+                return xyz + 1
+        """)
+        config = Config() 
+        config._conftest.importconftest(conf)
+        print(config.pluginmanager.getplugins())
+        res = config.hook.pytest_myhook(xyz=10)
+        assert res == [11]
+
+    def test_addhooks_docstring_error(self, testdir):
+        newhooks = testdir.makepyfile(newhooks="""
+            class A: # no pytest_ prefix
+                pass
+            def pytest_myhook(xyz):
+                pass
+        """)
+        conf = testdir.makeconftest("""
+            import sys ; sys.path.insert(0, '.')
+            import newhooks
+            def pytest_addhooks(pluginmanager):
+                pluginmanager.addhooks(newhooks)
+        """)
+        res = testdir.runpytest()
+        assert res.ret != 0
+        res.stderr.fnmatch_lines([
+            "*docstring*pytest_myhook*newhooks*"
+        ])
+
+    def test_addhooks_nohooks(self, testdir):
+        conf = testdir.makeconftest("""
+            import sys 
+            def pytest_addhooks(pluginmanager):
+                pluginmanager.addhooks(sys)
+        """)
+        res = testdir.runpytest()
+        assert res.ret != 0
+        res.stderr.fnmatch_lines([
+            "*did not find*sys*"
+        ])
+
     def test_do_option_conftestplugin(self, testdir):
         from py._test.config import Config 
         p = testdir.makepyfile("""
@@ -401,9 +453,9 @@ class TestHookRelay:
         registry = Registry()
         class Api:
             def hello(self, arg):
-                pass
+                "api hook 1"
 
-        mcm = HookRelay(hookspecs=Api, registry=registry)
+        mcm = HookRelay(hookspecs=Api, registry=registry, prefix="he")
         assert hasattr(mcm, 'hello')
         assert repr(mcm.hello).find("hello") != -1
         class Plugin:
@@ -418,17 +470,18 @@ class TestHookRelay:
         registry = Registry()
         class Api:
             def hello(self, arg):
-                pass
-        mcm = HookRelay(hookspecs=Api, registry=registry)
+                "api hook 1"
+        mcm = HookRelay(hookspecs=Api, registry=registry, prefix="he")
         py.test.raises(TypeError, "mcm.hello(3)")
 
     def test_firstresult_definition(self):
         registry = Registry()
         class Api:
-            def hello(self, arg): pass
+            def hello(self, arg): 
+                "api hook 1"
             hello.firstresult = True
 
-        mcm = HookRelay(hookspecs=Api, registry=registry)
+        mcm = HookRelay(hookspecs=Api, registry=registry, prefix="he")
         class Plugin:
             def hello(self, arg):
                 return arg + 1

--- a/py/_plugin/hookspec.py
+++ b/py/_plugin/hookspec.py
@@ -6,14 +6,14 @@ hook specifications for py.test plugins
 # Command line and configuration 
 # -------------------------------------------------------------------------
 
+def pytest_namespace():
+    "return dict of name->object which will get stored at py.test. namespace"
+
 def pytest_addoption(parser):
-    """ called before commandline parsing.  """
+    "add optparse-style options via parser.addoption."
 
-def pytest_registerhooks(pluginmanager):
-    """ called after commandline parsing before pytest_configure.  """
-
-def pytest_namespace():
-    """ return dict of name->object which will get stored at py.test. namespace"""
+def pytest_addhooks(pluginmanager):
+    "add hooks via pluginmanager.registerhooks(module)"
 
 def pytest_configure(config):
     """ called after command line options have been parsed. 

--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,9 +1,26 @@
 Changes between 1.2.1 and 1.3.0 (release pending)
 ==================================================
 
-- new mechanism to allow external plugins to register new hooks 
-  (a recent pytest-xdist plugin for distributed and looponfailing 
-  testing requires this feature)
+- allow external plugins to register new hooks via the new 
+  pytest_addhooks(pluginmanager) hook.  The new release of
+  the pytest-xdist plugin for distributed and looponfailing 
+  testing requires this feature. 
+- add a new pytest_ignore_collect(path, config) hook to allow projects and
+  plugins to define exclusion behaviour for their directory structure - 
+  for example you may define in a conftest.py this method:
+        def pytest_ignore_collect(path):
+            return path.check(link=1)
+  to prevent even a collection try of any tests in symlinked dirs. 
+- new pytest_pycollect_makemodule(path, parent) hook for
+  allowing customization of the Module collection object for a 
+  matching test module. 
+- expose (previously internal) commonly useful methods: 
+  py.io.get_terminal_with() -> return terminal width
+  py.io.ansi_print(...) -> print colored/bold text on linux/win32
+  py.io.saferepr(obj) -> return limited representation string
+- expose test outcome related exceptions as py.test.skip.Exception, 
+  py.test.raises.Exception etc., useful mostly for plugins
+  doing special outcome interpretation/tweaking
 - (issue85) fix junitxml plugin to handle tests with non-ascii output
 - fix/refine python3 compatibility (thanks Benjamin Peterson)
 - fixes for making the jython/win32 combination work, note however: 
@@ -13,22 +30,6 @@ Changes between 1.2.1 and 1.3.0 (release
 - fixes for handling of unicode exception values and unprintable objects
 - (issue87) fix unboundlocal error in assertionold code 
 - (issue86) improve documentation for looponfailing
-- add a new pytest_ignore_collect(path, config) hook to allow projects and
-  plugins to define exclusion behaviour for their directory structure - 
-  for example you may define in a conftest.py this method:
-        def pytest_ignore_collect(path):
-            return path.check(link=1)
-  to prevent even a collection try of any tests in symlinked dirs. 
-- new pytest_pycollect_makemodule(path, parent) hook for
-  allowing customization of the Module collection object for a 
-  matching test module.
-- expose (previously internal) commonly useful methods: 
-  py.io.get_terminal_with() -> return terminal width
-  py.io.ansi_print(...) -> print colored/bold text on linux/win32
-  py.io.saferepr(obj) -> return limited representation string
-- expose test outcome related exceptions as py.test.skip.Exception, 
-  py.test.raises.Exception etc., useful mostly for plugins
-  doing special outcome interpretation/tweaking
 - refine IO capturing: stdin-redirect pseudo-file now has a NOP close() method
 - ship distribute_setup.py version 0.6.10 
 - added links to the new capturelog and coverage plugins



More information about the pytest-commit mailing list