import os from contextlib import contextmanager from pelican.plugins._utils import ( get_namespace_plugins, get_plugin_name, load_plugins, plugin_enabled, ) from pelican.plugins.signals import signal from pelican.tests.dummy_plugins.normal_plugin import normal_plugin from pelican.tests.support import unittest @contextmanager def tmp_namespace_path(path): """Context manager for temporarily appending namespace plugin packages path: path containing the `pelican` folder This modifies the `pelican.__path__` and lets the `pelican.plugins` namespace package resolve it from that. """ # This avoids calls to internal `pelican.plugins.__path__._recalculate()` # as it should not be necessary import pelican old_path = pelican.__path__[:] try: pelican.__path__.append(os.path.join(path, "pelican")) yield finally: pelican.__path__ = old_path class PluginTest(unittest.TestCase): _PLUGIN_FOLDER = os.path.join( os.path.abspath(os.path.dirname(__file__)), "dummy_plugins" ) _NS_PLUGIN_FOLDER = os.path.join(_PLUGIN_FOLDER, "namespace_plugin") _NORMAL_PLUGIN_FOLDER = os.path.join(_PLUGIN_FOLDER, "normal_plugin") def test_namespace_path_modification(self): import pelican import pelican.plugins old_path = pelican.__path__[:] # not existing path path = os.path.join(self._PLUGIN_FOLDER, "foo") with tmp_namespace_path(path): self.assertIn(os.path.join(path, "pelican"), pelican.__path__) # foo/pelican does not exist, so it won't propagate self.assertNotIn( os.path.join(path, "pelican", "plugins"), pelican.plugins.__path__ ) # verify that we restored path back self.assertEqual(pelican.__path__, old_path) # existing path with tmp_namespace_path(self._NS_PLUGIN_FOLDER): self.assertIn( os.path.join(self._NS_PLUGIN_FOLDER, "pelican"), pelican.__path__ ) # /namespace_plugin/pelican exists, so it should be in self.assertIn( os.path.join(self._NS_PLUGIN_FOLDER, "pelican", "plugins"), pelican.plugins.__path__, ) self.assertEqual(pelican.__path__, old_path) def test_get_namespace_plugins(self): # existing namespace plugins existing_ns_plugins = get_namespace_plugins() # with plugin with tmp_namespace_path(self._NS_PLUGIN_FOLDER): ns_plugins = get_namespace_plugins() self.assertEqual(len(ns_plugins), len(existing_ns_plugins) + 1) self.assertIn("pelican.plugins.ns_plugin", ns_plugins) self.assertEqual( ns_plugins["pelican.plugins.ns_plugin"].NAME, "namespace plugin" ) # should be back to existing namespace plugins outside `with` ns_plugins = get_namespace_plugins() self.assertEqual(ns_plugins, existing_ns_plugins) def test_load_plugins(self): def get_plugin_names(plugins): return {get_plugin_name(p) for p in plugins} # existing namespace plugins existing_ns_plugins = load_plugins({}) with tmp_namespace_path(self._NS_PLUGIN_FOLDER): # with no `PLUGINS` setting, load namespace plugins plugins = load_plugins({}) self.assertEqual(len(plugins), len(existing_ns_plugins) + 1, plugins) self.assertEqual( {"pelican.plugins.ns_plugin"} | get_plugin_names(existing_ns_plugins), get_plugin_names(plugins), ) # disable namespace plugins with `PLUGINS = []` SETTINGS = {"PLUGINS": []} plugins = load_plugins(SETTINGS) self.assertEqual(len(plugins), 0, plugins) # with `PLUGINS`, load only specified plugins # normal plugin SETTINGS = { "PLUGINS": ["normal_plugin"], "PLUGIN_PATHS": [self._NORMAL_PLUGIN_FOLDER], } plugins = load_plugins(SETTINGS) self.assertEqual(len(plugins), 1, plugins) self.assertEqual({"normal_plugin"}, get_plugin_names(plugins)) # normal submodule/subpackage plugins SETTINGS = { "PLUGINS": [ "normal_submodule_plugin.subplugin", "normal_submodule_plugin.subpackage.subpackage", ], "PLUGIN_PATHS": [self._NORMAL_PLUGIN_FOLDER], } plugins = load_plugins(SETTINGS) self.assertEqual(len(plugins), 2, plugins) self.assertEqual( { "normal_submodule_plugin.subplugin", "normal_submodule_plugin.subpackage.subpackage", }, get_plugin_names(plugins), ) # ensure normal plugins are loaded only once SETTINGS = { "PLUGINS": ["normal_plugin"], "PLUGIN_PATHS": [self._NORMAL_PLUGIN_FOLDER], } plugins = load_plugins(SETTINGS) for plugin in load_plugins(SETTINGS): # The second load_plugins() should return the same plugin # objects as the first one self.assertIn(plugin, plugins) # namespace plugin short SETTINGS = {"PLUGINS": ["ns_plugin"]} plugins = load_plugins(SETTINGS) self.assertEqual(len(plugins), 1, plugins) self.assertEqual({"pelican.plugins.ns_plugin"}, get_plugin_names(plugins)) # namespace plugin long SETTINGS = {"PLUGINS": ["pelican.plugins.ns_plugin"]} plugins = load_plugins(SETTINGS) self.assertEqual(len(plugins), 1, plugins) self.assertEqual({"pelican.plugins.ns_plugin"}, get_plugin_names(plugins)) # normal and namespace plugin SETTINGS = { "PLUGINS": ["normal_plugin", "ns_plugin"], "PLUGIN_PATHS": [self._NORMAL_PLUGIN_FOLDER], } plugins = load_plugins(SETTINGS) self.assertEqual(len(plugins), 2, plugins) self.assertEqual( {"normal_plugin", "pelican.plugins.ns_plugin"}, get_plugin_names(plugins), ) def test_get_plugin_name(self): self.assertEqual( get_plugin_name(normal_plugin), "pelican.tests.dummy_plugins.normal_plugin.normal_plugin", ) class NoopPlugin: def register(self): pass self.assertEqual( get_plugin_name(NoopPlugin), "PluginTest.test_get_plugin_name..NoopPlugin", ) self.assertEqual( get_plugin_name(NoopPlugin()), "PluginTest.test_get_plugin_name..NoopPlugin", ) def test_plugin_enabled(self): def get_plugin_names(plugins): return [get_plugin_name(p) for p in plugins] with tmp_namespace_path(self._NS_PLUGIN_FOLDER): # with no `PLUGINS` setting, load namespace plugins SETTINGS = {} plugins = get_plugin_names(load_plugins(SETTINGS)) self.assertTrue(plugin_enabled("ns_plugin", plugins)) self.assertTrue(plugin_enabled("pelican.plugins.ns_plugin", plugins)) self.assertFalse(plugin_enabled("normal_plugin", plugins)) self.assertFalse(plugin_enabled("unknown", plugins)) # disable namespace plugins with `PLUGINS = []` SETTINGS = {"PLUGINS": []} plugins = get_plugin_names(load_plugins(SETTINGS)) self.assertFalse(plugin_enabled("ns_plugin", plugins)) self.assertFalse(plugin_enabled("pelican.plugins.ns_plugin", plugins)) self.assertFalse(plugin_enabled("normal_plugin", plugins)) self.assertFalse(plugin_enabled("unknown", plugins)) # with `PLUGINS`, load only specified plugins # normal plugin SETTINGS = { "PLUGINS": ["normal_plugin"], "PLUGIN_PATHS": [self._NORMAL_PLUGIN_FOLDER], } plugins = get_plugin_names(load_plugins(SETTINGS)) self.assertFalse(plugin_enabled("ns_plugin", plugins)) self.assertFalse(plugin_enabled("pelican.plugins.ns_plugin", plugins)) self.assertTrue(plugin_enabled("normal_plugin", plugins)) self.assertFalse(plugin_enabled("unknown", plugins)) # normal submodule/subpackage plugins SETTINGS = { "PLUGINS": [ "normal_submodule_plugin.subplugin", "normal_submodule_plugin.subpackage.subpackage", ], "PLUGIN_PATHS": [self._NORMAL_PLUGIN_FOLDER], } plugins = get_plugin_names(load_plugins(SETTINGS)) self.assertFalse(plugin_enabled("ns_plugin", plugins)) self.assertFalse(plugin_enabled("pelican.plugins.ns_plugin", plugins)) self.assertFalse(plugin_enabled("normal_plugin", plugins)) self.assertFalse(plugin_enabled("unknown", plugins)) # namespace plugin short SETTINGS = {"PLUGINS": ["ns_plugin"]} plugins = get_plugin_names(load_plugins(SETTINGS)) self.assertTrue(plugin_enabled("ns_plugin", plugins)) self.assertTrue(plugin_enabled("pelican.plugins.ns_plugin", plugins)) self.assertFalse(plugin_enabled("normal_plugin", plugins)) self.assertFalse(plugin_enabled("unknown", plugins)) # namespace plugin long SETTINGS = {"PLUGINS": ["pelican.plugins.ns_plugin"]} plugins = get_plugin_names(load_plugins(SETTINGS)) self.assertTrue(plugin_enabled("ns_plugin", plugins)) self.assertTrue(plugin_enabled("pelican.plugins.ns_plugin", plugins)) self.assertFalse(plugin_enabled("normal_plugin", plugins)) self.assertFalse(plugin_enabled("unknown", plugins)) # normal and namespace plugin SETTINGS = { "PLUGINS": ["normal_plugin", "ns_plugin"], "PLUGIN_PATHS": [self._NORMAL_PLUGIN_FOLDER], } plugins = get_plugin_names(load_plugins(SETTINGS)) self.assertTrue(plugin_enabled("ns_plugin", plugins)) self.assertTrue(plugin_enabled("pelican.plugins.ns_plugin", plugins)) self.assertTrue(plugin_enabled("normal_plugin", plugins)) self.assertFalse(plugin_enabled("unknown", plugins)) def test_blinker_is_ordered(self): """ensure that call order is connetion order""" dummy_signal = signal("dummpy_signal") functions = [] expected = [] for i in range(50): # function appends value of i to a list def func(input, i=i): input.append(i) functions.append(func) # we expect functions to be run in the connection order dummy_signal.connect(func) expected.append(i) input = [] dummy_signal.send(input) self.assertEqual(input, expected)