| # Copyright 2021 The Pigweed Authors |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| # use this file except in compliance with the License. You may obtain a copy of |
| # the License at |
| # |
| # https://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| # License for the specific language governing permissions and limitations under |
| # the License. |
| """Tests for pw_cli.plugins.""" |
| |
| from pathlib import Path |
| import sys |
| import tempfile |
| import types |
| from typing import Dict, Iterator |
| import unittest |
| |
| from pw_cli import plugins |
| |
| |
| def _no_docstring() -> int: |
| return 123 |
| |
| |
| def _with_docstring() -> int: |
| """This docstring is brought to you courtesy of Pigweed.""" |
| return 456 |
| |
| |
| def _create_files(directory: str, files: Dict[str, str]) -> Iterator[Path]: |
| for relative_path, contents in files.items(): |
| path = Path(directory) / relative_path |
| path.parent.mkdir(exist_ok=True, parents=True) |
| path.write_text(contents) |
| yield path |
| |
| |
| class TestPlugin(unittest.TestCase): |
| """Tests for plugins.Plugins.""" |
| def test_target_name_attribute(self) -> None: |
| self.assertEqual( |
| plugins.Plugin('abc', _no_docstring).target_name, |
| f'{__name__}._no_docstring') |
| |
| def test_target_name_no_name_attribute(self) -> None: |
| has_no_name = 'no __name__' |
| self.assertFalse(hasattr(has_no_name, '__name__')) |
| |
| self.assertEqual( |
| plugins.Plugin('abc', has_no_name).target_name, |
| '<unknown>.no __name__') |
| |
| |
| _TEST_PLUGINS = { |
| 'TEST_PLUGINS': (f'test_plugin {__name__} _with_docstring\n' |
| f'other_plugin {__name__} _no_docstring\n'), |
| 'nested/in/dirs/TEST_PLUGINS': |
| f'test_plugin {__name__} _no_docstring\n', |
| } |
| |
| |
| class TestPluginRegistry(unittest.TestCase): |
| """Tests for plugins.Registry.""" |
| def setUp(self) -> None: |
| self._registry = plugins.Registry( |
| validator=plugins.callable_with_no_args) |
| |
| def test_register(self) -> None: |
| self.assertIsNotNone(self._registry.register('a_plugin', |
| _no_docstring)) |
| self.assertIs(self._registry['a_plugin'].target, _no_docstring) |
| |
| def test_register_by_name(self) -> None: |
| self.assertIsNotNone( |
| self._registry.register_by_name('plugin_one', __name__, |
| '_no_docstring')) |
| self.assertIsNotNone( |
| self._registry.register('plugin_two', _no_docstring)) |
| |
| self.assertIs(self._registry['plugin_one'].target, _no_docstring) |
| self.assertIs(self._registry['plugin_two'].target, _no_docstring) |
| |
| def test_register_by_name_undefined_module(self) -> None: |
| with self.assertRaisesRegex(plugins.Error, 'No module named'): |
| self._registry.register_by_name('plugin_two', 'not a module', |
| 'something') |
| |
| def test_register_by_name_undefined_function(self) -> None: |
| with self.assertRaisesRegex(plugins.Error, 'does not exist'): |
| self._registry.register_by_name('plugin_two', __name__, 'nothing') |
| |
| def test_register_fails_validation(self) -> None: |
| with self.assertRaisesRegex(plugins.Error, 'must be callable'): |
| self._registry.register('plugin_two', 'not function') |
| |
| def test_run_with_argv_sets_sys_argv(self) -> None: |
| def stash_argv() -> int: |
| self.assertEqual(['pw go', '1', '2'], sys.argv) |
| return 1 |
| |
| self.assertIsNotNone(self._registry.register('go', stash_argv)) |
| |
| original_argv = sys.argv |
| self.assertEqual(self._registry.run_with_argv('go', ['1', '2']), 1) |
| self.assertIs(sys.argv, original_argv) |
| |
| def test_run_with_argv_registered_plugin(self) -> None: |
| with self.assertRaises(KeyError): |
| self._registry.run_with_argv('plugin_one', []) |
| |
| def test_register_cannot_overwrite(self) -> None: |
| self.assertIsNotNone(self._registry.register('foo', lambda: None)) |
| self.assertIsNotNone( |
| self._registry.register_by_name('bar', __name__, '_no_docstring')) |
| |
| with self.assertRaises(plugins.Error): |
| self._registry.register('foo', lambda: None) |
| |
| with self.assertRaises(plugins.Error): |
| self._registry.register('bar', lambda: None) |
| |
| def test_register_directory_innermost_takes_priority(self) -> None: |
| with tempfile.TemporaryDirectory() as tempdir: |
| paths = list(_create_files(tempdir, _TEST_PLUGINS)) |
| self._registry.register_directory(paths[1].parent, 'TEST_PLUGINS') |
| |
| self.assertEqual(self._registry.run_with_argv('test_plugin', []), 123) |
| |
| def test_register_directory_only_searches_up(self) -> None: |
| with tempfile.TemporaryDirectory() as tempdir: |
| paths = list(_create_files(tempdir, _TEST_PLUGINS)) |
| self._registry.register_directory(paths[0].parent, 'TEST_PLUGINS') |
| |
| self.assertEqual(self._registry.run_with_argv('test_plugin', []), 456) |
| |
| def test_register_directory_with_restriction(self) -> None: |
| with tempfile.TemporaryDirectory() as tempdir: |
| paths = list(_create_files(tempdir, _TEST_PLUGINS)) |
| self._registry.register_directory(paths[0].parent, 'TEST_PLUGINS', |
| Path(tempdir, 'nested', 'in')) |
| |
| self.assertNotIn('other_plugin', self._registry) |
| |
| def test_register_same_file_multiple_times_no_error(self) -> None: |
| with tempfile.TemporaryDirectory() as tempdir: |
| paths = list(_create_files(tempdir, _TEST_PLUGINS)) |
| self._registry.register_file(paths[0]) |
| self._registry.register_file(paths[0]) |
| self._registry.register_file(paths[0]) |
| |
| self.assertIs(self._registry['test_plugin'].target, _with_docstring) |
| |
| def test_help_uses_function_or_module_docstring(self) -> None: |
| self.assertIsNotNone(self._registry.register('a', _no_docstring)) |
| self.assertIsNotNone(self._registry.register('b', _with_docstring)) |
| |
| self.assertIn(__doc__, '\n'.join(self._registry.detailed_help(['a']))) |
| |
| self.assertNotIn(__doc__, |
| '\n'.join(self._registry.detailed_help(['b']))) |
| self.assertIn(_with_docstring.__doc__, |
| '\n'.join(self._registry.detailed_help(['b']))) |
| |
| def test_empty_string_if_no_help(self) -> None: |
| fake_module_name = f'{__name__}.fake_module_for_test{id(self)}' |
| fake_module = types.ModuleType(fake_module_name) |
| self.assertIsNone(fake_module.__doc__) |
| |
| sys.modules[fake_module_name] = fake_module |
| |
| try: |
| |
| function = lambda: None |
| function.__module__ = fake_module_name |
| self.assertIsNotNone(self._registry.register('a', function)) |
| |
| self.assertEqual(self._registry['a'].help(full=False), '') |
| self.assertEqual(self._registry['a'].help(full=True), '') |
| finally: |
| del sys.modules[fake_module_name] |
| |
| def test_decorator_not_called(self) -> None: |
| @self._registry.plugin |
| def nifty() -> None: |
| pass |
| |
| self.assertEqual(self._registry['nifty'].target, nifty) |
| |
| def test_decorator_called_no_args(self) -> None: |
| @self._registry.plugin() |
| def nifty() -> None: |
| pass |
| |
| self.assertEqual(self._registry['nifty'].target, nifty) |
| |
| def test_decorator_called_with_args(self) -> None: |
| @self._registry.plugin(name='nifty') |
| def my_nifty_keen_plugin() -> None: |
| pass |
| |
| self.assertEqual(self._registry['nifty'].target, my_nifty_keen_plugin) |
| |
| |
| if __name__ == '__main__': |
| unittest.main() |