test_termui.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450
  1. import platform
  2. import time
  3. import pytest
  4. import click._termui_impl
  5. from click._compat import WIN
  6. class FakeClock:
  7. def __init__(self):
  8. self.now = time.time()
  9. def advance_time(self, seconds=1):
  10. self.now += seconds
  11. def time(self):
  12. return self.now
  13. def _create_progress(length=10, **kwargs):
  14. progress = click.progressbar(tuple(range(length)))
  15. for key, value in kwargs.items():
  16. setattr(progress, key, value)
  17. return progress
  18. def test_progressbar_strip_regression(runner, monkeypatch):
  19. label = " padded line"
  20. @click.command()
  21. def cli():
  22. with _create_progress(label=label) as progress:
  23. for _ in progress:
  24. pass
  25. monkeypatch.setattr(click._termui_impl, "isatty", lambda _: True)
  26. assert (
  27. label
  28. in runner.invoke(cli, [], standalone_mode=False, catch_exceptions=False).output
  29. )
  30. def test_progressbar_length_hint(runner, monkeypatch):
  31. class Hinted:
  32. def __init__(self, n):
  33. self.items = list(range(n))
  34. def __length_hint__(self):
  35. return len(self.items)
  36. def __iter__(self):
  37. return self
  38. def __next__(self):
  39. if self.items:
  40. return self.items.pop()
  41. else:
  42. raise StopIteration
  43. next = __next__
  44. @click.command()
  45. def cli():
  46. with click.progressbar(Hinted(10), label="test") as progress:
  47. for _ in progress:
  48. pass
  49. monkeypatch.setattr(click._termui_impl, "isatty", lambda _: True)
  50. result = runner.invoke(cli, [])
  51. assert result.exception is None
  52. def test_progressbar_hidden(runner, monkeypatch):
  53. @click.command()
  54. def cli():
  55. with _create_progress(label="working") as progress:
  56. for _ in progress:
  57. pass
  58. monkeypatch.setattr(click._termui_impl, "isatty", lambda _: False)
  59. assert runner.invoke(cli, []).output == "working\n"
  60. @pytest.mark.parametrize("avg, expected", [([], 0.0), ([1, 4], 2.5)])
  61. def test_progressbar_time_per_iteration(runner, avg, expected):
  62. with _create_progress(2, avg=avg) as progress:
  63. assert progress.time_per_iteration == expected
  64. @pytest.mark.parametrize("finished, expected", [(False, 5), (True, 0)])
  65. def test_progressbar_eta(runner, finished, expected):
  66. with _create_progress(2, finished=finished, avg=[1, 4]) as progress:
  67. assert progress.eta == expected
  68. @pytest.mark.parametrize(
  69. "eta, expected",
  70. [
  71. (0, "00:00:00"),
  72. (30, "00:00:30"),
  73. (90, "00:01:30"),
  74. (900, "00:15:00"),
  75. (9000, "02:30:00"),
  76. (99999999999, "1157407d 09:46:39"),
  77. (None, ""),
  78. ],
  79. )
  80. def test_progressbar_format_eta(runner, eta, expected):
  81. with _create_progress(1, eta_known=eta is not None, avg=[eta]) as progress:
  82. assert progress.format_eta() == expected
  83. @pytest.mark.parametrize("pos, length", [(0, 5), (-1, 1), (5, 5), (6, 5), (4, 0)])
  84. def test_progressbar_format_pos(runner, pos, length):
  85. with _create_progress(length, pos=pos) as progress:
  86. result = progress.format_pos()
  87. assert result == f"{pos}/{length}"
  88. @pytest.mark.parametrize(
  89. "length, finished, pos, avg, expected",
  90. [
  91. (8, False, 7, 0, "#######-"),
  92. (0, True, 8, 0, "########"),
  93. ],
  94. )
  95. def test_progressbar_format_bar(runner, length, finished, pos, avg, expected):
  96. with _create_progress(
  97. length, width=8, pos=pos, finished=finished, avg=[avg]
  98. ) as progress:
  99. assert progress.format_bar() == expected
  100. @pytest.mark.parametrize(
  101. "length, show_percent, show_pos, pos, expected",
  102. [
  103. (0, True, True, 0, " [--------] 0/0 0%"),
  104. (0, False, True, 0, " [--------] 0/0"),
  105. (0, False, False, 0, " [--------]"),
  106. (0, False, False, 0, " [--------]"),
  107. (8, True, True, 8, " [########] 8/8 100%"),
  108. ],
  109. )
  110. def test_progressbar_format_progress_line(
  111. runner, length, show_percent, show_pos, pos, expected
  112. ):
  113. with _create_progress(
  114. length,
  115. width=8,
  116. show_percent=show_percent,
  117. pos=pos,
  118. show_pos=show_pos,
  119. ) as progress:
  120. assert progress.format_progress_line() == expected
  121. @pytest.mark.parametrize("test_item", ["test", None])
  122. def test_progressbar_format_progress_line_with_show_func(runner, test_item):
  123. def item_show_func(item):
  124. return item
  125. with _create_progress(
  126. item_show_func=item_show_func, current_item=test_item
  127. ) as progress:
  128. if test_item:
  129. assert progress.format_progress_line().endswith(test_item)
  130. else:
  131. assert progress.format_progress_line().endswith(progress.format_pct())
  132. def test_progressbar_init_exceptions(runner):
  133. with pytest.raises(TypeError, match="iterable or length is required"):
  134. click.progressbar()
  135. def test_progressbar_iter_outside_with_exceptions(runner):
  136. progress = click.progressbar(length=2)
  137. with pytest.raises(RuntimeError, match="with block"):
  138. iter(progress)
  139. def test_progressbar_is_iterator(runner, monkeypatch):
  140. @click.command()
  141. def cli():
  142. with click.progressbar(range(10), label="test") as progress:
  143. while True:
  144. try:
  145. next(progress)
  146. except StopIteration:
  147. break
  148. monkeypatch.setattr(click._termui_impl, "isatty", lambda _: True)
  149. result = runner.invoke(cli, [])
  150. assert result.exception is None
  151. def test_choices_list_in_prompt(runner, monkeypatch):
  152. @click.command()
  153. @click.option(
  154. "-g", type=click.Choice(["none", "day", "week", "month"]), prompt=True
  155. )
  156. def cli_with_choices(g):
  157. pass
  158. @click.command()
  159. @click.option(
  160. "-g",
  161. type=click.Choice(["none", "day", "week", "month"]),
  162. prompt=True,
  163. show_choices=False,
  164. )
  165. def cli_without_choices(g):
  166. pass
  167. result = runner.invoke(cli_with_choices, [], input="none")
  168. assert "(none, day, week, month)" in result.output
  169. result = runner.invoke(cli_without_choices, [], input="none")
  170. assert "(none, day, week, month)" not in result.output
  171. @pytest.mark.skip
  172. @pytest.mark.parametrize(
  173. "file_kwargs", [{"mode": "rt"}, {"mode": "rb"}, {"lazy": True}]
  174. )
  175. def test_file_prompt_default_format(runner, file_kwargs):
  176. @click.command()
  177. @click.option("-f", default=__file__, prompt="file", type=click.File(**file_kwargs))
  178. def cli(f):
  179. click.echo(f.name)
  180. result = runner.invoke(cli)
  181. assert result.output == f"file [{__file__}]: \n{__file__}\n"
  182. def test_secho(runner):
  183. with runner.isolation() as outstreams:
  184. click.secho(None, nl=False)
  185. bytes = outstreams[0].getvalue()
  186. assert bytes == b""
  187. @pytest.mark.skipif(platform.system() == "Windows", reason="No style on Windows.")
  188. @pytest.mark.parametrize(
  189. ("value", "expect"), [(123, b"\x1b[45m123\x1b[0m"), (b"test", b"test")]
  190. )
  191. def test_secho_non_text(runner, value, expect):
  192. with runner.isolation() as (out, _):
  193. click.secho(value, nl=False, color=True, bg="magenta")
  194. result = out.getvalue()
  195. assert result == expect
  196. def test_progressbar_yields_all_items(runner):
  197. with click.progressbar(range(3)) as progress:
  198. assert len(list(progress)) == 3
  199. def test_progressbar_update(runner, monkeypatch):
  200. fake_clock = FakeClock()
  201. @click.command()
  202. def cli():
  203. with click.progressbar(range(4)) as progress:
  204. for _ in progress:
  205. fake_clock.advance_time()
  206. print("")
  207. monkeypatch.setattr(time, "time", fake_clock.time)
  208. monkeypatch.setattr(click._termui_impl, "isatty", lambda _: True)
  209. output = runner.invoke(cli, []).output
  210. lines = [line for line in output.split("\n") if "[" in line]
  211. assert " 0%" in lines[0]
  212. assert " 25% 00:00:03" in lines[1]
  213. assert " 50% 00:00:02" in lines[2]
  214. assert " 75% 00:00:01" in lines[3]
  215. assert "100% " in lines[4]
  216. def test_progressbar_item_show_func(runner, monkeypatch):
  217. """item_show_func should show the current item being yielded."""
  218. @click.command()
  219. def cli():
  220. with click.progressbar(range(3), item_show_func=lambda x: str(x)) as progress:
  221. for item in progress:
  222. click.echo(f" item {item}")
  223. monkeypatch.setattr(click._termui_impl, "isatty", lambda _: True)
  224. lines = runner.invoke(cli).output.splitlines()
  225. for i, line in enumerate(x for x in lines if "item" in x):
  226. assert f"{i} item {i}" in line
  227. def test_progressbar_update_with_item_show_func(runner, monkeypatch):
  228. @click.command()
  229. def cli():
  230. with click.progressbar(
  231. length=6, item_show_func=lambda x: f"Custom {x}"
  232. ) as progress:
  233. while not progress.finished:
  234. progress.update(2, progress.pos)
  235. click.echo()
  236. monkeypatch.setattr(click._termui_impl, "isatty", lambda _: True)
  237. output = runner.invoke(cli, []).output
  238. lines = [line for line in output.split("\n") if "[" in line]
  239. assert "Custom 0" in lines[0]
  240. assert "Custom 2" in lines[1]
  241. assert "Custom 4" in lines[2]
  242. def test_progress_bar_update_min_steps(runner):
  243. bar = _create_progress(update_min_steps=5)
  244. bar.update(3)
  245. assert bar._completed_intervals == 3
  246. assert bar.pos == 0
  247. bar.update(2)
  248. assert bar._completed_intervals == 0
  249. assert bar.pos == 5
  250. @pytest.mark.parametrize("key_char", ("h", "H", "é", "À", " ", "字", "àH", "àR"))
  251. @pytest.mark.parametrize("echo", [True, False])
  252. @pytest.mark.skipif(not WIN, reason="Tests user-input using the msvcrt module.")
  253. def test_getchar_windows(runner, monkeypatch, key_char, echo):
  254. monkeypatch.setattr(click._termui_impl.msvcrt, "getwche", lambda: key_char)
  255. monkeypatch.setattr(click._termui_impl.msvcrt, "getwch", lambda: key_char)
  256. monkeypatch.setattr(click.termui, "_getchar", None)
  257. assert click.getchar(echo) == key_char
  258. @pytest.mark.parametrize(
  259. "special_key_char, key_char", [("\x00", "a"), ("\x00", "b"), ("\xe0", "c")]
  260. )
  261. @pytest.mark.skipif(
  262. not WIN, reason="Tests special character inputs using the msvcrt module."
  263. )
  264. def test_getchar_special_key_windows(runner, monkeypatch, special_key_char, key_char):
  265. ordered_inputs = [key_char, special_key_char]
  266. monkeypatch.setattr(
  267. click._termui_impl.msvcrt, "getwch", lambda: ordered_inputs.pop()
  268. )
  269. monkeypatch.setattr(click.termui, "_getchar", None)
  270. assert click.getchar() == f"{special_key_char}{key_char}"
  271. @pytest.mark.parametrize(
  272. ("key_char", "exc"), [("\x03", KeyboardInterrupt), ("\x1a", EOFError)]
  273. )
  274. @pytest.mark.skipif(not WIN, reason="Tests user-input using the msvcrt module.")
  275. def test_getchar_windows_exceptions(runner, monkeypatch, key_char, exc):
  276. monkeypatch.setattr(click._termui_impl.msvcrt, "getwch", lambda: key_char)
  277. monkeypatch.setattr(click.termui, "_getchar", None)
  278. with pytest.raises(exc):
  279. click.getchar()
  280. @pytest.mark.skipif(platform.system() == "Windows", reason="No sed on Windows.")
  281. def test_fast_edit(runner):
  282. result = click.edit("a\nb", editor="sed -i~ 's/$/Test/'")
  283. assert result == "aTest\nbTest\n"
  284. @pytest.mark.parametrize(
  285. ("prompt_required", "required", "args", "expect"),
  286. [
  287. (True, False, None, "prompt"),
  288. (True, False, ["-v"], "Option '-v' requires an argument."),
  289. (False, True, None, "prompt"),
  290. (False, True, ["-v"], "prompt"),
  291. ],
  292. )
  293. def test_prompt_required_with_required(runner, prompt_required, required, args, expect):
  294. @click.command()
  295. @click.option("-v", prompt=True, prompt_required=prompt_required, required=required)
  296. def cli(v):
  297. click.echo(str(v))
  298. result = runner.invoke(cli, args, input="prompt")
  299. assert expect in result.output
  300. @pytest.mark.parametrize(
  301. ("args", "expect"),
  302. [
  303. # Flag not passed, don't prompt.
  304. pytest.param(None, None, id="no flag"),
  305. # Flag and value passed, don't prompt.
  306. pytest.param(["-v", "value"], "value", id="short sep value"),
  307. pytest.param(["--value", "value"], "value", id="long sep value"),
  308. pytest.param(["-vvalue"], "value", id="short join value"),
  309. pytest.param(["--value=value"], "value", id="long join value"),
  310. # Flag without value passed, prompt.
  311. pytest.param(["-v"], "prompt", id="short no value"),
  312. pytest.param(["--value"], "prompt", id="long no value"),
  313. # Don't use next option flag as value.
  314. pytest.param(["-v", "-o", "42"], ("prompt", "42"), id="no value opt"),
  315. ],
  316. )
  317. def test_prompt_required_false(runner, args, expect):
  318. @click.command()
  319. @click.option("-v", "--value", prompt=True, prompt_required=False)
  320. @click.option("-o")
  321. def cli(value, o):
  322. if o is not None:
  323. return value, o
  324. return value
  325. result = runner.invoke(cli, args=args, input="prompt", standalone_mode=False)
  326. assert result.exception is None
  327. assert result.return_value == expect
  328. @pytest.mark.parametrize(
  329. ("prompt", "input", "default", "expect"),
  330. [
  331. (True, "password\npassword", None, "password"),
  332. ("Confirm Password", "password\npassword\n", None, "password"),
  333. (True, "", "", ""),
  334. (False, None, None, None),
  335. ],
  336. )
  337. def test_confirmation_prompt(runner, prompt, input, default, expect):
  338. @click.command()
  339. @click.option(
  340. "--password",
  341. prompt=prompt,
  342. hide_input=True,
  343. default=default,
  344. confirmation_prompt=prompt,
  345. )
  346. def cli(password):
  347. return password
  348. result = runner.invoke(cli, input=input, standalone_mode=False)
  349. assert result.exception is None
  350. assert result.return_value == expect
  351. if prompt == "Confirm Password":
  352. assert "Confirm Password: " in result.output