1*61c4878aSAndroid Build Coastguard Worker# Copyright 2021 The Pigweed Authors 2*61c4878aSAndroid Build Coastguard Worker# 3*61c4878aSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License"); you may not 4*61c4878aSAndroid Build Coastguard Worker# use this file except in compliance with the License. You may obtain a copy of 5*61c4878aSAndroid Build Coastguard Worker# the License at 6*61c4878aSAndroid Build Coastguard Worker# 7*61c4878aSAndroid Build Coastguard Worker# https://www.apache.org/licenses/LICENSE-2.0 8*61c4878aSAndroid Build Coastguard Worker# 9*61c4878aSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software 10*61c4878aSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11*61c4878aSAndroid Build Coastguard Worker# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12*61c4878aSAndroid Build Coastguard Worker# License for the specific language governing permissions and limitations under 13*61c4878aSAndroid Build Coastguard Worker# the License. 14*61c4878aSAndroid Build Coastguard Worker"""Tests for pw_cli.plugins.""" 15*61c4878aSAndroid Build Coastguard Worker 16*61c4878aSAndroid Build Coastguard Workerfrom pathlib import Path 17*61c4878aSAndroid Build Coastguard Workerimport sys 18*61c4878aSAndroid Build Coastguard Workerimport tempfile 19*61c4878aSAndroid Build Coastguard Workerimport types 20*61c4878aSAndroid Build Coastguard Workerfrom typing import Iterator 21*61c4878aSAndroid Build Coastguard Workerimport unittest 22*61c4878aSAndroid Build Coastguard Worker 23*61c4878aSAndroid Build Coastguard Workerfrom pw_cli import plugins 24*61c4878aSAndroid Build Coastguard Worker 25*61c4878aSAndroid Build Coastguard Worker 26*61c4878aSAndroid Build Coastguard Workerdef _no_docstring() -> int: 27*61c4878aSAndroid Build Coastguard Worker return 123 28*61c4878aSAndroid Build Coastguard Worker 29*61c4878aSAndroid Build Coastguard Worker 30*61c4878aSAndroid Build Coastguard Workerdef _with_docstring() -> int: 31*61c4878aSAndroid Build Coastguard Worker """This docstring is brought to you courtesy of Pigweed.""" 32*61c4878aSAndroid Build Coastguard Worker return 456 33*61c4878aSAndroid Build Coastguard Worker 34*61c4878aSAndroid Build Coastguard Worker 35*61c4878aSAndroid Build Coastguard Workerdef _create_files(directory: str, files: dict[str, str]) -> Iterator[Path]: 36*61c4878aSAndroid Build Coastguard Worker for relative_path, contents in files.items(): 37*61c4878aSAndroid Build Coastguard Worker path = Path(directory) / relative_path 38*61c4878aSAndroid Build Coastguard Worker path.parent.mkdir(exist_ok=True, parents=True) 39*61c4878aSAndroid Build Coastguard Worker path.write_text(contents) 40*61c4878aSAndroid Build Coastguard Worker yield path 41*61c4878aSAndroid Build Coastguard Worker 42*61c4878aSAndroid Build Coastguard Worker 43*61c4878aSAndroid Build Coastguard Workerclass TestPlugin(unittest.TestCase): 44*61c4878aSAndroid Build Coastguard Worker """Tests for plugins.Plugins.""" 45*61c4878aSAndroid Build Coastguard Worker 46*61c4878aSAndroid Build Coastguard Worker def test_target_name_attribute(self) -> None: 47*61c4878aSAndroid Build Coastguard Worker self.assertEqual( 48*61c4878aSAndroid Build Coastguard Worker plugins.Plugin('abc', _no_docstring).target_name, 49*61c4878aSAndroid Build Coastguard Worker f'{__name__}._no_docstring', 50*61c4878aSAndroid Build Coastguard Worker ) 51*61c4878aSAndroid Build Coastguard Worker 52*61c4878aSAndroid Build Coastguard Worker def test_target_name_no_name_attribute(self) -> None: 53*61c4878aSAndroid Build Coastguard Worker has_no_name = 'no __name__' 54*61c4878aSAndroid Build Coastguard Worker self.assertFalse(hasattr(has_no_name, '__name__')) 55*61c4878aSAndroid Build Coastguard Worker 56*61c4878aSAndroid Build Coastguard Worker self.assertEqual( 57*61c4878aSAndroid Build Coastguard Worker plugins.Plugin('abc', has_no_name).target_name, 58*61c4878aSAndroid Build Coastguard Worker '<unknown>.no __name__', 59*61c4878aSAndroid Build Coastguard Worker ) 60*61c4878aSAndroid Build Coastguard Worker 61*61c4878aSAndroid Build Coastguard Worker 62*61c4878aSAndroid Build Coastguard Worker_TEST_PLUGINS = { 63*61c4878aSAndroid Build Coastguard Worker 'TEST_PLUGINS': ( 64*61c4878aSAndroid Build Coastguard Worker f'test_plugin {__name__} _with_docstring\n' 65*61c4878aSAndroid Build Coastguard Worker f'other_plugin {__name__} _no_docstring\n' 66*61c4878aSAndroid Build Coastguard Worker ), 67*61c4878aSAndroid Build Coastguard Worker 'nested/in/dirs/TEST_PLUGINS': f'test_plugin {__name__} _no_docstring\n', 68*61c4878aSAndroid Build Coastguard Worker} 69*61c4878aSAndroid Build Coastguard Worker 70*61c4878aSAndroid Build Coastguard Worker 71*61c4878aSAndroid Build Coastguard Workerclass TestPluginRegistry(unittest.TestCase): 72*61c4878aSAndroid Build Coastguard Worker """Tests for plugins.Registry.""" 73*61c4878aSAndroid Build Coastguard Worker 74*61c4878aSAndroid Build Coastguard Worker def setUp(self) -> None: 75*61c4878aSAndroid Build Coastguard Worker self._registry = plugins.Registry( 76*61c4878aSAndroid Build Coastguard Worker validator=plugins.callable_with_no_args 77*61c4878aSAndroid Build Coastguard Worker ) 78*61c4878aSAndroid Build Coastguard Worker 79*61c4878aSAndroid Build Coastguard Worker def test_register(self) -> None: 80*61c4878aSAndroid Build Coastguard Worker self.assertIsNotNone(self._registry.register('a_plugin', _no_docstring)) 81*61c4878aSAndroid Build Coastguard Worker self.assertIs(self._registry['a_plugin'].target, _no_docstring) 82*61c4878aSAndroid Build Coastguard Worker 83*61c4878aSAndroid Build Coastguard Worker def test_register_by_name(self) -> None: 84*61c4878aSAndroid Build Coastguard Worker self.assertIsNotNone( 85*61c4878aSAndroid Build Coastguard Worker self._registry.register_by_name( 86*61c4878aSAndroid Build Coastguard Worker 'plugin_one', __name__, '_no_docstring' 87*61c4878aSAndroid Build Coastguard Worker ) 88*61c4878aSAndroid Build Coastguard Worker ) 89*61c4878aSAndroid Build Coastguard Worker self.assertIsNotNone( 90*61c4878aSAndroid Build Coastguard Worker self._registry.register('plugin_two', _no_docstring) 91*61c4878aSAndroid Build Coastguard Worker ) 92*61c4878aSAndroid Build Coastguard Worker 93*61c4878aSAndroid Build Coastguard Worker self.assertIs(self._registry['plugin_one'].target, _no_docstring) 94*61c4878aSAndroid Build Coastguard Worker self.assertIs(self._registry['plugin_two'].target, _no_docstring) 95*61c4878aSAndroid Build Coastguard Worker 96*61c4878aSAndroid Build Coastguard Worker def test_register_by_name_undefined_module(self) -> None: 97*61c4878aSAndroid Build Coastguard Worker with self.assertRaisesRegex(plugins.Error, 'No module named'): 98*61c4878aSAndroid Build Coastguard Worker self._registry.register_by_name( 99*61c4878aSAndroid Build Coastguard Worker 'plugin_two', 'not a module', 'something' 100*61c4878aSAndroid Build Coastguard Worker ) 101*61c4878aSAndroid Build Coastguard Worker 102*61c4878aSAndroid Build Coastguard Worker def test_register_by_name_undefined_function(self) -> None: 103*61c4878aSAndroid Build Coastguard Worker with self.assertRaisesRegex(plugins.Error, 'does not exist'): 104*61c4878aSAndroid Build Coastguard Worker self._registry.register_by_name('plugin_two', __name__, 'nothing') 105*61c4878aSAndroid Build Coastguard Worker 106*61c4878aSAndroid Build Coastguard Worker def test_register_fails_validation(self) -> None: 107*61c4878aSAndroid Build Coastguard Worker with self.assertRaisesRegex(plugins.Error, 'must be callable'): 108*61c4878aSAndroid Build Coastguard Worker self._registry.register('plugin_two', 'not function') 109*61c4878aSAndroid Build Coastguard Worker 110*61c4878aSAndroid Build Coastguard Worker def test_run_with_argv_sets_sys_argv(self) -> None: 111*61c4878aSAndroid Build Coastguard Worker def stash_argv() -> int: 112*61c4878aSAndroid Build Coastguard Worker self.assertEqual(['pw go', '1', '2'], sys.argv) 113*61c4878aSAndroid Build Coastguard Worker return 1 114*61c4878aSAndroid Build Coastguard Worker 115*61c4878aSAndroid Build Coastguard Worker self.assertIsNotNone(self._registry.register('go', stash_argv)) 116*61c4878aSAndroid Build Coastguard Worker 117*61c4878aSAndroid Build Coastguard Worker original_argv = sys.argv 118*61c4878aSAndroid Build Coastguard Worker self.assertEqual(self._registry.run_with_argv('go', ['1', '2']), 1) 119*61c4878aSAndroid Build Coastguard Worker self.assertIs(sys.argv, original_argv) 120*61c4878aSAndroid Build Coastguard Worker 121*61c4878aSAndroid Build Coastguard Worker def test_run_with_argv_registered_plugin(self) -> None: 122*61c4878aSAndroid Build Coastguard Worker with self.assertRaises(KeyError): 123*61c4878aSAndroid Build Coastguard Worker self._registry.run_with_argv('plugin_one', []) 124*61c4878aSAndroid Build Coastguard Worker 125*61c4878aSAndroid Build Coastguard Worker def test_register_cannot_overwrite(self) -> None: 126*61c4878aSAndroid Build Coastguard Worker self.assertIsNotNone(self._registry.register('foo', lambda: None)) 127*61c4878aSAndroid Build Coastguard Worker self.assertIsNotNone( 128*61c4878aSAndroid Build Coastguard Worker self._registry.register_by_name('bar', __name__, '_no_docstring') 129*61c4878aSAndroid Build Coastguard Worker ) 130*61c4878aSAndroid Build Coastguard Worker 131*61c4878aSAndroid Build Coastguard Worker with self.assertRaises(plugins.Error): 132*61c4878aSAndroid Build Coastguard Worker self._registry.register('foo', lambda: None) 133*61c4878aSAndroid Build Coastguard Worker 134*61c4878aSAndroid Build Coastguard Worker with self.assertRaises(plugins.Error): 135*61c4878aSAndroid Build Coastguard Worker self._registry.register('bar', lambda: None) 136*61c4878aSAndroid Build Coastguard Worker 137*61c4878aSAndroid Build Coastguard Worker def test_register_same_file_multiple_times_no_error(self) -> None: 138*61c4878aSAndroid Build Coastguard Worker with tempfile.TemporaryDirectory() as tempdir: 139*61c4878aSAndroid Build Coastguard Worker paths = list(_create_files(tempdir, _TEST_PLUGINS)) 140*61c4878aSAndroid Build Coastguard Worker self._registry.register_file(paths[0]) 141*61c4878aSAndroid Build Coastguard Worker self._registry.register_file(paths[0]) 142*61c4878aSAndroid Build Coastguard Worker self._registry.register_file(paths[0]) 143*61c4878aSAndroid Build Coastguard Worker 144*61c4878aSAndroid Build Coastguard Worker self.assertIs(self._registry['test_plugin'].target, _with_docstring) 145*61c4878aSAndroid Build Coastguard Worker 146*61c4878aSAndroid Build Coastguard Worker def test_help_uses_function_or_module_docstring(self) -> None: 147*61c4878aSAndroid Build Coastguard Worker self.assertIsNotNone(self._registry.register('a', _no_docstring)) 148*61c4878aSAndroid Build Coastguard Worker self.assertIsNotNone(self._registry.register('b', _with_docstring)) 149*61c4878aSAndroid Build Coastguard Worker 150*61c4878aSAndroid Build Coastguard Worker self.assertIn(__doc__, '\n'.join(self._registry.detailed_help(['a']))) 151*61c4878aSAndroid Build Coastguard Worker 152*61c4878aSAndroid Build Coastguard Worker self.assertNotIn( 153*61c4878aSAndroid Build Coastguard Worker __doc__, '\n'.join(self._registry.detailed_help(['b'])) 154*61c4878aSAndroid Build Coastguard Worker ) 155*61c4878aSAndroid Build Coastguard Worker self.assertIn( 156*61c4878aSAndroid Build Coastguard Worker _with_docstring.__doc__, 157*61c4878aSAndroid Build Coastguard Worker '\n'.join(self._registry.detailed_help(['b'])), 158*61c4878aSAndroid Build Coastguard Worker ) 159*61c4878aSAndroid Build Coastguard Worker 160*61c4878aSAndroid Build Coastguard Worker def test_empty_string_if_no_help(self) -> None: 161*61c4878aSAndroid Build Coastguard Worker fake_module_name = f'{__name__}.fake_module_for_test{id(self)}' 162*61c4878aSAndroid Build Coastguard Worker fake_module = types.ModuleType(fake_module_name) 163*61c4878aSAndroid Build Coastguard Worker self.assertIsNone(fake_module.__doc__) 164*61c4878aSAndroid Build Coastguard Worker 165*61c4878aSAndroid Build Coastguard Worker sys.modules[fake_module_name] = fake_module 166*61c4878aSAndroid Build Coastguard Worker 167*61c4878aSAndroid Build Coastguard Worker try: 168*61c4878aSAndroid Build Coastguard Worker 169*61c4878aSAndroid Build Coastguard Worker def function(): 170*61c4878aSAndroid Build Coastguard Worker return None 171*61c4878aSAndroid Build Coastguard Worker 172*61c4878aSAndroid Build Coastguard Worker function.__module__ = fake_module_name 173*61c4878aSAndroid Build Coastguard Worker self.assertIsNotNone(self._registry.register('a', function)) 174*61c4878aSAndroid Build Coastguard Worker 175*61c4878aSAndroid Build Coastguard Worker self.assertEqual(self._registry['a'].help(full=False), '') 176*61c4878aSAndroid Build Coastguard Worker self.assertEqual(self._registry['a'].help(full=True), '') 177*61c4878aSAndroid Build Coastguard Worker finally: 178*61c4878aSAndroid Build Coastguard Worker del sys.modules[fake_module_name] 179*61c4878aSAndroid Build Coastguard Worker 180*61c4878aSAndroid Build Coastguard Worker def test_decorator_not_called(self) -> None: 181*61c4878aSAndroid Build Coastguard Worker @self._registry.plugin 182*61c4878aSAndroid Build Coastguard Worker def nifty() -> None: 183*61c4878aSAndroid Build Coastguard Worker pass 184*61c4878aSAndroid Build Coastguard Worker 185*61c4878aSAndroid Build Coastguard Worker self.assertEqual(self._registry['nifty'].target, nifty) 186*61c4878aSAndroid Build Coastguard Worker 187*61c4878aSAndroid Build Coastguard Worker def test_decorator_called_no_args(self) -> None: 188*61c4878aSAndroid Build Coastguard Worker @self._registry.plugin() 189*61c4878aSAndroid Build Coastguard Worker def nifty() -> None: 190*61c4878aSAndroid Build Coastguard Worker pass 191*61c4878aSAndroid Build Coastguard Worker 192*61c4878aSAndroid Build Coastguard Worker self.assertEqual(self._registry['nifty'].target, nifty) 193*61c4878aSAndroid Build Coastguard Worker 194*61c4878aSAndroid Build Coastguard Worker def test_decorator_called_with_args(self) -> None: 195*61c4878aSAndroid Build Coastguard Worker @self._registry.plugin(name='nifty') 196*61c4878aSAndroid Build Coastguard Worker def my_nifty_keen_plugin() -> None: 197*61c4878aSAndroid Build Coastguard Worker pass 198*61c4878aSAndroid Build Coastguard Worker 199*61c4878aSAndroid Build Coastguard Worker self.assertEqual(self._registry['nifty'].target, my_nifty_keen_plugin) 200*61c4878aSAndroid Build Coastguard Worker 201*61c4878aSAndroid Build Coastguard Worker 202*61c4878aSAndroid Build Coastguard Workerif __name__ == '__main__': 203*61c4878aSAndroid Build Coastguard Worker unittest.main() 204