Improved test runtime from 352 seconds to 59 seconds by implementing an in-memory...
[mediagoblin.git] / mediagoblin / tests / test_pluginapi.py
CommitLineData
29b6f917
WKG
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
23055eb3 17import os
bdd23c0e 18import json
29b6f917 19import sys
70c06195 20
29b6f917 21from configobj import ConfigObj
70c06195 22import pytest
d3604e29
CAW
23import pkg_resources
24from validate import VdtTypeError
70c06195 25
29b6f917
WKG
26from mediagoblin import mg_globals
27from mediagoblin.init.plugins import setup_plugins
d3604e29 28from mediagoblin.init.config import read_mediagoblin_config
23055eb3 29from mediagoblin.gmg_commands.assetlink import link_plugin_assets
29b6f917 30from mediagoblin.tools import pluginapi
38103094 31from mediagoblin.tests.tools import get_app
23055eb3 32from mediagoblin.tools.common import CollectingPrinter
29b6f917
WKG
33
34
35def with_cleanup(*modules_to_delete):
36 def _with_cleanup(fun):
37 """Wrapper that saves and restores mg_globals"""
38 def _with_cleanup_inner(*args, **kwargs):
39 old_app_config = mg_globals.app_config
40 old_global_config = mg_globals.global_config
41 # Need to delete icky modules before and after so as to make
42 # sure things work correctly.
43 for module in modules_to_delete:
44 try:
45 del sys.modules[module]
46 except KeyError:
47 pass
48 # The plugin cache gets populated as a side-effect of
49 # importing, so it's best to clear it before and after a test.
05e007c1
WKG
50 pman = pluginapi.PluginManager()
51 pman.clear()
29b6f917
WKG
52 try:
53 return fun(*args, **kwargs)
54 finally:
55 mg_globals.app_config = old_app_config
56 mg_globals.global_config = old_global_config
57 # Need to delete icky modules before and after so as to make
58 # sure things work correctly.
59 for module in modules_to_delete:
60 try:
61 del sys.modules[module]
62 except KeyError:
63 pass
05e007c1 64 pman.clear()
29b6f917
WKG
65
66 _with_cleanup_inner.__name__ = fun.__name__
67 return _with_cleanup_inner
68 return _with_cleanup
69
70
71def build_config(sections):
72 """Builds a ConfigObj object with specified data
73
74 :arg sections: list of ``(section_name, section_data,
75 subsection_list)`` tuples where section_data is a dict and
76 subsection_list is a list of ``(section_name, section_data,
77 subsection_list)``, ...
78
79 For example:
80
81 >>> build_config([
82 ... ('mediagoblin', {'key1': 'val1'}, []),
83 ... ('section2', {}, [
84 ... ('subsection1', {}, [])
85 ... ])
86 ... ])
87 """
88 cfg = ConfigObj()
89 cfg.filename = 'foo'
90 def _iter_section(cfg, section_list):
91 for section_name, data, subsection_list in section_list:
92 cfg[section_name] = data
93 _iter_section(cfg[section_name], subsection_list)
94
95 _iter_section(cfg, sections)
96 return cfg
97
98
99@with_cleanup()
100def test_no_plugins():
101 """Run setup_plugins with no plugins in config"""
102 cfg = build_config([('mediagoblin', {}, [])])
103 mg_globals.app_config = cfg['mediagoblin']
104 mg_globals.global_config = cfg
105
05e007c1 106 pman = pluginapi.PluginManager()
29b6f917
WKG
107 setup_plugins()
108
109 # Make sure we didn't load anything.
7d503a89 110 assert len(pman.plugins) == 0
29b6f917
WKG
111
112
05e007c1 113@with_cleanup('mediagoblin.plugins.sampleplugin')
29b6f917
WKG
114def test_one_plugin():
115 """Run setup_plugins with a single working plugin"""
116 cfg = build_config([
117 ('mediagoblin', {}, []),
118 ('plugins', {}, [
119 ('mediagoblin.plugins.sampleplugin', {}, [])
120 ])
121 ])
122
123 mg_globals.app_config = cfg['mediagoblin']
124 mg_globals.global_config = cfg
125
05e007c1 126 pman = pluginapi.PluginManager()
29b6f917
WKG
127 setup_plugins()
128
05e007c1 129 # Make sure we only found one plugin
7d503a89 130 assert len(pman.plugins) == 1
05e007c1 131 # Make sure the plugin is the one we think it is.
7d503a89 132 assert pman.plugins[0] == 'mediagoblin.plugins.sampleplugin'
05e007c1 133 # Make sure there was one hook registered
7d503a89 134 assert len(pman.hooks) == 1
05e007c1
WKG
135 # Make sure _setup_plugin_called was called once
136 import mediagoblin.plugins.sampleplugin
7d503a89 137 assert mediagoblin.plugins.sampleplugin._setup_plugin_called == 1
29b6f917 138
29b6f917 139
05e007c1 140@with_cleanup('mediagoblin.plugins.sampleplugin')
29b6f917
WKG
141def test_same_plugin_twice():
142 """Run setup_plugins with a single working plugin twice"""
143 cfg = build_config([
144 ('mediagoblin', {}, []),
145 ('plugins', {}, [
146 ('mediagoblin.plugins.sampleplugin', {}, []),
147 ('mediagoblin.plugins.sampleplugin', {}, []),
148 ])
149 ])
150
151 mg_globals.app_config = cfg['mediagoblin']
152 mg_globals.global_config = cfg
153
05e007c1 154 pman = pluginapi.PluginManager()
29b6f917
WKG
155 setup_plugins()
156
05e007c1 157 # Make sure we only found one plugin
7d503a89 158 assert len(pman.plugins) == 1
05e007c1 159 # Make sure the plugin is the one we think it is.
7d503a89 160 assert pman.plugins[0] == 'mediagoblin.plugins.sampleplugin'
05e007c1 161 # Make sure there was one hook registered
7d503a89 162 assert len(pman.hooks) == 1
05e007c1
WKG
163 # Make sure _setup_plugin_called was called once
164 import mediagoblin.plugins.sampleplugin
7d503a89 165 assert mediagoblin.plugins.sampleplugin._setup_plugin_called == 1
05d8f314
WKG
166
167
168@with_cleanup()
169def test_disabled_plugin():
170 """Run setup_plugins with a single working plugin twice"""
171 cfg = build_config([
172 ('mediagoblin', {}, []),
173 ('plugins', {}, [
174 ('-mediagoblin.plugins.sampleplugin', {}, []),
175 ])
176 ])
177
178 mg_globals.app_config = cfg['mediagoblin']
179 mg_globals.global_config = cfg
180
181 pman = pluginapi.PluginManager()
182 setup_plugins()
183
184 # Make sure we didn't load the plugin
7d503a89 185 assert len(pman.plugins) == 0
70c06195
CAW
186
187
12dccc45
E
188CONFIG_ALL_CALLABLES = [
189 ('mediagoblin', {}, []),
190 ('plugins', {}, [
191 ('mediagoblin.tests.testplugins.callables1', {}, []),
192 ('mediagoblin.tests.testplugins.callables2', {}, []),
193 ('mediagoblin.tests.testplugins.callables3', {}, []),
194 ])
195 ]
196
197
70c06195 198@with_cleanup()
d1146101 199def test_hook_handle():
70c06195 200 """
d1146101 201 Test the hook_handle method
70c06195 202 """
12dccc45 203 cfg = build_config(CONFIG_ALL_CALLABLES)
70c06195
CAW
204
205 mg_globals.app_config = cfg['mediagoblin']
206 mg_globals.global_config = cfg
207
208 setup_plugins()
209
210 # Just one hook provided
211 call_log = []
d1146101 212 assert pluginapi.hook_handle(
70c06195
CAW
213 "just_one", call_log) == "Called just once"
214 assert call_log == ["expect this one call"]
215
216 # Nothing provided and unhandled not okay
217 call_log = []
d1146101
CAW
218 pluginapi.hook_handle(
219 "nothing_handling", call_log) == None
70c06195
CAW
220 assert call_log == []
221
222 # Nothing provided and unhandled okay
223 call_log = []
d1146101 224 assert pluginapi.hook_handle(
70c06195
CAW
225 "nothing_handling", call_log, unhandled_okay=True) is None
226 assert call_log == []
227
228 # Multiple provided, go with the first!
229 call_log = []
d1146101 230 assert pluginapi.hook_handle(
cdc821eb 231 "multi_handle", call_log) == "the first returns"
70c06195
CAW
232 assert call_log == ["Hi, I'm the first"]
233
234 # Multiple provided, one has CantHandleIt
235 call_log = []
d1146101 236 assert pluginapi.hook_handle(
70c06195 237 "multi_handle_with_canthandle",
cdc821eb 238 call_log) == "the second returns"
70c06195
CAW
239 assert call_log == ["Hi, I'm the second"]
240
241
242@with_cleanup()
d1146101 243def test_hook_runall():
70c06195 244 """
d1146101 245 Test the hook_runall method
70c06195 246 """
12dccc45 247 cfg = build_config(CONFIG_ALL_CALLABLES)
70c06195
CAW
248
249 mg_globals.app_config = cfg['mediagoblin']
250 mg_globals.global_config = cfg
251
252 setup_plugins()
253
254 # Just one hook, check results
255 call_log = []
d1146101
CAW
256 assert pluginapi.hook_runall(
257 "just_one", call_log) == ["Called just once"]
70c06195
CAW
258 assert call_log == ["expect this one call"]
259
260 # None provided, check results
261 call_log = []
d1146101 262 assert pluginapi.hook_runall(
70c06195
CAW
263 "nothing_handling", call_log) == []
264 assert call_log == []
265
266 # Multiple provided, check results
267 call_log = []
d1146101 268 assert pluginapi.hook_runall(
70c06195
CAW
269 "multi_handle", call_log) == [
270 "the first returns",
271 "the second returns",
272 "the third returns",
273 ]
274 assert call_log == [
275 "Hi, I'm the first",
276 "Hi, I'm the second",
277 "Hi, I'm the third"]
278
279 # Multiple provided, one has CantHandleIt, check results
280 call_log = []
d1146101 281 assert pluginapi.hook_runall(
70c06195
CAW
282 "multi_handle_with_canthandle", call_log) == [
283 "the second returns",
284 "the third returns",
285 ]
286 assert call_log == [
287 "Hi, I'm the second",
288 "Hi, I'm the third"]
d1146101
CAW
289
290
a0e7699a
CAW
291@with_cleanup()
292def test_hook_transform():
293 """
294 Test the hook_transform method
295 """
12dccc45 296 cfg = build_config(CONFIG_ALL_CALLABLES)
a0e7699a
CAW
297
298 mg_globals.app_config = cfg['mediagoblin']
299 mg_globals.global_config = cfg
300
301 setup_plugins()
302
303 assert pluginapi.hook_transform(
304 "expand_tuple", (-1, 0)) == (-1, 0, 1, 2, 3)
d3604e29
CAW
305
306
307def test_plugin_config():
308 """
309 Make sure plugins can set up their own config
310 """
311 config, validation_result = read_mediagoblin_config(
312 pkg_resources.resource_filename(
313 'mediagoblin.tests', 'appconfig_plugin_specs.ini'))
314
315 pluginspec_section = config['plugins'][
316 'mediagoblin.tests.testplugins.pluginspec']
317 assert pluginspec_section['some_string'] == 'not blork'
318 assert pluginspec_section['dont_change_me'] == 'still the default'
319
320 # Make sure validation works... this should be an error
321 assert isinstance(
322 validation_result[
323 'plugins'][
324 'mediagoblin.tests.testplugins.pluginspec'][
325 'some_int'],
326 VdtTypeError)
327
328 # the callables thing shouldn't really have anything though.
329 assert len(config['plugins'][
330 'mediagoblin.tests.testplugins.callables1']) == 0
38103094
CAW
331
332
333@pytest.fixture()
334def context_modified_app(request):
5046ca24
CAW
335 """
336 Get a MediaGoblin app fixture using appconfig_context_modified.ini
337 """
f7a5c7c7 338 return get_app(
38103094
CAW
339 request,
340 mgoblin_config=pkg_resources.resource_filename(
341 'mediagoblin.tests', 'appconfig_context_modified.ini'))
342
f7a5c7c7 343
38103094 344def test_modify_context(context_modified_app):
5046ca24
CAW
345 """
346 Test that we can modify both the view/template specific and
347 global contexts for templates.
348 """
f7a5c7c7
CAW
349 # Specific thing passed into a page
350 result = context_modified_app.get("/modify_context/specific/")
351 assert result.body.strip() == """Specific page!
352
353specific thing: in yer specificpage
354global thing: globally appended!
a1099bba
CAW
355something: orother
356doubleme: happyhappy"""
f7a5c7c7
CAW
357
358 # General test, should have global context variable only
359 result = context_modified_app.get("/modify_context/")
360 assert result.body.strip() == """General page!
361
362global thing: globally appended!
a1099bba
CAW
363lol: cats
364doubleme: joyjoy"""
bdd23c0e
CAW
365
366
367@pytest.fixture()
368def static_plugin_app(request):
369 """
370 Get a MediaGoblin app fixture using appconfig_static_plugin.ini
371 """
372 return get_app(
373 request,
374 mgoblin_config=pkg_resources.resource_filename(
375 'mediagoblin.tests', 'appconfig_static_plugin.ini'))
376
377
378def test_plugin_assetlink(static_plugin_app):
379 """
380 Test that the assetlink command works correctly
381 """
23055eb3
CAW
382 linked_assets_dir = mg_globals.app_config['plugin_linked_assets_dir']
383 plugin_link_dir = os.path.join(
384 linked_assets_dir.rstrip(os.path.sep),
385 'staticstuff')
386
387 plugin_statics = pluginapi.hook_runall("static_setup")
388 assert len(plugin_statics) == 1
389 plugin_static = plugin_statics[0]
390
391 def run_assetlink():
392 printer = CollectingPrinter()
393
394 link_plugin_assets(
395 plugin_static, linked_assets_dir, printer)
396
397 return printer
398
399 # it shouldn't exist yet
400 assert not os.path.lexists(plugin_link_dir)
401
402 # link dir doesn't exist, link it
403 result = run_assetlink().collection[0]
404 assert result == \
405 'Linked asset directory for plugin "staticstuff":\n %s\nto:\n %s\n' % (
406 plugin_static.file_path.rstrip(os.path.sep),
407 plugin_link_dir)
408 assert os.path.lexists(plugin_link_dir)
409 assert os.path.islink(plugin_link_dir)
410 assert os.path.realpath(plugin_link_dir) == plugin_static.file_path
411
412 # link dir exists, leave it alone
413 # (and it should exist still since we just ran it..)
414 result = run_assetlink().collection[0]
415 assert result == 'Skipping "staticstuff"; already set up.\n'
416 assert os.path.lexists(plugin_link_dir)
417 assert os.path.islink(plugin_link_dir)
418 assert os.path.realpath(plugin_link_dir) == plugin_static.file_path
419
420 # link dir exists, is a symlink to somewhere else (re-link)
421 junk_file_path = os.path.join(
422 linked_assets_dir.rstrip(os.path.sep),
423 'junk.txt')
424 with file(junk_file_path, 'w') as junk_file:
425 junk_file.write('barf')
426
427 os.unlink(plugin_link_dir)
428 os.symlink(junk_file_path, plugin_link_dir)
429
430 result = run_assetlink().combined_string
431 assert result == """Old link found for "staticstuff"; removing.
432Linked asset directory for plugin "staticstuff":
433 %s
434to:
435 %s
436""" % (plugin_static.file_path.rstrip(os.path.sep), plugin_link_dir)
437 assert os.path.lexists(plugin_link_dir)
438 assert os.path.islink(plugin_link_dir)
439 assert os.path.realpath(plugin_link_dir) == plugin_static.file_path
440
441 # link dir exists, but is a non-symlink
442 os.unlink(plugin_link_dir)
443 with file(plugin_link_dir, 'w') as clobber_file:
444 clobber_file.write('clobbered!')
445
446 result = run_assetlink().collection[0]
447 assert result == 'Could not link "staticstuff": %s exists and is not a symlink\n' % (
448 plugin_link_dir)
449
450 with file(plugin_link_dir, 'r') as clobber_file:
451 assert clobber_file.read() == 'clobbered!'
bdd23c0e
CAW
452
453
454def test_plugin_staticdirect(static_plugin_app):
455 """
456 Test that the staticdirect utilities pull up the right things
457 """
458 result = json.loads(
459 static_plugin_app.get('/staticstuff/').body)
460
461 assert len(result) == 2
462
463 assert result['mgoblin_bunny_pic'] == '/test_static/images/bunny_pic.png'
464 assert result['plugin_bunny_css'] == \
465 '/plugin_static/staticstuff/css/bunnify.css'
466