test_commands.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  1. import re
  2. import pytest
  3. import click
  4. def test_other_command_invoke(runner):
  5. @click.command()
  6. @click.pass_context
  7. def cli(ctx):
  8. return ctx.invoke(other_cmd, arg=42)
  9. @click.command()
  10. @click.argument("arg", type=click.INT)
  11. def other_cmd(arg):
  12. click.echo(arg)
  13. result = runner.invoke(cli, [])
  14. assert not result.exception
  15. assert result.output == "42\n"
  16. def test_other_command_forward(runner):
  17. cli = click.Group()
  18. @cli.command()
  19. @click.option("--count", default=1)
  20. def test(count):
  21. click.echo(f"Count: {count:d}")
  22. @cli.command()
  23. @click.option("--count", default=1)
  24. @click.pass_context
  25. def dist(ctx, count):
  26. ctx.forward(test)
  27. ctx.invoke(test, count=42)
  28. result = runner.invoke(cli, ["dist"])
  29. assert not result.exception
  30. assert result.output == "Count: 1\nCount: 42\n"
  31. def test_forwarded_params_consistency(runner):
  32. cli = click.Group()
  33. @cli.command()
  34. @click.option("-a")
  35. @click.pass_context
  36. def first(ctx, **kwargs):
  37. click.echo(f"{ctx.params}")
  38. @cli.command()
  39. @click.option("-a")
  40. @click.option("-b")
  41. @click.pass_context
  42. def second(ctx, **kwargs):
  43. click.echo(f"{ctx.params}")
  44. ctx.forward(first)
  45. result = runner.invoke(cli, ["second", "-a", "foo", "-b", "bar"])
  46. assert not result.exception
  47. assert result.output == "{'a': 'foo', 'b': 'bar'}\n{'a': 'foo', 'b': 'bar'}\n"
  48. def test_auto_shorthelp(runner):
  49. @click.group()
  50. def cli():
  51. pass
  52. @cli.command()
  53. def short():
  54. """This is a short text."""
  55. @cli.command()
  56. def special_chars():
  57. """Login and store the token in ~/.netrc."""
  58. @cli.command()
  59. def long():
  60. """This is a long text that is too long to show as short help
  61. and will be truncated instead."""
  62. result = runner.invoke(cli, ["--help"])
  63. assert (
  64. re.search(
  65. r"Commands:\n\s+"
  66. r"long\s+This is a long text that is too long to show as short help"
  67. r"\.\.\.\n\s+"
  68. r"short\s+This is a short text\.\n\s+"
  69. r"special-chars\s+Login and store the token in ~/.netrc\.\s*",
  70. result.output,
  71. )
  72. is not None
  73. )
  74. def test_no_args_is_help(runner):
  75. @click.command(no_args_is_help=True)
  76. def cli():
  77. pass
  78. result = runner.invoke(cli, [])
  79. assert result.exit_code == 0
  80. assert "Show this message and exit." in result.output
  81. def test_default_maps(runner):
  82. @click.group()
  83. def cli():
  84. pass
  85. @cli.command()
  86. @click.option("--name", default="normal")
  87. def foo(name):
  88. click.echo(name)
  89. result = runner.invoke(cli, ["foo"], default_map={"foo": {"name": "changed"}})
  90. assert not result.exception
  91. assert result.output == "changed\n"
  92. @pytest.mark.parametrize(
  93. ("args", "exit_code", "expect"),
  94. [
  95. (["obj1"], 2, "Error: Missing command."),
  96. (["obj1", "--help"], 0, "Show this message and exit."),
  97. (["obj1", "move"], 0, "obj=obj1\nmove\n"),
  98. ([], 0, "Show this message and exit."),
  99. ],
  100. )
  101. def test_group_with_args(runner, args, exit_code, expect):
  102. @click.group()
  103. @click.argument("obj")
  104. def cli(obj):
  105. click.echo(f"obj={obj}")
  106. @cli.command()
  107. def move():
  108. click.echo("move")
  109. result = runner.invoke(cli, args)
  110. assert result.exit_code == exit_code
  111. assert expect in result.output
  112. def test_base_command(runner):
  113. import optparse
  114. @click.group()
  115. def cli():
  116. pass
  117. class OptParseCommand(click.BaseCommand):
  118. def __init__(self, name, parser, callback):
  119. super().__init__(name)
  120. self.parser = parser
  121. self.callback = callback
  122. def parse_args(self, ctx, args):
  123. try:
  124. opts, args = parser.parse_args(args)
  125. except Exception as e:
  126. ctx.fail(str(e))
  127. ctx.args = args
  128. ctx.params = vars(opts)
  129. def get_usage(self, ctx):
  130. return self.parser.get_usage()
  131. def get_help(self, ctx):
  132. return self.parser.format_help()
  133. def invoke(self, ctx):
  134. ctx.invoke(self.callback, ctx.args, **ctx.params)
  135. parser = optparse.OptionParser(usage="Usage: foo test [OPTIONS]")
  136. parser.add_option(
  137. "-f", "--file", dest="filename", help="write report to FILE", metavar="FILE"
  138. )
  139. parser.add_option(
  140. "-q",
  141. "--quiet",
  142. action="store_false",
  143. dest="verbose",
  144. default=True,
  145. help="don't print status messages to stdout",
  146. )
  147. def test_callback(args, filename, verbose):
  148. click.echo(" ".join(args))
  149. click.echo(filename)
  150. click.echo(verbose)
  151. cli.add_command(OptParseCommand("test", parser, test_callback))
  152. result = runner.invoke(cli, ["test", "-f", "f.txt", "-q", "q1.txt", "q2.txt"])
  153. assert result.exception is None
  154. assert result.output.splitlines() == ["q1.txt q2.txt", "f.txt", "False"]
  155. result = runner.invoke(cli, ["test", "--help"])
  156. assert result.exception is None
  157. assert result.output.splitlines() == [
  158. "Usage: foo test [OPTIONS]",
  159. "",
  160. "Options:",
  161. " -h, --help show this help message and exit",
  162. " -f FILE, --file=FILE write report to FILE",
  163. " -q, --quiet don't print status messages to stdout",
  164. ]
  165. def test_object_propagation(runner):
  166. for chain in False, True:
  167. @click.group(chain=chain)
  168. @click.option("--debug/--no-debug", default=False)
  169. @click.pass_context
  170. def cli(ctx, debug):
  171. if ctx.obj is None:
  172. ctx.obj = {}
  173. ctx.obj["DEBUG"] = debug
  174. @cli.command()
  175. @click.pass_context
  176. def sync(ctx):
  177. click.echo(f"Debug is {'on' if ctx.obj['DEBUG'] else 'off'}")
  178. result = runner.invoke(cli, ["sync"])
  179. assert result.exception is None
  180. assert result.output == "Debug is off\n"
  181. def test_other_command_invoke_with_defaults(runner):
  182. @click.command()
  183. @click.pass_context
  184. def cli(ctx):
  185. return ctx.invoke(other_cmd)
  186. @click.command()
  187. @click.option("-a", type=click.INT, default=42)
  188. @click.option("-b", type=click.INT, default="15")
  189. @click.option("-c", multiple=True)
  190. @click.pass_context
  191. def other_cmd(ctx, a, b, c):
  192. return ctx.info_name, a, b, c
  193. result = runner.invoke(cli, standalone_mode=False)
  194. # invoke should type cast default values, str becomes int, empty
  195. # multiple should be empty tuple instead of None
  196. assert result.return_value == ("other-cmd", 42, 15, ())
  197. def test_invoked_subcommand(runner):
  198. @click.group(invoke_without_command=True)
  199. @click.pass_context
  200. def cli(ctx):
  201. if ctx.invoked_subcommand is None:
  202. click.echo("no subcommand, use default")
  203. ctx.invoke(sync)
  204. else:
  205. click.echo("invoke subcommand")
  206. @cli.command()
  207. def sync():
  208. click.echo("in subcommand")
  209. result = runner.invoke(cli, ["sync"])
  210. assert not result.exception
  211. assert result.output == "invoke subcommand\nin subcommand\n"
  212. result = runner.invoke(cli)
  213. assert not result.exception
  214. assert result.output == "no subcommand, use default\nin subcommand\n"
  215. def test_aliased_command_canonical_name(runner):
  216. class AliasedGroup(click.Group):
  217. def get_command(self, ctx, cmd_name):
  218. return push
  219. def resolve_command(self, ctx, args):
  220. _, command, args = super().resolve_command(ctx, args)
  221. return command.name, command, args
  222. cli = AliasedGroup()
  223. @cli.command()
  224. def push():
  225. click.echo("push command")
  226. result = runner.invoke(cli, ["pu", "--help"])
  227. assert not result.exception
  228. assert result.output.startswith("Usage: root push [OPTIONS]")
  229. def test_group_add_command_name(runner):
  230. cli = click.Group("cli")
  231. cmd = click.Command("a", params=[click.Option(["-x"], required=True)])
  232. cli.add_command(cmd, "b")
  233. # Check that the command is accessed through the registered name,
  234. # not the original name.
  235. result = runner.invoke(cli, ["b"], default_map={"b": {"x": 3}})
  236. assert result.exit_code == 0
  237. def test_unprocessed_options(runner):
  238. @click.command(context_settings=dict(ignore_unknown_options=True))
  239. @click.argument("args", nargs=-1, type=click.UNPROCESSED)
  240. @click.option("--verbose", "-v", count=True)
  241. def cli(verbose, args):
  242. click.echo(f"Verbosity: {verbose}")
  243. click.echo(f"Args: {'|'.join(args)}")
  244. result = runner.invoke(cli, ["-foo", "-vvvvx", "--muhaha", "x", "y", "-x"])
  245. assert not result.exception
  246. assert result.output.splitlines() == [
  247. "Verbosity: 4",
  248. "Args: -foo|-x|--muhaha|x|y|-x",
  249. ]
  250. @pytest.mark.parametrize("doc", ["CLI HELP", None])
  251. def test_deprecated_in_help_messages(runner, doc):
  252. @click.command(deprecated=True, help=doc)
  253. def cli():
  254. pass
  255. result = runner.invoke(cli, ["--help"])
  256. assert "(Deprecated)" in result.output
  257. def test_deprecated_in_invocation(runner):
  258. @click.command(deprecated=True)
  259. def deprecated_cmd():
  260. pass
  261. result = runner.invoke(deprecated_cmd)
  262. assert "DeprecationWarning:" in result.output
  263. def test_command_parse_args_collects_option_prefixes():
  264. @click.command()
  265. @click.option("+p", is_flag=True)
  266. @click.option("!e", is_flag=True)
  267. def test(p, e):
  268. pass
  269. ctx = click.Context(test)
  270. test.parse_args(ctx, [])
  271. assert ctx._opt_prefixes == {"-", "--", "+", "!"}
  272. def test_group_parse_args_collects_base_option_prefixes():
  273. @click.group()
  274. @click.option("~t", is_flag=True)
  275. def group(t):
  276. pass
  277. @group.command()
  278. @click.option("+p", is_flag=True)
  279. def command1(p):
  280. pass
  281. @group.command()
  282. @click.option("!e", is_flag=True)
  283. def command2(e):
  284. pass
  285. ctx = click.Context(group)
  286. group.parse_args(ctx, ["command1", "+p"])
  287. assert ctx._opt_prefixes == {"-", "--", "~"}
  288. def test_group_invoke_collects_used_option_prefixes(runner):
  289. opt_prefixes = set()
  290. @click.group()
  291. @click.option("~t", is_flag=True)
  292. def group(t):
  293. pass
  294. @group.command()
  295. @click.option("+p", is_flag=True)
  296. @click.pass_context
  297. def command1(ctx, p):
  298. nonlocal opt_prefixes
  299. opt_prefixes = ctx._opt_prefixes
  300. @group.command()
  301. @click.option("!e", is_flag=True)
  302. def command2(e):
  303. pass
  304. runner.invoke(group, ["command1"])
  305. assert opt_prefixes == {"-", "--", "~", "+"}
  306. @pytest.mark.parametrize("exc", (EOFError, KeyboardInterrupt))
  307. def test_abort_exceptions_with_disabled_standalone_mode(runner, exc):
  308. @click.command()
  309. def cli():
  310. raise exc("catch me!")
  311. rv = runner.invoke(cli, standalone_mode=False)
  312. assert rv.exit_code == 1
  313. assert isinstance(rv.exception.__cause__, exc)
  314. assert rv.exception.__cause__.args == ("catch me!",)