python-click/tests/test_commands.py

414 lines
11 KiB
Python
Raw Normal View History

2014-10-16 20:40:34 +02:00
import re
2018-09-06 20:55:10 +02:00
2022-11-30 09:52:01 +01:00
import pytest
2014-10-16 20:40:34 +02:00
import click
def test_other_command_invoke(runner):
@click.command()
@click.pass_context
def cli(ctx):
return ctx.invoke(other_cmd, arg=42)
@click.command()
2020-07-21 08:23:42 +02:00
@click.argument("arg", type=click.INT)
2014-10-16 20:40:34 +02:00
def other_cmd(arg):
click.echo(arg)
result = runner.invoke(cli, [])
assert not result.exception
2020-07-21 08:23:42 +02:00
assert result.output == "42\n"
2014-10-16 20:40:34 +02:00
def test_other_command_forward(runner):
cli = click.Group()
@cli.command()
2020-07-21 08:23:42 +02:00
@click.option("--count", default=1)
2014-10-16 20:40:34 +02:00
def test(count):
2021-10-10 03:31:57 +02:00
click.echo(f"Count: {count:d}")
2014-10-16 20:40:34 +02:00
@cli.command()
2020-07-21 08:23:42 +02:00
@click.option("--count", default=1)
2014-10-16 20:40:34 +02:00
@click.pass_context
def dist(ctx, count):
ctx.forward(test)
ctx.invoke(test, count=42)
2020-07-21 08:23:42 +02:00
result = runner.invoke(cli, ["dist"])
2014-10-16 20:40:34 +02:00
assert not result.exception
2020-07-21 08:23:42 +02:00
assert result.output == "Count: 1\nCount: 42\n"
2014-10-16 20:40:34 +02:00
2021-10-10 03:31:57 +02:00
def test_forwarded_params_consistency(runner):
cli = click.Group()
@cli.command()
@click.option("-a")
@click.pass_context
def first(ctx, **kwargs):
click.echo(f"{ctx.params}")
@cli.command()
@click.option("-a")
@click.option("-b")
@click.pass_context
def second(ctx, **kwargs):
click.echo(f"{ctx.params}")
ctx.forward(first)
result = runner.invoke(cli, ["second", "-a", "foo", "-b", "bar"])
assert not result.exception
assert result.output == "{'a': 'foo', 'b': 'bar'}\n{'a': 'foo', 'b': 'bar'}\n"
2014-10-16 20:40:34 +02:00
def test_auto_shorthelp(runner):
@click.group()
def cli():
pass
@cli.command()
def short():
"""This is a short text."""
@cli.command()
def special_chars():
"""Login and store the token in ~/.netrc."""
@cli.command()
def long():
"""This is a long text that is too long to show as short help
and will be truncated instead."""
2020-07-21 08:23:42 +02:00
result = runner.invoke(cli, ["--help"])
assert (
re.search(
r"Commands:\n\s+"
r"long\s+This is a long text that is too long to show as short help"
r"\.\.\.\n\s+"
r"short\s+This is a short text\.\n\s+"
r"special-chars\s+Login and store the token in ~/.netrc\.\s*",
result.output,
)
is not None
)
2022-11-30 09:52:01 +01:00
def test_help_truncation(runner):
@click.command()
def cli():
"""This is a command with truncated help.
\f
This text should be truncated.
"""
result = runner.invoke(cli, ["--help"])
assert result.exit_code == 0
assert "This is a command with truncated help." in result.output
2020-07-21 08:23:42 +02:00
def test_no_args_is_help(runner):
@click.command(no_args_is_help=True)
def cli():
pass
result = runner.invoke(cli, [])
assert result.exit_code == 0
assert "Show this message and exit." in result.output
2014-10-16 20:40:34 +02:00
def test_default_maps(runner):
@click.group()
def cli():
pass
@cli.command()
2020-07-21 08:23:42 +02:00
@click.option("--name", default="normal")
2014-10-16 20:40:34 +02:00
def foo(name):
click.echo(name)
2020-07-21 08:23:42 +02:00
result = runner.invoke(cli, ["foo"], default_map={"foo": {"name": "changed"}})
2014-10-16 20:40:34 +02:00
assert not result.exception
2020-07-21 08:23:42 +02:00
assert result.output == "changed\n"
2014-10-16 20:40:34 +02:00
2022-11-30 09:52:01 +01:00
@pytest.mark.parametrize(
("args", "exit_code", "expect"),
[
(["obj1"], 2, "Error: Missing command."),
(["obj1", "--help"], 0, "Show this message and exit."),
(["obj1", "move"], 0, "obj=obj1\nmove\n"),
([], 0, "Show this message and exit."),
],
)
def test_group_with_args(runner, args, exit_code, expect):
2014-10-16 20:40:34 +02:00
@click.group()
2020-07-21 08:23:42 +02:00
@click.argument("obj")
2014-10-16 20:40:34 +02:00
def cli(obj):
2021-10-10 03:31:57 +02:00
click.echo(f"obj={obj}")
2014-10-16 20:40:34 +02:00
@cli.command()
def move():
2020-07-21 08:23:42 +02:00
click.echo("move")
2014-10-16 20:40:34 +02:00
2022-11-30 09:52:01 +01:00
result = runner.invoke(cli, args)
assert result.exit_code == exit_code
assert expect in result.output
2014-10-16 20:40:34 +02:00
def test_base_command(runner):
import optparse
@click.group()
def cli():
pass
class OptParseCommand(click.BaseCommand):
def __init__(self, name, parser, callback):
2021-10-10 03:31:57 +02:00
super().__init__(name)
2014-10-16 20:40:34 +02:00
self.parser = parser
self.callback = callback
def parse_args(self, ctx, args):
try:
opts, args = parser.parse_args(args)
except Exception as e:
ctx.fail(str(e))
ctx.args = args
ctx.params = vars(opts)
def get_usage(self, ctx):
return self.parser.get_usage()
def get_help(self, ctx):
return self.parser.format_help()
def invoke(self, ctx):
ctx.invoke(self.callback, ctx.args, **ctx.params)
2020-07-21 08:23:42 +02:00
parser = optparse.OptionParser(usage="Usage: foo test [OPTIONS]")
parser.add_option(
"-f", "--file", dest="filename", help="write report to FILE", metavar="FILE"
)
parser.add_option(
"-q",
"--quiet",
action="store_false",
dest="verbose",
default=True,
help="don't print status messages to stdout",
)
2014-10-16 20:40:34 +02:00
def test_callback(args, filename, verbose):
2020-07-21 08:23:42 +02:00
click.echo(" ".join(args))
2014-10-16 20:40:34 +02:00
click.echo(filename)
click.echo(verbose)
2020-07-21 08:23:42 +02:00
cli.add_command(OptParseCommand("test", parser, test_callback))
2022-11-30 09:52:01 +01:00
result = runner.invoke(cli, ["test", "-f", "f.txt", "-q", "q1.txt", "q2.txt"])
assert result.exception is None
assert result.output.splitlines() == ["q1.txt q2.txt", "f.txt", "False"]
2014-10-16 20:40:34 +02:00
2020-07-21 08:23:42 +02:00
result = runner.invoke(cli, ["test", "--help"])
2022-11-30 09:52:01 +01:00
assert result.exception is None
2014-10-16 20:40:34 +02:00
assert result.output.splitlines() == [
2020-07-21 08:23:42 +02:00
"Usage: foo test [OPTIONS]",
"",
"Options:",
" -h, --help show this help message and exit",
" -f FILE, --file=FILE write report to FILE",
" -q, --quiet don't print status messages to stdout",
2014-10-16 20:40:34 +02:00
]
def test_object_propagation(runner):
for chain in False, True:
2020-07-21 08:23:42 +02:00
2014-10-16 20:40:34 +02:00
@click.group(chain=chain)
2020-07-21 08:23:42 +02:00
@click.option("--debug/--no-debug", default=False)
2014-10-16 20:40:34 +02:00
@click.pass_context
def cli(ctx, debug):
if ctx.obj is None:
ctx.obj = {}
2020-07-21 08:23:42 +02:00
ctx.obj["DEBUG"] = debug
2014-10-16 20:40:34 +02:00
@cli.command()
@click.pass_context
def sync(ctx):
2021-10-10 03:31:57 +02:00
click.echo(f"Debug is {'on' if ctx.obj['DEBUG'] else 'off'}")
2014-10-16 20:40:34 +02:00
2020-07-21 08:23:42 +02:00
result = runner.invoke(cli, ["sync"])
2014-10-16 20:40:34 +02:00
assert result.exception is None
2020-07-21 08:23:42 +02:00
assert result.output == "Debug is off\n"
2014-10-16 20:40:34 +02:00
def test_other_command_invoke_with_defaults(runner):
@click.command()
@click.pass_context
def cli(ctx):
return ctx.invoke(other_cmd)
@click.command()
2022-01-03 02:50:02 +01:00
@click.option("-a", type=click.INT, default=42)
@click.option("-b", type=click.INT, default="15")
@click.option("-c", multiple=True)
2014-10-16 20:40:34 +02:00
@click.pass_context
2022-01-03 02:50:02 +01:00
def other_cmd(ctx, a, b, c):
return ctx.info_name, a, b, c
2014-10-16 20:40:34 +02:00
2022-01-03 02:50:02 +01:00
result = runner.invoke(cli, standalone_mode=False)
# invoke should type cast default values, str becomes int, empty
# multiple should be empty tuple instead of None
assert result.return_value == ("other-cmd", 42, 15, ())
2014-10-16 20:40:34 +02:00
def test_invoked_subcommand(runner):
@click.group(invoke_without_command=True)
@click.pass_context
def cli(ctx):
if ctx.invoked_subcommand is None:
2020-07-21 08:23:42 +02:00
click.echo("no subcommand, use default")
2014-10-16 20:40:34 +02:00
ctx.invoke(sync)
else:
2020-07-21 08:23:42 +02:00
click.echo("invoke subcommand")
2014-10-16 20:40:34 +02:00
@cli.command()
def sync():
2020-07-21 08:23:42 +02:00
click.echo("in subcommand")
2014-10-16 20:40:34 +02:00
2020-07-21 08:23:42 +02:00
result = runner.invoke(cli, ["sync"])
2014-10-16 20:40:34 +02:00
assert not result.exception
2020-07-21 08:23:42 +02:00
assert result.output == "invoke subcommand\nin subcommand\n"
2014-10-16 20:40:34 +02:00
result = runner.invoke(cli)
assert not result.exception
2020-07-21 08:23:42 +02:00
assert result.output == "no subcommand, use default\nin subcommand\n"
2015-07-16 14:26:14 +02:00
2021-10-10 03:31:57 +02:00
def test_aliased_command_canonical_name(runner):
class AliasedGroup(click.Group):
def get_command(self, ctx, cmd_name):
return push
def resolve_command(self, ctx, args):
_, command, args = super().resolve_command(ctx, args)
return command.name, command, args
cli = AliasedGroup()
@cli.command()
def push():
click.echo("push command")
result = runner.invoke(cli, ["pu", "--help"])
assert not result.exception
assert result.output.startswith("Usage: root push [OPTIONS]")
def test_group_add_command_name(runner):
cli = click.Group("cli")
cmd = click.Command("a", params=[click.Option(["-x"], required=True)])
cli.add_command(cmd, "b")
# Check that the command is accessed through the registered name,
# not the original name.
result = runner.invoke(cli, ["b"], default_map={"b": {"x": 3}})
assert result.exit_code == 0
2015-07-16 14:26:14 +02:00
def test_unprocessed_options(runner):
2020-07-21 08:23:42 +02:00
@click.command(context_settings=dict(ignore_unknown_options=True))
@click.argument("args", nargs=-1, type=click.UNPROCESSED)
@click.option("--verbose", "-v", count=True)
2015-07-16 14:26:14 +02:00
def cli(verbose, args):
2021-10-10 03:31:57 +02:00
click.echo(f"Verbosity: {verbose}")
click.echo(f"Args: {'|'.join(args)}")
2015-07-16 14:26:14 +02:00
2020-07-21 08:23:42 +02:00
result = runner.invoke(cli, ["-foo", "-vvvvx", "--muhaha", "x", "y", "-x"])
2015-07-16 14:26:14 +02:00
assert not result.exception
assert result.output.splitlines() == [
2020-07-21 08:23:42 +02:00
"Verbosity: 4",
"Args: -foo|-x|--muhaha|x|y|-x",
2015-07-16 14:26:14 +02:00
]
2018-09-06 20:55:10 +02:00
2022-11-30 09:52:01 +01:00
@pytest.mark.parametrize("doc", ["CLI HELP", None])
def test_deprecated_in_help_messages(runner, doc):
@click.command(deprecated=True, help=doc)
def cli():
2018-09-06 20:55:10 +02:00
pass
2022-11-30 09:52:01 +01:00
result = runner.invoke(cli, ["--help"])
2021-10-10 03:31:57 +02:00
assert "(Deprecated)" in result.output
2018-09-06 20:55:10 +02:00
def test_deprecated_in_invocation(runner):
@click.command(deprecated=True)
def deprecated_cmd():
2020-07-21 08:23:42 +02:00
pass
2018-09-06 20:55:10 +02:00
result = runner.invoke(deprecated_cmd)
2020-07-21 08:23:42 +02:00
assert "DeprecationWarning:" in result.output
2022-11-30 09:52:01 +01:00
def test_command_parse_args_collects_option_prefixes():
@click.command()
@click.option("+p", is_flag=True)
@click.option("!e", is_flag=True)
def test(p, e):
pass
ctx = click.Context(test)
test.parse_args(ctx, [])
assert ctx._opt_prefixes == {"-", "--", "+", "!"}
def test_group_parse_args_collects_base_option_prefixes():
@click.group()
@click.option("~t", is_flag=True)
def group(t):
pass
@group.command()
@click.option("+p", is_flag=True)
def command1(p):
pass
@group.command()
@click.option("!e", is_flag=True)
def command2(e):
pass
ctx = click.Context(group)
group.parse_args(ctx, ["command1", "+p"])
assert ctx._opt_prefixes == {"-", "--", "~"}
def test_group_invoke_collects_used_option_prefixes(runner):
opt_prefixes = set()
@click.group()
@click.option("~t", is_flag=True)
def group(t):
pass
@group.command()
@click.option("+p", is_flag=True)
@click.pass_context
def command1(ctx, p):
nonlocal opt_prefixes
opt_prefixes = ctx._opt_prefixes
@group.command()
@click.option("!e", is_flag=True)
def command2(e):
pass
runner.invoke(group, ["command1"])
assert opt_prefixes == {"-", "--", "~", "+"}