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