test_options.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558
  1. # -*- coding: utf-8 -*-
  2. import os
  3. import re
  4. import pytest
  5. import click
  6. from click._compat import text_type
  7. def test_prefixes(runner):
  8. @click.command()
  9. @click.option("++foo", is_flag=True, help="das foo")
  10. @click.option("--bar", is_flag=True, help="das bar")
  11. def cli(foo, bar):
  12. click.echo("foo={} bar={}".format(foo, bar))
  13. result = runner.invoke(cli, ["++foo", "--bar"])
  14. assert not result.exception
  15. assert result.output == "foo=True bar=True\n"
  16. result = runner.invoke(cli, ["--help"])
  17. assert re.search(r"\+\+foo\s+das foo", result.output) is not None
  18. assert re.search(r"--bar\s+das bar", result.output) is not None
  19. def test_invalid_option(runner):
  20. with pytest.raises(TypeError, match="name was passed"):
  21. @click.command()
  22. @click.option("foo")
  23. def cli(foo):
  24. pass
  25. def test_invalid_nargs(runner):
  26. with pytest.raises(TypeError, match="nargs < 0"):
  27. @click.command()
  28. @click.option("--foo", nargs=-1)
  29. def cli(foo):
  30. pass
  31. def test_nargs_tup_composite_mult(runner):
  32. @click.command()
  33. @click.option("--item", type=(str, int), multiple=True)
  34. def copy(item):
  35. for item in item:
  36. click.echo("name={0[0]} id={0[1]:d}".format(item))
  37. result = runner.invoke(copy, ["--item", "peter", "1", "--item", "max", "2"])
  38. assert not result.exception
  39. assert result.output.splitlines() == ["name=peter id=1", "name=max id=2"]
  40. def test_counting(runner):
  41. @click.command()
  42. @click.option("-v", count=True, help="Verbosity", type=click.IntRange(0, 3))
  43. def cli(v):
  44. click.echo("verbosity={:d}".format(v))
  45. result = runner.invoke(cli, ["-vvv"])
  46. assert not result.exception
  47. assert result.output == "verbosity=3\n"
  48. result = runner.invoke(cli, ["-vvvv"])
  49. assert result.exception
  50. assert (
  51. "Invalid value for '-v': 4 is not in the valid range of 0 to 3."
  52. in result.output
  53. )
  54. result = runner.invoke(cli, [])
  55. assert not result.exception
  56. assert result.output == "verbosity=0\n"
  57. result = runner.invoke(cli, ["--help"])
  58. assert re.search(r"-v\s+Verbosity", result.output) is not None
  59. @pytest.mark.parametrize("unknown_flag", ["--foo", "-f"])
  60. def test_unknown_options(runner, unknown_flag):
  61. @click.command()
  62. def cli():
  63. pass
  64. result = runner.invoke(cli, [unknown_flag])
  65. assert result.exception
  66. assert "no such option: {}".format(unknown_flag) in result.output
  67. def test_multiple_required(runner):
  68. @click.command()
  69. @click.option("-m", "--message", multiple=True, required=True)
  70. def cli(message):
  71. click.echo("\n".join(message))
  72. result = runner.invoke(cli, ["-m", "foo", "-mbar"])
  73. assert not result.exception
  74. assert result.output == "foo\nbar\n"
  75. result = runner.invoke(cli, [])
  76. assert result.exception
  77. assert "Error: Missing option '-m' / '--message'." in result.output
  78. def test_empty_envvar(runner):
  79. @click.command()
  80. @click.option("--mypath", type=click.Path(exists=True), envvar="MYPATH")
  81. def cli(mypath):
  82. click.echo("mypath: {}".format(mypath))
  83. result = runner.invoke(cli, [], env={"MYPATH": ""})
  84. assert result.exit_code == 0
  85. assert result.output == "mypath: None\n"
  86. def test_multiple_envvar(runner):
  87. @click.command()
  88. @click.option("--arg", multiple=True)
  89. def cmd(arg):
  90. click.echo("|".join(arg))
  91. result = runner.invoke(
  92. cmd, [], auto_envvar_prefix="TEST", env={"TEST_ARG": "foo bar baz"}
  93. )
  94. assert not result.exception
  95. assert result.output == "foo|bar|baz\n"
  96. @click.command()
  97. @click.option("--arg", multiple=True, envvar="X")
  98. def cmd(arg):
  99. click.echo("|".join(arg))
  100. result = runner.invoke(cmd, [], env={"X": "foo bar baz"})
  101. assert not result.exception
  102. assert result.output == "foo|bar|baz\n"
  103. @click.command()
  104. @click.option("--arg", multiple=True, type=click.Path())
  105. def cmd(arg):
  106. click.echo("|".join(arg))
  107. result = runner.invoke(
  108. cmd,
  109. [],
  110. auto_envvar_prefix="TEST",
  111. env={"TEST_ARG": "foo{}bar".format(os.path.pathsep)},
  112. )
  113. assert not result.exception
  114. assert result.output == "foo|bar\n"
  115. def test_multiple_default_help(runner):
  116. @click.command()
  117. @click.option("--arg1", multiple=True, default=("foo", "bar"), show_default=True)
  118. @click.option("--arg2", multiple=True, default=(1, 2), type=int, show_default=True)
  119. def cmd(arg, arg2):
  120. pass
  121. result = runner.invoke(cmd, ["--help"])
  122. assert not result.exception
  123. assert "foo, bar" in result.output
  124. assert "1, 2" in result.output
  125. def test_multiple_default_type(runner):
  126. @click.command()
  127. @click.option("--arg1", multiple=True, default=("foo", "bar"))
  128. @click.option("--arg2", multiple=True, default=(1, "a"))
  129. def cmd(arg1, arg2):
  130. assert all(isinstance(e[0], text_type) for e in arg1)
  131. assert all(isinstance(e[1], text_type) for e in arg1)
  132. assert all(isinstance(e[0], int) for e in arg2)
  133. assert all(isinstance(e[1], text_type) for e in arg2)
  134. result = runner.invoke(
  135. cmd, "--arg1 a b --arg1 test 1 --arg2 2 two --arg2 4 four".split()
  136. )
  137. assert not result.exception
  138. def test_dynamic_default_help_unset(runner):
  139. @click.command()
  140. @click.option(
  141. "--username",
  142. prompt=True,
  143. default=lambda: os.environ.get("USER", ""),
  144. show_default=True,
  145. )
  146. def cmd(username):
  147. print("Hello,", username)
  148. result = runner.invoke(cmd, ["--help"])
  149. assert result.exit_code == 0
  150. assert "--username" in result.output
  151. assert "lambda" not in result.output
  152. assert "(dynamic)" in result.output
  153. def test_dynamic_default_help_text(runner):
  154. @click.command()
  155. @click.option(
  156. "--username",
  157. prompt=True,
  158. default=lambda: os.environ.get("USER", ""),
  159. show_default="current user",
  160. )
  161. def cmd(username):
  162. print("Hello,", username)
  163. result = runner.invoke(cmd, ["--help"])
  164. assert result.exit_code == 0
  165. assert "--username" in result.output
  166. assert "lambda" not in result.output
  167. assert "(current user)" in result.output
  168. def test_toupper_envvar_prefix(runner):
  169. @click.command()
  170. @click.option("--arg")
  171. def cmd(arg):
  172. click.echo(arg)
  173. result = runner.invoke(cmd, [], auto_envvar_prefix="test", env={"TEST_ARG": "foo"})
  174. assert not result.exception
  175. assert result.output == "foo\n"
  176. def test_nargs_envvar(runner):
  177. @click.command()
  178. @click.option("--arg", nargs=2)
  179. def cmd(arg):
  180. click.echo("|".join(arg))
  181. result = runner.invoke(
  182. cmd, [], auto_envvar_prefix="TEST", env={"TEST_ARG": "foo bar"}
  183. )
  184. assert not result.exception
  185. assert result.output == "foo|bar\n"
  186. @click.command()
  187. @click.option("--arg", nargs=2, multiple=True)
  188. def cmd(arg):
  189. for item in arg:
  190. click.echo("|".join(item))
  191. result = runner.invoke(
  192. cmd, [], auto_envvar_prefix="TEST", env={"TEST_ARG": "x 1 y 2"}
  193. )
  194. assert not result.exception
  195. assert result.output == "x|1\ny|2\n"
  196. def test_show_envvar(runner):
  197. @click.command()
  198. @click.option("--arg1", envvar="ARG1", show_envvar=True)
  199. def cmd(arg):
  200. pass
  201. result = runner.invoke(cmd, ["--help"])
  202. assert not result.exception
  203. assert "ARG1" in result.output
  204. def test_show_envvar_auto_prefix(runner):
  205. @click.command()
  206. @click.option("--arg1", show_envvar=True)
  207. def cmd(arg):
  208. pass
  209. result = runner.invoke(cmd, ["--help"], auto_envvar_prefix="TEST")
  210. assert not result.exception
  211. assert "TEST_ARG1" in result.output
  212. def test_show_envvar_auto_prefix_dash_in_command(runner):
  213. @click.group()
  214. def cli():
  215. pass
  216. @cli.command()
  217. @click.option("--baz", show_envvar=True)
  218. def foo_bar(baz):
  219. pass
  220. result = runner.invoke(cli, ["foo-bar", "--help"], auto_envvar_prefix="TEST")
  221. assert not result.exception
  222. assert "TEST_FOO_BAR_BAZ" in result.output
  223. def test_custom_validation(runner):
  224. def validate_pos_int(ctx, param, value):
  225. if value < 0:
  226. raise click.BadParameter("Value needs to be positive")
  227. return value
  228. @click.command()
  229. @click.option("--foo", callback=validate_pos_int, default=1)
  230. def cmd(foo):
  231. click.echo(foo)
  232. result = runner.invoke(cmd, ["--foo", "-1"])
  233. assert "Invalid value for '--foo': Value needs to be positive" in result.output
  234. result = runner.invoke(cmd, ["--foo", "42"])
  235. assert result.output == "42\n"
  236. def test_winstyle_options(runner):
  237. @click.command()
  238. @click.option("/debug;/no-debug", help="Enables or disables debug mode.")
  239. def cmd(debug):
  240. click.echo(debug)
  241. result = runner.invoke(cmd, ["/debug"], help_option_names=["/?"])
  242. assert result.output == "True\n"
  243. result = runner.invoke(cmd, ["/no-debug"], help_option_names=["/?"])
  244. assert result.output == "False\n"
  245. result = runner.invoke(cmd, [], help_option_names=["/?"])
  246. assert result.output == "False\n"
  247. result = runner.invoke(cmd, ["/?"], help_option_names=["/?"])
  248. assert "/debug; /no-debug Enables or disables debug mode." in result.output
  249. assert "/? Show this message and exit." in result.output
  250. def test_legacy_options(runner):
  251. @click.command()
  252. @click.option("-whatever")
  253. def cmd(whatever):
  254. click.echo(whatever)
  255. result = runner.invoke(cmd, ["-whatever", "42"])
  256. assert result.output == "42\n"
  257. result = runner.invoke(cmd, ["-whatever=23"])
  258. assert result.output == "23\n"
  259. def test_missing_option_string_cast():
  260. ctx = click.Context(click.Command(""))
  261. with pytest.raises(click.MissingParameter) as excinfo:
  262. click.Option(["-a"], required=True).full_process_value(ctx, None)
  263. assert str(excinfo.value) == "missing parameter: a"
  264. def test_missing_choice(runner):
  265. @click.command()
  266. @click.option("--foo", type=click.Choice(["foo", "bar"]), required=True)
  267. def cmd(foo):
  268. click.echo(foo)
  269. result = runner.invoke(cmd)
  270. assert result.exit_code == 2
  271. error, separator, choices = result.output.partition("Choose from")
  272. assert "Error: Missing option '--foo'. " in error
  273. assert "Choose from" in separator
  274. assert "foo" in choices
  275. assert "bar" in choices
  276. def test_case_insensitive_choice(runner):
  277. @click.command()
  278. @click.option("--foo", type=click.Choice(["Orange", "Apple"], case_sensitive=False))
  279. def cmd(foo):
  280. click.echo(foo)
  281. result = runner.invoke(cmd, ["--foo", "apple"])
  282. assert result.exit_code == 0
  283. assert result.output == "Apple\n"
  284. result = runner.invoke(cmd, ["--foo", "oRANGe"])
  285. assert result.exit_code == 0
  286. assert result.output == "Orange\n"
  287. result = runner.invoke(cmd, ["--foo", "Apple"])
  288. assert result.exit_code == 0
  289. assert result.output == "Apple\n"
  290. @click.command()
  291. @click.option("--foo", type=click.Choice(["Orange", "Apple"]))
  292. def cmd2(foo):
  293. click.echo(foo)
  294. result = runner.invoke(cmd2, ["--foo", "apple"])
  295. assert result.exit_code == 2
  296. result = runner.invoke(cmd2, ["--foo", "oRANGe"])
  297. assert result.exit_code == 2
  298. result = runner.invoke(cmd2, ["--foo", "Apple"])
  299. assert result.exit_code == 0
  300. def test_case_insensitive_choice_returned_exactly(runner):
  301. @click.command()
  302. @click.option("--foo", type=click.Choice(["Orange", "Apple"], case_sensitive=False))
  303. def cmd(foo):
  304. click.echo(foo)
  305. result = runner.invoke(cmd, ["--foo", "apple"])
  306. assert result.exit_code == 0
  307. assert result.output == "Apple\n"
  308. def test_option_help_preserve_paragraphs(runner):
  309. @click.command()
  310. @click.option(
  311. "-C",
  312. "--config",
  313. type=click.Path(),
  314. help="""Configuration file to use.
  315. If not given, the environment variable CONFIG_FILE is consulted
  316. and used if set. If neither are given, a default configuration
  317. file is loaded.""",
  318. )
  319. def cmd(config):
  320. pass
  321. result = runner.invoke(cmd, ["--help"],)
  322. assert result.exit_code == 0
  323. assert (
  324. " -C, --config PATH Configuration file to use.\n"
  325. "{i}\n"
  326. "{i}If not given, the environment variable CONFIG_FILE is\n"
  327. "{i}consulted and used if set. If neither are given, a default\n"
  328. "{i}configuration file is loaded.".format(i=" " * 21)
  329. ) in result.output
  330. def test_argument_custom_class(runner):
  331. class CustomArgument(click.Argument):
  332. def get_default(self, ctx):
  333. """a dumb override of a default value for testing"""
  334. return "I am a default"
  335. @click.command()
  336. @click.argument("testarg", cls=CustomArgument, default="you wont see me")
  337. def cmd(testarg):
  338. click.echo(testarg)
  339. result = runner.invoke(cmd)
  340. assert "I am a default" in result.output
  341. assert "you wont see me" not in result.output
  342. def test_option_custom_class(runner):
  343. class CustomOption(click.Option):
  344. def get_help_record(self, ctx):
  345. """a dumb override of a help text for testing"""
  346. return ("--help", "I am a help text")
  347. @click.command()
  348. @click.option("--testoption", cls=CustomOption, help="you wont see me")
  349. def cmd(testoption):
  350. click.echo(testoption)
  351. result = runner.invoke(cmd, ["--help"])
  352. assert "I am a help text" in result.output
  353. assert "you wont see me" not in result.output
  354. def test_option_custom_class_reusable(runner):
  355. """Ensure we can reuse a custom class option. See Issue #926"""
  356. class CustomOption(click.Option):
  357. def get_help_record(self, ctx):
  358. """a dumb override of a help text for testing"""
  359. return ("--help", "I am a help text")
  360. # Assign to a variable to re-use the decorator.
  361. testoption = click.option("--testoption", cls=CustomOption, help="you wont see me")
  362. @click.command()
  363. @testoption
  364. def cmd1(testoption):
  365. click.echo(testoption)
  366. @click.command()
  367. @testoption
  368. def cmd2(testoption):
  369. click.echo(testoption)
  370. # Both of the commands should have the --help option now.
  371. for cmd in (cmd1, cmd2):
  372. result = runner.invoke(cmd, ["--help"])
  373. assert "I am a help text" in result.output
  374. assert "you wont see me" not in result.output
  375. def test_bool_flag_with_type(runner):
  376. @click.command()
  377. @click.option("--shout/--no-shout", default=False, type=bool)
  378. def cmd(shout):
  379. pass
  380. result = runner.invoke(cmd)
  381. assert not result.exception
  382. def test_aliases_for_flags(runner):
  383. @click.command()
  384. @click.option("--warnings/--no-warnings", " /-W", default=True)
  385. def cli(warnings):
  386. click.echo(warnings)
  387. result = runner.invoke(cli, ["--warnings"])
  388. assert result.output == "True\n"
  389. result = runner.invoke(cli, ["--no-warnings"])
  390. assert result.output == "False\n"
  391. result = runner.invoke(cli, ["-W"])
  392. assert result.output == "False\n"
  393. @click.command()
  394. @click.option("--warnings/--no-warnings", "-w", default=True)
  395. def cli_alt(warnings):
  396. click.echo(warnings)
  397. result = runner.invoke(cli_alt, ["--warnings"])
  398. assert result.output == "True\n"
  399. result = runner.invoke(cli_alt, ["--no-warnings"])
  400. assert result.output == "False\n"
  401. result = runner.invoke(cli_alt, ["-w"])
  402. assert result.output == "True\n"
  403. @pytest.mark.parametrize(
  404. "option_args,expected",
  405. [
  406. (["--aggressive", "--all", "-a"], "aggressive"),
  407. (["--first", "--second", "--third", "-a", "-b", "-c"], "first"),
  408. (["--apple", "--banana", "--cantaloupe", "-a", "-b", "-c"], "apple"),
  409. (["--cantaloupe", "--banana", "--apple", "-c", "-b", "-a"], "cantaloupe"),
  410. (["-a", "-b", "-c"], "a"),
  411. (["-c", "-b", "-a"], "c"),
  412. (["-a", "--apple", "-b", "--banana", "-c", "--cantaloupe"], "apple"),
  413. (["-c", "-a", "--cantaloupe", "-b", "--banana", "--apple"], "cantaloupe"),
  414. (["--from", "-f", "_from"], "_from"),
  415. (["--return", "-r", "_ret"], "_ret"),
  416. ],
  417. )
  418. def test_option_names(runner, option_args, expected):
  419. @click.command()
  420. @click.option(*option_args, is_flag=True)
  421. def cmd(**kwargs):
  422. click.echo(str(kwargs[expected]))
  423. assert cmd.params[0].name == expected
  424. for form in option_args:
  425. if form.startswith("-"):
  426. result = runner.invoke(cmd, [form])
  427. assert result.output == "True\n"