Expand plugin testing

This commit is contained in:
Oleh Prypin
2022-10-06 23:25:53 +02:00
parent 7a72282f8a
commit 76709ab540
3 changed files with 227 additions and 169 deletions

View File

@@ -6,7 +6,6 @@ from __future__ import annotations
import logging
import sys
from collections import OrderedDict
from typing import (
TYPE_CHECKING,
Any,
@@ -14,6 +13,7 @@ from typing import (
Dict,
Generic,
List,
MutableMapping,
Optional,
Tuple,
Type,
@@ -461,7 +461,7 @@ def event_priority(priority: float) -> Callable[[T], T]:
return decorator
class PluginCollection(OrderedDict):
class PluginCollection(dict, MutableMapping[str, BasePlugin]):
"""
A collection of plugins.
@@ -480,8 +480,11 @@ class PluginCollection(OrderedDict):
self.events[event_name], method, key=lambda m: -getattr(m, 'mkdocs_priority', 0)
)
def __setitem__(self, key: str, value: BasePlugin, **kwargs) -> None:
super().__setitem__(key, value, **kwargs)
def __getitem__(self, key: str) -> BasePlugin:
return super().__getitem__(key)
def __setitem__(self, key: str, value: BasePlugin) -> None:
super().__setitem__(key, value)
# Register all of the event methods defined for this Plugin.
for event_name in (x for x in dir(value) if x.startswith('on_')):
method = getattr(value, event_name, None)

View File

@@ -20,6 +20,7 @@ else:
import mkdocs
from mkdocs.config import config_options as c
from mkdocs.config.base import Config
from mkdocs.plugins import BasePlugin, PluginCollection
from mkdocs.tests.base import tempdir
from mkdocs.theme import Theme
from mkdocs.utils import write_file, yaml_load
@@ -1619,6 +1620,205 @@ class MarkdownExtensionsTest(TestCase):
self.assertIsNone(conf.mdx_configs.get('toc'))
class _FakePluginConfig(Config):
foo = c.Type(str, default='default foo')
bar = c.Type(int, default=0)
dir = c.Optional(c.Dir(exists=False))
class FakePlugin(BasePlugin[_FakePluginConfig]):
pass
class FakeEntryPoint:
@property
def name(self):
return 'sample'
def load(self):
return FakePlugin
@patch('mkdocs.plugins.entry_points', return_value=[FakeEntryPoint()])
class PluginsTest(TestCase):
def test_plugin_config_without_options(self, mock_class) -> None:
class Schema(Config):
plugins = c.Plugins()
cfg = {
'plugins': ['sample'],
}
conf = self.get_config(Schema, cfg)
assert_type(conf.plugins, PluginCollection)
self.assertIsInstance(conf.plugins, PluginCollection)
self.assertIn('sample', conf.plugins)
plugin = conf.plugins['sample']
assert_type(plugin, BasePlugin)
self.assertIsInstance(plugin, FakePlugin)
self.assertIsInstance(plugin.config, _FakePluginConfig)
expected = {
'foo': 'default foo',
'bar': 0,
'dir': None,
}
self.assertEqual(plugin.config, expected)
def test_plugin_config_with_options(self, mock_class) -> None:
class Schema(Config):
plugins = c.Plugins()
cfg = {
'plugins': [
{
'sample': {
'foo': 'foo value',
'bar': 42,
},
}
],
}
conf = self.get_config(Schema, cfg)
self.assertIsInstance(conf.plugins, PluginCollection)
self.assertIn('sample', conf.plugins)
self.assertIsInstance(conf.plugins['sample'], BasePlugin)
expected = {
'foo': 'foo value',
'bar': 42,
'dir': None,
}
self.assertEqual(conf.plugins['sample'].config, expected)
def test_plugin_config_as_dict(self, mock_class) -> None:
class Schema(Config):
plugins = c.Plugins()
cfg = {
'plugins': {
'sample': {
'foo': 'foo value',
'bar': 42,
},
},
}
conf = self.get_config(Schema, cfg)
self.assertIsInstance(conf.plugins, PluginCollection)
self.assertIn('sample', conf.plugins)
self.assertIsInstance(conf.plugins['sample'], BasePlugin)
expected = {
'foo': 'foo value',
'bar': 42,
'dir': None,
}
self.assertEqual(conf.plugins['sample'].config, expected)
def test_plugin_config_empty_list_with_empty_default(self, mock_class) -> None:
class Schema(Config):
plugins = c.Plugins(default=[])
cfg: Dict[str, Any] = {'plugins': []}
conf = self.get_config(Schema, cfg)
self.assertIsInstance(conf.plugins, PluginCollection)
self.assertEqual(len(conf.plugins), 0)
def test_plugin_config_empty_list_with_default(self, mock_class) -> None:
class Schema(Config):
plugins = c.Plugins(default=['sample'])
# Default is ignored
cfg: Dict[str, Any] = {'plugins': []}
conf = self.get_config(Schema, cfg)
self.assertIsInstance(conf.plugins, PluginCollection)
self.assertEqual(len(conf.plugins), 0)
def test_plugin_config_none_with_empty_default(self, mock_class) -> None:
class Schema(Config):
plugins = c.Plugins(default=[])
cfg = {'plugins': None}
conf = self.get_config(Schema, cfg)
self.assertIsInstance(conf.plugins, PluginCollection)
self.assertEqual(len(conf.plugins), 0)
def test_plugin_config_none_with_default(self, mock_class) -> None:
class Schema(Config):
plugins = c.Plugins(default=['sample'])
# Default is used.
cfg = {'plugins': None}
conf = self.get_config(Schema, cfg)
self.assertIsInstance(conf.plugins, PluginCollection)
self.assertIn('sample', conf.plugins)
self.assertIsInstance(conf.plugins['sample'], BasePlugin)
expected = {
'foo': 'default foo',
'bar': 0,
'dir': None,
}
self.assertEqual(conf.plugins['sample'].config, expected)
def test_plugin_config_uninstalled(self, mock_class) -> None:
class Schema(Config):
plugins = c.Plugins()
cfg = {'plugins': ['uninstalled']}
with self.expect_error(plugins='The "uninstalled" plugin is not installed'):
self.get_config(Schema, cfg)
def test_plugin_config_not_list(self, mock_class) -> None:
class Schema(Config):
plugins = c.Plugins()
cfg = {'plugins': 'sample'}
with self.expect_error(plugins="Invalid Plugins configuration. Expected a list or dict."):
self.get_config(Schema, cfg)
def test_plugin_config_multivalue_dict(self, mock_class) -> None:
class Schema(Config):
plugins = c.Plugins()
cfg = {
'plugins': [
{
'sample': {
'foo': 'foo value',
'bar': 42,
},
'extra_key': 'baz',
}
],
}
with self.expect_error(plugins="Invalid Plugins configuration"):
self.get_config(Schema, cfg)
def test_plugin_config_not_string_or_dict(self, mock_class) -> None:
class Schema(Config):
plugins = c.Plugins()
cfg = {
'plugins': [('not a string or dict',)],
}
with self.expect_error(plugins="'('not a string or dict',)' is not a valid plugin name."):
self.get_config(Schema, cfg)
def test_plugin_config_options_not_dict(self, mock_class) -> None:
class Schema(Config):
plugins = c.Plugins()
cfg = {
'plugins': [{'sample': 'not a dict'}],
}
with self.expect_error(plugins="Invalid config options for the 'sample' plugin."):
self.get_config(Schema, cfg)
class TestHooks(TestCase):
class Schema(Config):
plugins = c.Plugins(default=[])
@@ -1646,7 +1846,7 @@ class TestHooks(TestCase):
{**conf.plugins.events, 'page_markdown': [hook.on_page_markdown]},
conf.plugins.events,
)
self.assertEqual(hook.on_page_markdown('foo foo'), 'zoo zoo')
self.assertEqual(hook.on_page_markdown('foo foo'), 'zoo zoo') # type: ignore[call-arg]
self.assertFalse(hasattr(hook, 'on_nav'))

View File

@@ -3,9 +3,17 @@
import os
import unittest
from unittest import mock
from typing import TYPE_CHECKING, Optional
from mkdocs import config, plugins
if TYPE_CHECKING:
from typing_extensions import assert_type
else:
def assert_type(val, typ):
return None
from mkdocs import plugins
from mkdocs.commands import build
from mkdocs.config import base
from mkdocs.config import config_options as c
@@ -39,7 +47,7 @@ class DummyPlugin(plugins.BasePlugin[_DummyPluginConfig]):
class TestPluginClass(unittest.TestCase):
def test_valid_plugin_options(self):
def test_valid_plugin_options(self) -> None:
test_dir = 'test'
options = {
@@ -51,20 +59,24 @@ class TestPluginClass(unittest.TestCase):
cfg_fname = os.path.abspath(cfg_fname)
cfg_dirname = os.path.dirname(cfg_fname)
expected = os.path.join(cfg_dirname, test_dir)
expected = {
'foo': 'some value',
'bar': 0,
'dir': expected,
'dir': os.path.join(cfg_dirname, test_dir),
}
plugin = DummyPlugin()
errors, warnings = plugin.load_config(options, config_file_path=cfg_fname)
self.assertEqual(plugin.config, expected)
self.assertEqual(errors, [])
self.assertEqual(warnings, [])
assert_type(plugin.config, _DummyPluginConfig)
self.assertEqual(plugin.config, expected)
assert_type(plugin.config.bar, int)
self.assertEqual(plugin.config.bar, 0)
assert_type(plugin.config.dir, Optional[str])
def test_invalid_plugin_options(self):
plugin = DummyPlugin()
errors, warnings = plugin.load_config({'foo': 42})
@@ -285,160 +297,3 @@ class TestPluginCollection(unittest.TestCase):
self.assertEqual(str(build_errors[2]), 'page content error')
self.assertIs(build_errors[3].__class__, ValueError)
self.assertEqual(str(build_errors[3]), 'post page error')
MockEntryPoint = mock.Mock()
MockEntryPoint.configure_mock(**{'name': 'sample', 'load.return_value': DummyPlugin})
@mock.patch('mkdocs.plugins.entry_points', return_value=[MockEntryPoint])
class TestPluginConfig(unittest.TestCase):
def test_plugin_config_without_options(self, mock_class):
cfg = {'plugins': ['sample']}
option = c.Plugins()
cfg['plugins'] = option.validate(cfg['plugins'])
self.assertIsInstance(cfg['plugins'], plugins.PluginCollection)
self.assertIn('sample', cfg['plugins'])
self.assertIsInstance(cfg['plugins']['sample'], plugins.BasePlugin)
expected = {
'foo': 'default foo',
'bar': 0,
'dir': None,
}
self.assertEqual(cfg['plugins']['sample'].config, expected)
def test_plugin_config_with_options(self, mock_class):
cfg = {
'plugins': [
{
'sample': {
'foo': 'foo value',
'bar': 42,
},
}
],
}
option = c.Plugins()
cfg['plugins'] = option.validate(cfg['plugins'])
self.assertIsInstance(cfg['plugins'], plugins.PluginCollection)
self.assertIn('sample', cfg['plugins'])
self.assertIsInstance(cfg['plugins']['sample'], plugins.BasePlugin)
expected = {
'foo': 'foo value',
'bar': 42,
'dir': None,
}
self.assertEqual(cfg['plugins']['sample'].config, expected)
def test_plugin_config_as_dict(self, mock_class):
cfg = {
'plugins': {
'sample': {
'foo': 'foo value',
'bar': 42,
},
},
}
option = c.Plugins()
cfg['plugins'] = option.validate(cfg['plugins'])
self.assertIsInstance(cfg['plugins'], plugins.PluginCollection)
self.assertIn('sample', cfg['plugins'])
self.assertIsInstance(cfg['plugins']['sample'], plugins.BasePlugin)
expected = {
'foo': 'foo value',
'bar': 42,
'dir': None,
}
self.assertEqual(cfg['plugins']['sample'].config, expected)
def test_plugin_config_empty_list_with_empty_default(self, mock_class):
cfg = {'plugins': []}
option = c.Plugins(default=[])
cfg['plugins'] = option.validate(cfg['plugins'])
self.assertIsInstance(cfg['plugins'], plugins.PluginCollection)
self.assertEqual(len(cfg['plugins']), 0)
def test_plugin_config_empty_list_with_default(self, mock_class):
# Default is ignored
cfg = {'plugins': []}
option = c.Plugins(default=['sample'])
cfg['plugins'] = option.validate(cfg['plugins'])
self.assertIsInstance(cfg['plugins'], plugins.PluginCollection)
self.assertEqual(len(cfg['plugins']), 0)
def test_plugin_config_none_with_empty_default(self, mock_class):
cfg = {'plugins': None}
option = c.Plugins(default=[])
cfg['plugins'] = option.validate(cfg['plugins'])
self.assertIsInstance(cfg['plugins'], plugins.PluginCollection)
self.assertEqual(len(cfg['plugins']), 0)
def test_plugin_config_none_with_default(self, mock_class):
# Default is used.
cfg = {'plugins': None}
option = c.Plugins(default=['sample'])
cfg['plugins'] = option.validate(cfg['plugins'])
self.assertIsInstance(cfg['plugins'], plugins.PluginCollection)
self.assertIn('sample', cfg['plugins'])
self.assertIsInstance(cfg['plugins']['sample'], plugins.BasePlugin)
expected = {
'foo': 'default foo',
'bar': 0,
'dir': None,
}
self.assertEqual(cfg['plugins']['sample'].config, expected)
def test_plugin_config_uninstalled(self, mock_class):
cfg = {'plugins': ['uninstalled']}
option = c.Plugins()
with self.assertRaises(config.base.ValidationError):
option.validate(cfg['plugins'])
def test_plugin_config_not_list(self, mock_class):
cfg = {'plugins': 'sample'} # should be a list
option = c.Plugins()
with self.assertRaises(config.base.ValidationError):
option.validate(cfg['plugins'])
def test_plugin_config_multivalue_dict(self, mock_class):
cfg = {
'plugins': [
{
'sample': {
'foo': 'foo value',
'bar': 42,
},
'extra_key': 'baz',
}
],
}
option = c.Plugins()
with self.assertRaises(config.base.ValidationError):
option.validate(cfg['plugins'])
def test_plugin_config_not_string_or_dict(self, mock_class):
cfg = {
'plugins': [('not a string or dict',)],
}
option = c.Plugins()
with self.assertRaises(config.base.ValidationError):
option.validate(cfg['plugins'])
def test_plugin_config_options_not_dict(self, mock_class):
cfg = {
'plugins': [
{
'sample': 'not a dict',
}
],
}
option = c.Plugins()
with self.assertRaises(config.base.ValidationError):
option.validate(cfg['plugins'])