1from pkg_resources import EntryPoint 2from pkg_resources import iter_entry_points 3from pkg_resources import working_set 4 5import click 6from click_plugins import with_plugins 7import pytest 8 9 10# Create a few CLI commands for testing 11@click.command() 12@click.argument('arg') 13def cmd1(arg): 14 """Test command 1""" 15 click.echo('passed') 16 17@click.command() 18@click.argument('arg') 19def cmd2(arg): 20 """Test command 2""" 21 click.echo('passed') 22 23 24# Manually register plugins in an entry point and put broken plugins in a 25# different entry point. 26 27# The `DistStub()` class gets around an exception that is raised when 28# `entry_point.load()` is called. By default `load()` has `requires=True` 29# which calls `dist.requires()` and the `click.group()` decorator 30# doesn't allow us to change this. Because we are manually registering these 31# plugins the `dist` attribute is `None` so we can just create a stub that 32# always returns an empty list since we don't have any requirements. A full 33# `pkg_resources.Distribution()` instance is not needed because there isn't 34# a package installed anywhere. 35class DistStub(object): 36 def requires(self, *args): 37 return [] 38 39working_set.by_key['click']._ep_map = { 40 '_test_click_plugins.test_plugins': { 41 'cmd1': EntryPoint.parse( 42 'cmd1=tests.test_plugins:cmd1', dist=DistStub()), 43 'cmd2': EntryPoint.parse( 44 'cmd2=tests.test_plugins:cmd2', dist=DistStub()) 45 }, 46 '_test_click_plugins.broken_plugins': { 47 'before': EntryPoint.parse( 48 'before=tests.broken_plugins:before', dist=DistStub()), 49 'after': EntryPoint.parse( 50 'after=tests.broken_plugins:after', dist=DistStub()), 51 'do_not_exist': EntryPoint.parse( 52 'do_not_exist=tests.broken_plugins:do_not_exist', dist=DistStub()) 53 } 54} 55 56 57# Main CLI groups - one with good plugins attached and the other broken 58@with_plugins(iter_entry_points('_test_click_plugins.test_plugins')) 59@click.group() 60def good_cli(): 61 """Good CLI group.""" 62 pass 63 64@with_plugins(iter_entry_points('_test_click_plugins.broken_plugins')) 65@click.group() 66def broken_cli(): 67 """Broken CLI group.""" 68 pass 69 70 71def test_registered(): 72 # Make sure the plugins are properly registered. If this test fails it 73 # means that some of the for loops in other tests may not be executing. 74 assert len([ep for ep in iter_entry_points('_test_click_plugins.test_plugins')]) > 1 75 assert len([ep for ep in iter_entry_points('_test_click_plugins.broken_plugins')]) > 1 76 77 78def test_register_and_run(runner): 79 80 result = runner.invoke(good_cli) 81 assert result.exit_code == 0 82 83 for ep in iter_entry_points('_test_click_plugins.test_plugins'): 84 cmd_result = runner.invoke(good_cli, [ep.name, 'something']) 85 assert cmd_result.exit_code == 0 86 assert cmd_result.output.strip() == 'passed' 87 88 89def test_broken_register_and_run(runner): 90 91 result = runner.invoke(broken_cli) 92 assert result.exit_code == 0 93 assert u'\U0001F4A9' in result.output or u'\u2020' in result.output 94 95 for ep in iter_entry_points('_test_click_plugins.broken_plugins'): 96 cmd_result = runner.invoke(broken_cli, [ep.name]) 97 assert cmd_result.exit_code != 0 98 assert 'Traceback' in cmd_result.output 99 100 101def test_group_chain(runner): 102 103 # Attach a sub-group to a CLI and get execute it without arguments to make 104 # sure both the sub-group and all the parent group's commands are present 105 @good_cli.group() 106 def sub_cli(): 107 """Sub CLI.""" 108 pass 109 110 result = runner.invoke(good_cli) 111 assert result.exit_code == 0 112 assert sub_cli.name in result.output 113 for ep in iter_entry_points('_test_click_plugins.test_plugins'): 114 assert ep.name in result.output 115 116 # Same as above but the sub-group has plugins 117 @with_plugins(plugins=iter_entry_points('_test_click_plugins.test_plugins')) 118 @good_cli.group(name='sub-cli-plugins') 119 def sub_cli_plugins(): 120 """Sub CLI with plugins.""" 121 pass 122 123 result = runner.invoke(good_cli, ['sub-cli-plugins']) 124 assert result.exit_code == 0 125 for ep in iter_entry_points('_test_click_plugins.test_plugins'): 126 assert ep.name in result.output 127 128 # Execute one of the sub-group's commands 129 result = runner.invoke(good_cli, ['sub-cli-plugins', 'cmd1', 'something']) 130 assert result.exit_code == 0 131 assert result.output.strip() == 'passed' 132 133 134def test_exception(): 135 # Decorating something that isn't a click.Group() should fail 136 with pytest.raises(TypeError): 137 @with_plugins([]) 138 @click.command() 139 def cli(): 140 """Whatever""" 141 142 143def test_broken_register_and_run_with_help(runner): 144 result = runner.invoke(broken_cli) 145 assert result.exit_code == 0 146 assert u'\U0001F4A9' in result.output or u'\u2020' in result.output 147 148 for ep in iter_entry_points('_test_click_plugins.broken_plugins'): 149 cmd_result = runner.invoke(broken_cli, [ep.name, "--help"]) 150 assert cmd_result.exit_code != 0 151 assert 'Traceback' in cmd_result.output 152 153 154def test_broken_register_and_run_with_args(runner): 155 result = runner.invoke(broken_cli) 156 assert result.exit_code == 0 157 assert u'\U0001F4A9' in result.output or u'\u2020' in result.output 158 159 for ep in iter_entry_points('_test_click_plugins.broken_plugins'): 160 cmd_result = runner.invoke(broken_cli, [ep.name, "-a", "b"]) 161 assert cmd_result.exit_code != 0 162 assert 'Traceback' in cmd_result.output 163