Merge remote-tracking branch 'refs/remotes/elrond/misc/pytest_enable_testing'
[mediagoblin.git] / mediagoblin / tests / test_pluginapi.py
1 # GNU MediaGoblin -- federated, autonomous media hosting
2 # Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
3 #
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License as published by
6 # the Free Software Foundation, either version 3 of the License, or
7 # (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU Affero General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17 import sys
18
19 from configobj import ConfigObj
20 import pytest
21 import pkg_resources
22 from validate import VdtTypeError
23
24 from mediagoblin import mg_globals
25 from mediagoblin.init.plugins import setup_plugins
26 from mediagoblin.init.config import read_mediagoblin_config
27 from mediagoblin.tools import pluginapi
28 from mediagoblin.tests.tools import get_app
29
30
31 def with_cleanup(*modules_to_delete):
32 def _with_cleanup(fun):
33 """Wrapper that saves and restores mg_globals"""
34 def _with_cleanup_inner(*args, **kwargs):
35 old_app_config = mg_globals.app_config
36 old_global_config = mg_globals.global_config
37 # Need to delete icky modules before and after so as to make
38 # sure things work correctly.
39 for module in modules_to_delete:
40 try:
41 del sys.modules[module]
42 except KeyError:
43 pass
44 # The plugin cache gets populated as a side-effect of
45 # importing, so it's best to clear it before and after a test.
46 pman = pluginapi.PluginManager()
47 pman.clear()
48 try:
49 return fun(*args, **kwargs)
50 finally:
51 mg_globals.app_config = old_app_config
52 mg_globals.global_config = old_global_config
53 # Need to delete icky modules before and after so as to make
54 # sure things work correctly.
55 for module in modules_to_delete:
56 try:
57 del sys.modules[module]
58 except KeyError:
59 pass
60 pman.clear()
61
62 _with_cleanup_inner.__name__ = fun.__name__
63 return _with_cleanup_inner
64 return _with_cleanup
65
66
67 def build_config(sections):
68 """Builds a ConfigObj object with specified data
69
70 :arg sections: list of ``(section_name, section_data,
71 subsection_list)`` tuples where section_data is a dict and
72 subsection_list is a list of ``(section_name, section_data,
73 subsection_list)``, ...
74
75 For example:
76
77 >>> build_config([
78 ... ('mediagoblin', {'key1': 'val1'}, []),
79 ... ('section2', {}, [
80 ... ('subsection1', {}, [])
81 ... ])
82 ... ])
83 """
84 cfg = ConfigObj()
85 cfg.filename = 'foo'
86 def _iter_section(cfg, section_list):
87 for section_name, data, subsection_list in section_list:
88 cfg[section_name] = data
89 _iter_section(cfg[section_name], subsection_list)
90
91 _iter_section(cfg, sections)
92 return cfg
93
94
95 @with_cleanup()
96 def test_no_plugins():
97 """Run setup_plugins with no plugins in config"""
98 cfg = build_config([('mediagoblin', {}, [])])
99 mg_globals.app_config = cfg['mediagoblin']
100 mg_globals.global_config = cfg
101
102 pman = pluginapi.PluginManager()
103 setup_plugins()
104
105 # Make sure we didn't load anything.
106 assert len(pman.plugins) == 0
107
108
109 @with_cleanup('mediagoblin.plugins.sampleplugin')
110 def test_one_plugin():
111 """Run setup_plugins with a single working plugin"""
112 cfg = build_config([
113 ('mediagoblin', {}, []),
114 ('plugins', {}, [
115 ('mediagoblin.plugins.sampleplugin', {}, [])
116 ])
117 ])
118
119 mg_globals.app_config = cfg['mediagoblin']
120 mg_globals.global_config = cfg
121
122 pman = pluginapi.PluginManager()
123 setup_plugins()
124
125 # Make sure we only found one plugin
126 assert len(pman.plugins) == 1
127 # Make sure the plugin is the one we think it is.
128 assert pman.plugins[0] == 'mediagoblin.plugins.sampleplugin'
129 # Make sure there was one hook registered
130 assert len(pman.hooks) == 1
131 # Make sure _setup_plugin_called was called once
132 import mediagoblin.plugins.sampleplugin
133 assert mediagoblin.plugins.sampleplugin._setup_plugin_called == 1
134
135
136 @with_cleanup('mediagoblin.plugins.sampleplugin')
137 def test_same_plugin_twice():
138 """Run setup_plugins with a single working plugin twice"""
139 cfg = build_config([
140 ('mediagoblin', {}, []),
141 ('plugins', {}, [
142 ('mediagoblin.plugins.sampleplugin', {}, []),
143 ('mediagoblin.plugins.sampleplugin', {}, []),
144 ])
145 ])
146
147 mg_globals.app_config = cfg['mediagoblin']
148 mg_globals.global_config = cfg
149
150 pman = pluginapi.PluginManager()
151 setup_plugins()
152
153 # Make sure we only found one plugin
154 assert len(pman.plugins) == 1
155 # Make sure the plugin is the one we think it is.
156 assert pman.plugins[0] == 'mediagoblin.plugins.sampleplugin'
157 # Make sure there was one hook registered
158 assert len(pman.hooks) == 1
159 # Make sure _setup_plugin_called was called once
160 import mediagoblin.plugins.sampleplugin
161 assert mediagoblin.plugins.sampleplugin._setup_plugin_called == 1
162
163
164 @with_cleanup()
165 def test_disabled_plugin():
166 """Run setup_plugins with a single working plugin twice"""
167 cfg = build_config([
168 ('mediagoblin', {}, []),
169 ('plugins', {}, [
170 ('-mediagoblin.plugins.sampleplugin', {}, []),
171 ])
172 ])
173
174 mg_globals.app_config = cfg['mediagoblin']
175 mg_globals.global_config = cfg
176
177 pman = pluginapi.PluginManager()
178 setup_plugins()
179
180 # Make sure we didn't load the plugin
181 assert len(pman.plugins) == 0
182
183
184 CONFIG_ALL_CALLABLES = [
185 ('mediagoblin', {}, []),
186 ('plugins', {}, [
187 ('mediagoblin.tests.testplugins.callables1', {}, []),
188 ('mediagoblin.tests.testplugins.callables2', {}, []),
189 ('mediagoblin.tests.testplugins.callables3', {}, []),
190 ])
191 ]
192
193
194 @with_cleanup()
195 def test_hook_handle():
196 """
197 Test the hook_handle method
198 """
199 cfg = build_config(CONFIG_ALL_CALLABLES)
200
201 mg_globals.app_config = cfg['mediagoblin']
202 mg_globals.global_config = cfg
203
204 setup_plugins()
205
206 # Just one hook provided
207 call_log = []
208 assert pluginapi.hook_handle(
209 "just_one", call_log) == "Called just once"
210 assert call_log == ["expect this one call"]
211
212 # Nothing provided and unhandled not okay
213 call_log = []
214 pluginapi.hook_handle(
215 "nothing_handling", call_log) == None
216 assert call_log == []
217
218 # Nothing provided and unhandled okay
219 call_log = []
220 assert pluginapi.hook_handle(
221 "nothing_handling", call_log, unhandled_okay=True) is None
222 assert call_log == []
223
224 # Multiple provided, go with the first!
225 call_log = []
226 assert pluginapi.hook_handle(
227 "multi_handle", call_log) == "the first returns"
228 assert call_log == ["Hi, I'm the first"]
229
230 # Multiple provided, one has CantHandleIt
231 call_log = []
232 assert pluginapi.hook_handle(
233 "multi_handle_with_canthandle",
234 call_log) == "the second returns"
235 assert call_log == ["Hi, I'm the second"]
236
237
238 @with_cleanup()
239 def test_hook_runall():
240 """
241 Test the hook_runall method
242 """
243 cfg = build_config(CONFIG_ALL_CALLABLES)
244
245 mg_globals.app_config = cfg['mediagoblin']
246 mg_globals.global_config = cfg
247
248 setup_plugins()
249
250 # Just one hook, check results
251 call_log = []
252 assert pluginapi.hook_runall(
253 "just_one", call_log) == ["Called just once"]
254 assert call_log == ["expect this one call"]
255
256 # None provided, check results
257 call_log = []
258 assert pluginapi.hook_runall(
259 "nothing_handling", call_log) == []
260 assert call_log == []
261
262 # Multiple provided, check results
263 call_log = []
264 assert pluginapi.hook_runall(
265 "multi_handle", call_log) == [
266 "the first returns",
267 "the second returns",
268 "the third returns",
269 ]
270 assert call_log == [
271 "Hi, I'm the first",
272 "Hi, I'm the second",
273 "Hi, I'm the third"]
274
275 # Multiple provided, one has CantHandleIt, check results
276 call_log = []
277 assert pluginapi.hook_runall(
278 "multi_handle_with_canthandle", call_log) == [
279 "the second returns",
280 "the third returns",
281 ]
282 assert call_log == [
283 "Hi, I'm the second",
284 "Hi, I'm the third"]
285
286
287 @with_cleanup()
288 def test_hook_transform():
289 """
290 Test the hook_transform method
291 """
292 cfg = build_config(CONFIG_ALL_CALLABLES)
293
294 mg_globals.app_config = cfg['mediagoblin']
295 mg_globals.global_config = cfg
296
297 setup_plugins()
298
299 assert pluginapi.hook_transform(
300 "expand_tuple", (-1, 0)) == (-1, 0, 1, 2, 3)
301
302
303 def test_plugin_config():
304 """
305 Make sure plugins can set up their own config
306 """
307 config, validation_result = read_mediagoblin_config(
308 pkg_resources.resource_filename(
309 'mediagoblin.tests', 'appconfig_plugin_specs.ini'))
310
311 pluginspec_section = config['plugins'][
312 'mediagoblin.tests.testplugins.pluginspec']
313 assert pluginspec_section['some_string'] == 'not blork'
314 assert pluginspec_section['dont_change_me'] == 'still the default'
315
316 # Make sure validation works... this should be an error
317 assert isinstance(
318 validation_result[
319 'plugins'][
320 'mediagoblin.tests.testplugins.pluginspec'][
321 'some_int'],
322 VdtTypeError)
323
324 # the callables thing shouldn't really have anything though.
325 assert len(config['plugins'][
326 'mediagoblin.tests.testplugins.callables1']) == 0
327
328
329 @pytest.fixture()
330 def context_modified_app(request):
331 """
332 Get a MediaGoblin app fixture using appconfig_context_modified.ini
333 """
334 return get_app(
335 request,
336 mgoblin_config=pkg_resources.resource_filename(
337 'mediagoblin.tests', 'appconfig_context_modified.ini'))
338
339
340 def test_modify_context(context_modified_app):
341 """
342 Test that we can modify both the view/template specific and
343 global contexts for templates.
344 """
345 # Specific thing passed into a page
346 result = context_modified_app.get("/modify_context/specific/")
347 assert result.body.strip() == """Specific page!
348
349 specific thing: in yer specificpage
350 global thing: globally appended!
351 something: orother
352 doubleme: happyhappy"""
353
354 # General test, should have global context variable only
355 result = context_modified_app.get("/modify_context/")
356 assert result.body.strip() == """General page!
357
358 global thing: globally appended!
359 lol: cats
360 doubleme: joyjoy"""