test_arguments.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404
  1. import sys
  2. from unittest import mock
  3. import pytest
  4. import click
  5. def test_nargs_star(runner):
  6. @click.command()
  7. @click.argument("src", nargs=-1)
  8. @click.argument("dst")
  9. def copy(src, dst):
  10. click.echo(f"src={'|'.join(src)}")
  11. click.echo(f"dst={dst}")
  12. result = runner.invoke(copy, ["foo.txt", "bar.txt", "dir"])
  13. assert not result.exception
  14. assert result.output.splitlines() == ["src=foo.txt|bar.txt", "dst=dir"]
  15. def test_argument_unbounded_nargs_cant_have_default(runner):
  16. with pytest.raises(TypeError, match="nargs=-1"):
  17. @click.command()
  18. @click.argument("src", nargs=-1, default=["42"])
  19. def copy(src):
  20. pass
  21. def test_nargs_tup(runner):
  22. @click.command()
  23. @click.argument("name", nargs=1)
  24. @click.argument("point", nargs=2, type=click.INT)
  25. def copy(name, point):
  26. click.echo(f"name={name}")
  27. x, y = point
  28. click.echo(f"point={x}/{y}")
  29. result = runner.invoke(copy, ["peter", "1", "2"])
  30. assert not result.exception
  31. assert result.output.splitlines() == ["name=peter", "point=1/2"]
  32. @pytest.mark.parametrize(
  33. "opts",
  34. [
  35. dict(type=(str, int)),
  36. dict(type=click.Tuple([str, int])),
  37. dict(nargs=2, type=click.Tuple([str, int])),
  38. dict(nargs=2, type=(str, int)),
  39. ],
  40. )
  41. def test_nargs_tup_composite(runner, opts):
  42. @click.command()
  43. @click.argument("item", **opts)
  44. def copy(item):
  45. name, id = item
  46. click.echo(f"name={name} id={id:d}")
  47. result = runner.invoke(copy, ["peter", "1"])
  48. assert result.exception is None
  49. assert result.output.splitlines() == ["name=peter id=1"]
  50. def test_nargs_err(runner):
  51. @click.command()
  52. @click.argument("x")
  53. def copy(x):
  54. click.echo(x)
  55. result = runner.invoke(copy, ["foo"])
  56. assert not result.exception
  57. assert result.output == "foo\n"
  58. result = runner.invoke(copy, ["foo", "bar"])
  59. assert result.exit_code == 2
  60. assert "Got unexpected extra argument (bar)" in result.output
  61. @pytest.mark.skip(reason="Can't monkeypatch sys.stdin.encoding in Arcadia Python")
  62. def test_bytes_args(runner, monkeypatch):
  63. @click.command()
  64. @click.argument("arg")
  65. def from_bytes(arg):
  66. assert isinstance(
  67. arg, str
  68. ), "UTF-8 encoded argument should be implicitly converted to Unicode"
  69. # Simulate empty locale environment variables
  70. monkeypatch.setattr(sys, "getfilesystemencoding", lambda: "utf-8")
  71. monkeypatch.setattr(sys, "getdefaultencoding", lambda: "utf-8")
  72. # sys.stdin.encoding is readonly, needs some extra effort to patch.
  73. stdin = mock.Mock(wraps=sys.stdin)
  74. stdin.encoding = "utf-8"
  75. monkeypatch.setattr(sys, "stdin", stdin)
  76. runner.invoke(
  77. from_bytes,
  78. ["Something outside of ASCII range: 林".encode()],
  79. catch_exceptions=False,
  80. )
  81. def test_file_args(runner):
  82. @click.command()
  83. @click.argument("input", type=click.File("rb"))
  84. @click.argument("output", type=click.File("wb"))
  85. def inout(input, output):
  86. while True:
  87. chunk = input.read(1024)
  88. if not chunk:
  89. break
  90. output.write(chunk)
  91. with runner.isolated_filesystem():
  92. result = runner.invoke(inout, ["-", "hello.txt"], input="Hey!")
  93. assert result.output == ""
  94. assert result.exit_code == 0
  95. with open("hello.txt", "rb") as f:
  96. assert f.read() == b"Hey!"
  97. result = runner.invoke(inout, ["hello.txt", "-"])
  98. assert result.output == "Hey!"
  99. assert result.exit_code == 0
  100. def test_path_allow_dash(runner):
  101. @click.command()
  102. @click.argument("input", type=click.Path(allow_dash=True))
  103. def foo(input):
  104. click.echo(input)
  105. result = runner.invoke(foo, ["-"])
  106. assert result.output == "-\n"
  107. assert result.exit_code == 0
  108. def test_file_atomics(runner):
  109. @click.command()
  110. @click.argument("output", type=click.File("wb", atomic=True))
  111. def inout(output):
  112. output.write(b"Foo bar baz\n")
  113. output.flush()
  114. with open(output.name, "rb") as f:
  115. old_content = f.read()
  116. assert old_content == b"OLD\n"
  117. with runner.isolated_filesystem():
  118. with open("foo.txt", "wb") as f:
  119. f.write(b"OLD\n")
  120. result = runner.invoke(inout, ["foo.txt"], input="Hey!", catch_exceptions=False)
  121. assert result.output == ""
  122. assert result.exit_code == 0
  123. with open("foo.txt", "rb") as f:
  124. assert f.read() == b"Foo bar baz\n"
  125. def test_stdout_default(runner):
  126. @click.command()
  127. @click.argument("output", type=click.File("w"), default="-")
  128. def inout(output):
  129. output.write("Foo bar baz\n")
  130. output.flush()
  131. result = runner.invoke(inout, [])
  132. assert not result.exception
  133. assert result.output == "Foo bar baz\n"
  134. @pytest.mark.parametrize(
  135. ("nargs", "value", "expect"),
  136. [
  137. (2, "", None),
  138. (2, "a", "Takes 2 values but 1 was given."),
  139. (2, "a b", ("a", "b")),
  140. (2, "a b c", "Takes 2 values but 3 were given."),
  141. (-1, "a b c", ("a", "b", "c")),
  142. (-1, "", ()),
  143. ],
  144. )
  145. def test_nargs_envvar(runner, nargs, value, expect):
  146. if nargs == -1:
  147. param = click.argument("arg", envvar="X", nargs=nargs)
  148. else:
  149. param = click.option("--arg", envvar="X", nargs=nargs)
  150. @click.command()
  151. @param
  152. def cmd(arg):
  153. return arg
  154. result = runner.invoke(cmd, env={"X": value}, standalone_mode=False)
  155. if isinstance(expect, str):
  156. assert isinstance(result.exception, click.BadParameter)
  157. assert expect in result.exception.format_message()
  158. else:
  159. assert result.return_value == expect
  160. def test_nargs_envvar_only_if_values_empty(runner):
  161. @click.command()
  162. @click.argument("arg", envvar="X", nargs=-1)
  163. def cli(arg):
  164. return arg
  165. result = runner.invoke(cli, ["a", "b"], standalone_mode=False)
  166. assert result.return_value == ("a", "b")
  167. result = runner.invoke(cli, env={"X": "a"}, standalone_mode=False)
  168. assert result.return_value == ("a",)
  169. def test_empty_nargs(runner):
  170. @click.command()
  171. @click.argument("arg", nargs=-1)
  172. def cmd(arg):
  173. click.echo(f"arg:{'|'.join(arg)}")
  174. result = runner.invoke(cmd, [])
  175. assert result.exit_code == 0
  176. assert result.output == "arg:\n"
  177. @click.command()
  178. @click.argument("arg", nargs=-1, required=True)
  179. def cmd2(arg):
  180. click.echo(f"arg:{'|'.join(arg)}")
  181. result = runner.invoke(cmd2, [])
  182. assert result.exit_code == 2
  183. assert "Missing argument 'ARG...'" in result.output
  184. def test_missing_arg(runner):
  185. @click.command()
  186. @click.argument("arg")
  187. def cmd(arg):
  188. click.echo(f"arg:{arg}")
  189. result = runner.invoke(cmd, [])
  190. assert result.exit_code == 2
  191. assert "Missing argument 'ARG'." in result.output
  192. def test_missing_argument_string_cast():
  193. ctx = click.Context(click.Command(""))
  194. with pytest.raises(click.MissingParameter) as excinfo:
  195. click.Argument(["a"], required=True).process_value(ctx, None)
  196. assert str(excinfo.value) == "Missing parameter: a"
  197. def test_implicit_non_required(runner):
  198. @click.command()
  199. @click.argument("f", default="test")
  200. def cli(f):
  201. click.echo(f)
  202. result = runner.invoke(cli, [])
  203. assert result.exit_code == 0
  204. assert result.output == "test\n"
  205. def test_eat_options(runner):
  206. @click.command()
  207. @click.option("-f")
  208. @click.argument("files", nargs=-1)
  209. def cmd(f, files):
  210. for filename in files:
  211. click.echo(filename)
  212. click.echo(f)
  213. result = runner.invoke(cmd, ["--", "-foo", "bar"])
  214. assert result.output.splitlines() == ["-foo", "bar", ""]
  215. result = runner.invoke(cmd, ["-f", "-x", "--", "-foo", "bar"])
  216. assert result.output.splitlines() == ["-foo", "bar", "-x"]
  217. def test_nargs_star_ordering(runner):
  218. @click.command()
  219. @click.argument("a", nargs=-1)
  220. @click.argument("b")
  221. @click.argument("c")
  222. def cmd(a, b, c):
  223. for arg in (a, b, c):
  224. click.echo(arg)
  225. result = runner.invoke(cmd, ["a", "b", "c"])
  226. assert result.output.splitlines() == ["('a',)", "b", "c"]
  227. def test_nargs_specified_plus_star_ordering(runner):
  228. @click.command()
  229. @click.argument("a", nargs=-1)
  230. @click.argument("b")
  231. @click.argument("c", nargs=2)
  232. def cmd(a, b, c):
  233. for arg in (a, b, c):
  234. click.echo(arg)
  235. result = runner.invoke(cmd, ["a", "b", "c", "d", "e", "f"])
  236. assert result.output.splitlines() == ["('a', 'b', 'c')", "d", "('e', 'f')"]
  237. def test_defaults_for_nargs(runner):
  238. @click.command()
  239. @click.argument("a", nargs=2, type=int, default=(1, 2))
  240. def cmd(a):
  241. x, y = a
  242. click.echo(x + y)
  243. result = runner.invoke(cmd, [])
  244. assert result.output.strip() == "3"
  245. result = runner.invoke(cmd, ["3", "4"])
  246. assert result.output.strip() == "7"
  247. result = runner.invoke(cmd, ["3"])
  248. assert result.exception is not None
  249. assert "Argument 'a' takes 2 values." in result.output
  250. def test_multiple_param_decls_not_allowed(runner):
  251. with pytest.raises(TypeError):
  252. @click.command()
  253. @click.argument("x", click.Choice(["a", "b"]))
  254. def copy(x):
  255. click.echo(x)
  256. def test_multiple_not_allowed():
  257. with pytest.raises(TypeError, match="multiple"):
  258. click.Argument(["a"], multiple=True)
  259. @pytest.mark.parametrize("value", [(), ("a",), ("a", "b", "c")])
  260. def test_nargs_bad_default(runner, value):
  261. with pytest.raises(ValueError, match="nargs=2"):
  262. click.Argument(["a"], nargs=2, default=value)
  263. def test_subcommand_help(runner):
  264. @click.group()
  265. @click.argument("name")
  266. @click.argument("val")
  267. @click.option("--opt")
  268. @click.pass_context
  269. def cli(ctx, name, val, opt):
  270. ctx.obj = dict(name=name, val=val)
  271. @cli.command()
  272. @click.pass_obj
  273. def cmd(obj):
  274. click.echo(f"CMD for {obj['name']} with value {obj['val']}")
  275. result = runner.invoke(cli, ["foo", "bar", "cmd", "--help"])
  276. assert not result.exception
  277. assert "Usage: cli NAME VAL cmd [OPTIONS]" in result.output
  278. def test_nested_subcommand_help(runner):
  279. @click.group()
  280. @click.argument("arg1")
  281. @click.option("--opt1")
  282. def cli(arg1, opt1):
  283. pass
  284. @cli.group()
  285. @click.argument("arg2")
  286. @click.option("--opt2")
  287. def cmd(arg2, opt2):
  288. pass
  289. @cmd.command()
  290. def subcmd():
  291. click.echo("subcommand")
  292. result = runner.invoke(cli, ["arg1", "cmd", "arg2", "subcmd", "--help"])
  293. assert not result.exception
  294. assert "Usage: cli ARG1 cmd ARG2 subcmd [OPTIONS]" in result.output
  295. def test_when_argument_decorator_is_used_multiple_times_cls_is_preserved():
  296. class CustomArgument(click.Argument):
  297. pass
  298. reusable_argument = click.argument("art", cls=CustomArgument)
  299. @click.command()
  300. @reusable_argument
  301. def foo(arg):
  302. pass
  303. @click.command()
  304. @reusable_argument
  305. def bar(arg):
  306. pass
  307. assert isinstance(foo.params[0], CustomArgument)
  308. assert isinstance(bar.params[0], CustomArgument)