plugin_tests.py 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. #!/usr/bin/env python
  2. import unittest
  3. from unittest import mock
  4. import os
  5. from mkdocs import plugins
  6. from mkdocs import config
  7. class DummyPlugin(plugins.BasePlugin):
  8. config_scheme = (
  9. ('foo', config.config_options.Type(str, default='default foo')),
  10. ('bar', config.config_options.Type(int, default=0)),
  11. ('dir', config.config_options.Dir(exists=False)),
  12. )
  13. def on_pre_page(self, content, **kwargs):
  14. """ modify page content by prepending `foo` config value. """
  15. return '{} {}'.format(self.config['foo'], content)
  16. def on_nav(self, item, **kwargs):
  17. """ do nothing (return None) to not modify item. """
  18. return None
  19. def on_page_read_source(self, **kwargs):
  20. """ create new source by prepending `foo` config value to 'source'. """
  21. return '{} {}'.format(self.config['foo'], 'source')
  22. def on_pre_build(self, **kwargs):
  23. """ do nothing (return None). """
  24. return None
  25. class TestPluginClass(unittest.TestCase):
  26. def test_valid_plugin_options(self):
  27. test_dir = 'test'
  28. options = {
  29. 'foo': 'some value',
  30. 'dir': test_dir,
  31. }
  32. cfg_fname = os.path.join('tmp', 'test', 'fname.yml')
  33. cfg_fname = os.path.abspath(cfg_fname)
  34. cfg_dirname = os.path.dirname(cfg_fname)
  35. expected = os.path.join(cfg_dirname, test_dir)
  36. expected = {
  37. 'foo': 'some value',
  38. 'bar': 0,
  39. 'dir': expected,
  40. }
  41. plugin = DummyPlugin()
  42. errors, warnings = plugin.load_config(options, config_file_path=cfg_fname)
  43. self.assertEqual(plugin.config, expected)
  44. self.assertEqual(errors, [])
  45. self.assertEqual(warnings, [])
  46. def test_invalid_plugin_options(self):
  47. plugin = DummyPlugin()
  48. errors, warnings = plugin.load_config({'foo': 42})
  49. self.assertEqual(len(errors), 1)
  50. self.assertIn('foo', errors[0])
  51. self.assertEqual(warnings, [])
  52. errors, warnings = plugin.load_config({'bar': 'a string'})
  53. self.assertEqual(len(errors), 1)
  54. self.assertIn('bar', errors[0])
  55. self.assertEqual(warnings, [])
  56. errors, warnings = plugin.load_config({'invalid_key': 'value'})
  57. self.assertEqual(errors, [])
  58. self.assertEqual(len(warnings), 1)
  59. self.assertIn('invalid_key', warnings[0])
  60. class TestPluginCollection(unittest.TestCase):
  61. def test_set_plugin_on_collection(self):
  62. collection = plugins.PluginCollection()
  63. plugin = DummyPlugin()
  64. collection['foo'] = plugin
  65. self.assertEqual([(k, v) for k, v in collection.items()], [('foo', plugin)])
  66. def test_set_multiple_plugins_on_collection(self):
  67. collection = plugins.PluginCollection()
  68. plugin1 = DummyPlugin()
  69. collection['foo'] = plugin1
  70. plugin2 = DummyPlugin()
  71. collection['bar'] = plugin2
  72. self.assertEqual([(k, v) for k, v in collection.items()], [('foo', plugin1), ('bar', plugin2)])
  73. def test_run_event_on_collection(self):
  74. collection = plugins.PluginCollection()
  75. plugin = DummyPlugin()
  76. plugin.load_config({'foo': 'new'})
  77. collection['foo'] = plugin
  78. self.assertEqual(collection.run_event('pre_page', 'page content'), 'new page content')
  79. def test_run_event_twice_on_collection(self):
  80. collection = plugins.PluginCollection()
  81. plugin1 = DummyPlugin()
  82. plugin1.load_config({'foo': 'new'})
  83. collection['foo'] = plugin1
  84. plugin2 = DummyPlugin()
  85. plugin2.load_config({'foo': 'second'})
  86. collection['bar'] = plugin2
  87. self.assertEqual(collection.run_event('pre_page', 'page content'),
  88. 'second new page content')
  89. def test_event_returns_None(self):
  90. collection = plugins.PluginCollection()
  91. plugin = DummyPlugin()
  92. plugin.load_config({'foo': 'new'})
  93. collection['foo'] = plugin
  94. self.assertEqual(collection.run_event('nav', 'nav item'), 'nav item')
  95. def test_event_empty_item(self):
  96. collection = plugins.PluginCollection()
  97. plugin = DummyPlugin()
  98. plugin.load_config({'foo': 'new'})
  99. collection['foo'] = plugin
  100. self.assertEqual(collection.run_event('page_read_source'), 'new source')
  101. def test_event_empty_item_returns_None(self):
  102. collection = plugins.PluginCollection()
  103. plugin = DummyPlugin()
  104. plugin.load_config({'foo': 'new'})
  105. collection['foo'] = plugin
  106. self.assertEqual(collection.run_event('pre_build'), None)
  107. def test_run_undefined_event_on_collection(self):
  108. collection = plugins.PluginCollection()
  109. self.assertEqual(collection.run_event('pre_page', 'page content'), 'page content')
  110. def test_run_unknown_event_on_collection(self):
  111. collection = plugins.PluginCollection()
  112. self.assertRaises(KeyError, collection.run_event, 'unknown', 'page content')
  113. MockEntryPoint = mock.Mock()
  114. MockEntryPoint.configure_mock(**{'name': 'sample', 'load.return_value': DummyPlugin})
  115. @mock.patch('pkg_resources.iter_entry_points', return_value=[MockEntryPoint])
  116. class TestPluginConfig(unittest.TestCase):
  117. def test_plugin_config_without_options(self, mock_class):
  118. cfg = {'plugins': ['sample']}
  119. option = config.config_options.Plugins()
  120. cfg['plugins'] = option.validate(cfg['plugins'])
  121. self.assertIsInstance(cfg['plugins'], plugins.PluginCollection)
  122. self.assertIn('sample', cfg['plugins'])
  123. self.assertIsInstance(cfg['plugins']['sample'], plugins.BasePlugin)
  124. expected = {
  125. 'foo': 'default foo',
  126. 'bar': 0,
  127. 'dir': None,
  128. }
  129. self.assertEqual(cfg['plugins']['sample'].config, expected)
  130. def test_plugin_config_with_options(self, mock_class):
  131. cfg = {
  132. 'plugins': [{
  133. 'sample': {
  134. 'foo': 'foo value',
  135. 'bar': 42
  136. }
  137. }]
  138. }
  139. option = config.config_options.Plugins()
  140. cfg['plugins'] = option.validate(cfg['plugins'])
  141. self.assertIsInstance(cfg['plugins'], plugins.PluginCollection)
  142. self.assertIn('sample', cfg['plugins'])
  143. self.assertIsInstance(cfg['plugins']['sample'], plugins.BasePlugin)
  144. expected = {
  145. 'foo': 'foo value',
  146. 'bar': 42,
  147. 'dir': None,
  148. }
  149. self.assertEqual(cfg['plugins']['sample'].config, expected)
  150. def test_plugin_config_empty_list_with_empty_default(self, mock_class):
  151. cfg = {'plugins': []}
  152. option = config.config_options.Plugins(default=[])
  153. cfg['plugins'] = option.validate(cfg['plugins'])
  154. self.assertIsInstance(cfg['plugins'], plugins.PluginCollection)
  155. self.assertEqual(len(cfg['plugins']), 0)
  156. def test_plugin_config_empty_list_with_default(self, mock_class):
  157. # Default is ignored
  158. cfg = {'plugins': []}
  159. option = config.config_options.Plugins(default=['sample'])
  160. cfg['plugins'] = option.validate(cfg['plugins'])
  161. self.assertIsInstance(cfg['plugins'], plugins.PluginCollection)
  162. self.assertEqual(len(cfg['plugins']), 0)
  163. def test_plugin_config_none_with_empty_default(self, mock_class):
  164. cfg = {'plugins': None}
  165. option = config.config_options.Plugins(default=[])
  166. cfg['plugins'] = option.validate(cfg['plugins'])
  167. self.assertIsInstance(cfg['plugins'], plugins.PluginCollection)
  168. self.assertEqual(len(cfg['plugins']), 0)
  169. def test_plugin_config_none_with_default(self, mock_class):
  170. # Default is used.
  171. cfg = {'plugins': None}
  172. option = config.config_options.Plugins(default=['sample'])
  173. cfg['plugins'] = option.validate(cfg['plugins'])
  174. self.assertIsInstance(cfg['plugins'], plugins.PluginCollection)
  175. self.assertIn('sample', cfg['plugins'])
  176. self.assertIsInstance(cfg['plugins']['sample'], plugins.BasePlugin)
  177. expected = {
  178. 'foo': 'default foo',
  179. 'bar': 0,
  180. 'dir': None,
  181. }
  182. self.assertEqual(cfg['plugins']['sample'].config, expected)
  183. def test_plugin_config_uninstalled(self, mock_class):
  184. cfg = {'plugins': ['uninstalled']}
  185. option = config.config_options.Plugins()
  186. self.assertRaises(config.base.ValidationError, option.validate, cfg['plugins'])
  187. def test_plugin_config_not_list(self, mock_class):
  188. cfg = {'plugins': 'sample'} # should be a list
  189. option = config.config_options.Plugins()
  190. self.assertRaises(config.base.ValidationError, option.validate, cfg['plugins'])
  191. def test_plugin_config_multivalue_dict(self, mock_class):
  192. cfg = {
  193. 'plugins': [{
  194. 'sample': {
  195. 'foo': 'foo value',
  196. 'bar': 42
  197. },
  198. 'extra_key': 'baz'
  199. }]
  200. }
  201. option = config.config_options.Plugins()
  202. self.assertRaises(config.base.ValidationError, option.validate, cfg['plugins'])
  203. def test_plugin_config_not_string_or_dict(self, mock_class):
  204. cfg = {
  205. 'plugins': [('not a string or dict',)]
  206. }
  207. option = config.config_options.Plugins()
  208. self.assertRaises(config.base.ValidationError, option.validate, cfg['plugins'])
  209. def test_plugin_config_options_not_dict(self, mock_class):
  210. cfg = {
  211. 'plugins': [{
  212. 'sample': 'not a dict'
  213. }]
  214. }
  215. option = config.config_options.Plugins()
  216. self.assertRaises(config.base.ValidationError, option.validate, cfg['plugins'])