22Validates all hook registrations in all plugins
33"""
44import importlib
5- import string
5+ import re
66from numbers import Number
77from pathlib import Path
88
99from sqlalchemy import MetaData
1010
11+ from cloudbot .event import Event , CommandEvent , RegexEvent , CapEvent , PostHookEvent , IrcOutEvent
1112from cloudbot .hook import Action
1213from cloudbot .plugin import Plugin , Hook
14+ from cloudbot .util import database
15+
16+ database .metadata = MetaData ()
17+ Hook .original_init = Hook .__init__
18+
19+ DOC_RE = re .compile (r"^(?:(?:<.+?>|{.+?}|\[.+?\]).+?)*?-\s.+$" )
20+ PLUGINS = []
21+
22+
23+ class MockBot :
24+ def __init__ (self ):
25+ self .loop = None
1326
1427
1528def patch_hook_init (self , _type , plugin , func_hook ):
1629 self .original_init (_type , plugin , func_hook )
30+ self .func_hook = func_hook
31+
1732
18- assert not func_hook .kwargs , \
19- "Unknown arguments '{}' passed during registration of hook '{}'" .format (func_hook .kwargs , self .function_name )
33+ Hook .__init__ = patch_hook_init
2034
2135
2236def gather_plugins ():
@@ -25,12 +39,7 @@ def gather_plugins():
2539 return path_list
2640
2741
28- def load_plugin (plugin_path , monkeypatch ):
29- monkeypatch .setattr ('cloudbot.plugin.Hook.original_init' , Hook .__init__ , raising = False )
30- monkeypatch .setattr ('cloudbot.plugin.Hook.__init__' , patch_hook_init )
31-
32- monkeypatch .setattr ('cloudbot.util.database.metadata' , MetaData ())
33-
42+ def load_plugin (plugin_path ):
3443 path = Path (plugin_path )
3544 file_path = path .resolve ()
3645 file_name = file_path .name
@@ -45,10 +54,23 @@ def load_plugin(plugin_path, monkeypatch):
4554 return Plugin (str (file_path ), file_name , title , plugin_module )
4655
4756
57+ def get_plugins ():
58+ if not PLUGINS :
59+ PLUGINS .extend (map (load_plugin , gather_plugins ()))
60+
61+ return PLUGINS
62+
63+
4864def pytest_generate_tests (metafunc ):
49- if 'plugin_path' in metafunc .fixturenames :
50- paths = list (gather_plugins ())
51- metafunc .parametrize ('plugin_path' , paths , ids = list (map (str , paths )))
65+ if 'plugin' in metafunc .fixturenames :
66+ plugins = get_plugins ()
67+ metafunc .parametrize ('plugin' , plugins , ids = [plugin .title for plugin in plugins ])
68+ elif 'hook' in metafunc .fixturenames :
69+ plugins = get_plugins ()
70+ hooks = [hook for plugin in plugins for hook_list in plugin .hooks .values () for hook in hook_list ]
71+ metafunc .parametrize (
72+ 'hook' , hooks , ids = ["{}.{}" .format (hook .plugin .title , hook .function_name ) for hook in hooks ]
73+ )
5274
5375
5476HOOK_ATTR_TYPES = {
@@ -67,15 +89,12 @@ def pytest_generate_tests(metafunc):
6789}
6890
6991
70- def test_plugin (plugin_path , monkeypatch ):
71- plugin = load_plugin (plugin_path , monkeypatch )
72- for hooks in plugin .hooks .values ():
73- for hook in hooks :
74- _test_hook (hook )
75-
92+ def test_hook_kwargs (hook ):
93+ assert not hook .func_hook .kwargs , \
94+ "Unknown arguments '{}' passed during registration of hook '{}'" .format (
95+ hook .func_hook .kwargs , hook .function_name
96+ )
7697
77- def _test_hook (hook ):
78- assert 'async' not in hook .required_args , "Use of deprecated function Event.async"
7998 for name , types in HOOK_ATTR_TYPES .items ():
8099 try :
81100 attr = getattr (hook , name )
@@ -85,6 +104,33 @@ def _test_hook(hook):
85104 assert isinstance (attr , types ), \
86105 "Unexpected type '{}' for hook attribute '{}'" .format (type (attr ).__name__ , name )
87106
107+
108+ def test_hook_doc (hook ):
88109 if hook .type == "command" and hook .doc :
89- assert hook .doc [: 1 ] not in "." + string . ascii_letters , \
110+ assert DOC_RE . match ( hook .doc ) , \
90111 "Invalid docstring '{}' format for command hook" .format (hook .doc )
112+
113+
114+ def test_hook_args (hook ):
115+ assert 'async' not in hook .required_args , "Use of deprecated function Event.async"
116+
117+ bot = MockBot ()
118+ if hook .type in ("irc_raw" , "perm_check" , "periodic" , "on_start" , "on_stop" , "event" , "on_connect" ):
119+ event = Event (bot = bot )
120+ elif hook .type == "command" :
121+ event = CommandEvent (bot = bot , hook = hook , text = "" , triggered_command = "" )
122+ elif hook .type == "regex" :
123+ event = RegexEvent (bot = bot , hook = hook , match = None )
124+ elif hook .type .startswith ("on_cap" ):
125+ event = CapEvent (bot = bot , cap = "" )
126+ elif hook .type == "post_hook" :
127+ event = PostHookEvent (bot = bot )
128+ elif hook .type == "irc_out" :
129+ event = IrcOutEvent (bot = bot )
130+ elif hook .type == "sieve" :
131+ return
132+ else :
133+ assert False , "Unhandled hook type '{}' in tests" .format (hook .type )
134+
135+ for arg in hook .required_args :
136+ assert hasattr (event , arg ), "Undefined parameter '{}' for hook function" .format (arg )
0 commit comments