test_utils.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  1. import os
  2. import stat
  3. import sys
  4. import pytest
  5. import click._termui_impl
  6. import click.utils
  7. from click._compat import WIN
  8. def test_echo(runner):
  9. with runner.isolation() as outstreams:
  10. click.echo(u"\N{SNOWMAN}")
  11. click.echo(b"\x44\x44")
  12. click.echo(42, nl=False)
  13. click.echo(b"a", nl=False)
  14. click.echo("\x1b[31mx\x1b[39m", nl=False)
  15. bytes = outstreams[0].getvalue().replace(b"\r\n", b"\n")
  16. assert bytes == b"\xe2\x98\x83\nDD\n42ax"
  17. # If we are in Python 2, we expect that writing bytes into a string io
  18. # does not do anything crazy. In Python 3
  19. if sys.version_info[0] == 2:
  20. import StringIO
  21. sys.stdout = x = StringIO.StringIO()
  22. try:
  23. click.echo("\xf6")
  24. finally:
  25. sys.stdout = sys.__stdout__
  26. assert x.getvalue() == "\xf6\n"
  27. # And in any case, if wrapped, we expect bytes to survive.
  28. @click.command()
  29. def cli():
  30. click.echo(b"\xf6")
  31. result = runner.invoke(cli, [])
  32. assert result.stdout_bytes == b"\xf6\n"
  33. # Ensure we do not strip for bytes.
  34. with runner.isolation() as outstreams:
  35. click.echo(bytearray(b"\x1b[31mx\x1b[39m"), nl=False)
  36. assert outstreams[0].getvalue() == b"\x1b[31mx\x1b[39m"
  37. def test_echo_custom_file():
  38. import io
  39. f = io.StringIO()
  40. click.echo(u"hello", file=f)
  41. assert f.getvalue() == u"hello\n"
  42. @pytest.mark.parametrize(
  43. ("styles", "ref"),
  44. [
  45. ({"fg": "black"}, "\x1b[30mx y\x1b[0m"),
  46. ({"fg": "red"}, "\x1b[31mx y\x1b[0m"),
  47. ({"fg": "green"}, "\x1b[32mx y\x1b[0m"),
  48. ({"fg": "yellow"}, "\x1b[33mx y\x1b[0m"),
  49. ({"fg": "blue"}, "\x1b[34mx y\x1b[0m"),
  50. ({"fg": "magenta"}, "\x1b[35mx y\x1b[0m"),
  51. ({"fg": "cyan"}, "\x1b[36mx y\x1b[0m"),
  52. ({"fg": "white"}, "\x1b[37mx y\x1b[0m"),
  53. ({"bg": "black"}, "\x1b[40mx y\x1b[0m"),
  54. ({"bg": "red"}, "\x1b[41mx y\x1b[0m"),
  55. ({"bg": "green"}, "\x1b[42mx y\x1b[0m"),
  56. ({"bg": "yellow"}, "\x1b[43mx y\x1b[0m"),
  57. ({"bg": "blue"}, "\x1b[44mx y\x1b[0m"),
  58. ({"bg": "magenta"}, "\x1b[45mx y\x1b[0m"),
  59. ({"bg": "cyan"}, "\x1b[46mx y\x1b[0m"),
  60. ({"bg": "white"}, "\x1b[47mx y\x1b[0m"),
  61. ({"blink": True}, "\x1b[5mx y\x1b[0m"),
  62. ({"underline": True}, "\x1b[4mx y\x1b[0m"),
  63. ({"bold": True}, "\x1b[1mx y\x1b[0m"),
  64. ({"dim": True}, "\x1b[2mx y\x1b[0m"),
  65. ],
  66. )
  67. def test_styling(styles, ref):
  68. assert click.style("x y", **styles) == ref
  69. assert click.unstyle(ref) == "x y"
  70. @pytest.mark.parametrize(("text", "expect"), [("\x1b[?25lx y\x1b[?25h", "x y")])
  71. def test_unstyle_other_ansi(text, expect):
  72. assert click.unstyle(text) == expect
  73. def test_filename_formatting():
  74. assert click.format_filename(b"foo.txt") == "foo.txt"
  75. assert click.format_filename(b"/x/foo.txt") == "/x/foo.txt"
  76. assert click.format_filename(u"/x/foo.txt") == "/x/foo.txt"
  77. assert click.format_filename(u"/x/foo.txt", shorten=True) == "foo.txt"
  78. # filesystem encoding on windows permits this.
  79. if not WIN:
  80. assert (
  81. click.format_filename(b"/x/foo\xff.txt", shorten=True) == u"foo\ufffd.txt"
  82. )
  83. def test_prompts(runner):
  84. @click.command()
  85. def test():
  86. if click.confirm("Foo"):
  87. click.echo("yes!")
  88. else:
  89. click.echo("no :(")
  90. result = runner.invoke(test, input="y\n")
  91. assert not result.exception
  92. assert result.output == "Foo [y/N]: y\nyes!\n"
  93. result = runner.invoke(test, input="\n")
  94. assert not result.exception
  95. assert result.output == "Foo [y/N]: \nno :(\n"
  96. result = runner.invoke(test, input="n\n")
  97. assert not result.exception
  98. assert result.output == "Foo [y/N]: n\nno :(\n"
  99. @click.command()
  100. def test_no():
  101. if click.confirm("Foo", default=True):
  102. click.echo("yes!")
  103. else:
  104. click.echo("no :(")
  105. result = runner.invoke(test_no, input="y\n")
  106. assert not result.exception
  107. assert result.output == "Foo [Y/n]: y\nyes!\n"
  108. result = runner.invoke(test_no, input="\n")
  109. assert not result.exception
  110. assert result.output == "Foo [Y/n]: \nyes!\n"
  111. result = runner.invoke(test_no, input="n\n")
  112. assert not result.exception
  113. assert result.output == "Foo [Y/n]: n\nno :(\n"
  114. @pytest.mark.skipif(WIN, reason="Different behavior on windows.")
  115. def test_prompts_abort(monkeypatch, capsys):
  116. def f(_):
  117. raise KeyboardInterrupt()
  118. monkeypatch.setattr("click.termui.hidden_prompt_func", f)
  119. try:
  120. click.prompt("Password", hide_input=True)
  121. except click.Abort:
  122. click.echo("Screw you.")
  123. out, err = capsys.readouterr()
  124. assert out == "Password: \nScrew you.\n"
  125. def _test_gen_func():
  126. yield "a"
  127. yield "b"
  128. yield "c"
  129. yield "abc"
  130. @pytest.mark.skipif(WIN, reason="Different behavior on windows.")
  131. @pytest.mark.parametrize("cat", ["cat", "cat ", "cat "])
  132. @pytest.mark.parametrize(
  133. "test",
  134. [
  135. # We need lambda here, because pytest will
  136. # reuse the parameters, and then the generators
  137. # are already used and will not yield anymore
  138. ("just text\n", lambda: "just text"),
  139. ("iterable\n", lambda: ["itera", "ble"]),
  140. ("abcabc\n", lambda: _test_gen_func),
  141. ("abcabc\n", lambda: _test_gen_func()),
  142. ("012345\n", lambda: (c for c in range(6))),
  143. ],
  144. )
  145. def test_echo_via_pager(monkeypatch, capfd, cat, test):
  146. monkeypatch.setitem(os.environ, "PAGER", cat)
  147. monkeypatch.setattr(click._termui_impl, "isatty", lambda x: True)
  148. expected_output = test[0]
  149. test_input = test[1]()
  150. click.echo_via_pager(test_input)
  151. out, err = capfd.readouterr()
  152. assert out == expected_output
  153. @pytest.mark.skipif(WIN, reason="Test does not make sense on Windows.")
  154. def test_echo_color_flag(monkeypatch, capfd):
  155. isatty = True
  156. monkeypatch.setattr(click._compat, "isatty", lambda x: isatty)
  157. text = "foo"
  158. styled_text = click.style(text, fg="red")
  159. assert styled_text == "\x1b[31mfoo\x1b[0m"
  160. click.echo(styled_text, color=False)
  161. out, err = capfd.readouterr()
  162. assert out == "{}\n".format(text)
  163. click.echo(styled_text, color=True)
  164. out, err = capfd.readouterr()
  165. assert out == "{}\n".format(styled_text)
  166. isatty = True
  167. click.echo(styled_text)
  168. out, err = capfd.readouterr()
  169. assert out == "{}\n".format(styled_text)
  170. isatty = False
  171. click.echo(styled_text)
  172. out, err = capfd.readouterr()
  173. assert out == "{}\n".format(text)
  174. @pytest.mark.skipif(WIN, reason="Test too complex to make work windows.")
  175. def test_echo_writing_to_standard_error(capfd, monkeypatch):
  176. def emulate_input(text):
  177. """Emulate keyboard input."""
  178. if sys.version_info[0] == 2:
  179. from StringIO import StringIO
  180. else:
  181. from io import StringIO
  182. monkeypatch.setattr(sys, "stdin", StringIO(text))
  183. click.echo("Echo to standard output")
  184. out, err = capfd.readouterr()
  185. assert out == "Echo to standard output\n"
  186. assert err == ""
  187. click.echo("Echo to standard error", err=True)
  188. out, err = capfd.readouterr()
  189. assert out == ""
  190. assert err == "Echo to standard error\n"
  191. emulate_input("asdlkj\n")
  192. click.prompt("Prompt to stdin")
  193. out, err = capfd.readouterr()
  194. assert out == "Prompt to stdin: "
  195. assert err == ""
  196. emulate_input("asdlkj\n")
  197. click.prompt("Prompt to stderr", err=True)
  198. out, err = capfd.readouterr()
  199. assert out == ""
  200. assert err == "Prompt to stderr: "
  201. emulate_input("y\n")
  202. click.confirm("Prompt to stdin")
  203. out, err = capfd.readouterr()
  204. assert out == "Prompt to stdin [y/N]: "
  205. assert err == ""
  206. emulate_input("y\n")
  207. click.confirm("Prompt to stderr", err=True)
  208. out, err = capfd.readouterr()
  209. assert out == ""
  210. assert err == "Prompt to stderr [y/N]: "
  211. monkeypatch.setattr(click.termui, "isatty", lambda x: True)
  212. monkeypatch.setattr(click.termui, "getchar", lambda: " ")
  213. click.pause("Pause to stdout")
  214. out, err = capfd.readouterr()
  215. assert out == "Pause to stdout\n"
  216. assert err == ""
  217. click.pause("Pause to stderr", err=True)
  218. out, err = capfd.readouterr()
  219. assert out == ""
  220. assert err == "Pause to stderr\n"
  221. def test_open_file(runner):
  222. @click.command()
  223. @click.argument("filename")
  224. def cli(filename):
  225. with click.open_file(filename) as f:
  226. click.echo(f.read())
  227. click.echo("meep")
  228. with runner.isolated_filesystem():
  229. with open("hello.txt", "w") as f:
  230. f.write("Cool stuff")
  231. result = runner.invoke(cli, ["hello.txt"])
  232. assert result.exception is None
  233. assert result.output == "Cool stuff\nmeep\n"
  234. result = runner.invoke(cli, ["-"], input="foobar")
  235. assert result.exception is None
  236. assert result.output == "foobar\nmeep\n"
  237. def test_open_file_ignore_errors_stdin(runner):
  238. @click.command()
  239. @click.argument("filename")
  240. def cli(filename):
  241. with click.open_file(filename, errors="ignore") as f:
  242. click.echo(f.read())
  243. result = runner.invoke(cli, ["-"], input=os.urandom(16))
  244. assert result.exception is None
  245. def test_open_file_respects_ignore(runner):
  246. with runner.isolated_filesystem():
  247. with open("test.txt", "w") as f:
  248. f.write("Hello world!")
  249. with click.open_file("test.txt", encoding="utf8", errors="ignore") as f:
  250. assert f.errors == "ignore"
  251. def test_open_file_ignore_invalid_utf8(runner):
  252. with runner.isolated_filesystem():
  253. with open("test.txt", "wb") as f:
  254. f.write(b"\xe2\x28\xa1")
  255. with click.open_file("test.txt", encoding="utf8", errors="ignore") as f:
  256. f.read()
  257. def test_open_file_ignore_no_encoding(runner):
  258. with runner.isolated_filesystem():
  259. with open("test.bin", "wb") as f:
  260. f.write(os.urandom(16))
  261. with click.open_file("test.bin", errors="ignore") as f:
  262. f.read()
  263. @pytest.mark.skipif(WIN, reason="os.chmod() is not fully supported on Windows.")
  264. @pytest.mark.parametrize("permissions", [0o400, 0o444, 0o600, 0o644])
  265. def test_open_file_atomic_permissions_existing_file(runner, permissions):
  266. with runner.isolated_filesystem():
  267. with open("existing.txt", "w") as f:
  268. f.write("content")
  269. os.chmod("existing.txt", permissions)
  270. @click.command()
  271. @click.argument("filename")
  272. def cli(filename):
  273. click.open_file(filename, "w", atomic=True).close()
  274. result = runner.invoke(cli, ["existing.txt"])
  275. assert result.exception is None
  276. assert stat.S_IMODE(os.stat("existing.txt").st_mode) == permissions
  277. @pytest.mark.skipif(WIN, reason="os.stat() is not fully supported on Windows.")
  278. def test_open_file_atomic_permissions_new_file(runner):
  279. with runner.isolated_filesystem():
  280. @click.command()
  281. @click.argument("filename")
  282. def cli(filename):
  283. click.open_file(filename, "w", atomic=True).close()
  284. # Create a test file to get the expected permissions for new files
  285. # according to the current umask.
  286. with open("test.txt", "w"):
  287. pass
  288. permissions = stat.S_IMODE(os.stat("test.txt").st_mode)
  289. result = runner.invoke(cli, ["new.txt"])
  290. assert result.exception is None
  291. assert stat.S_IMODE(os.stat("new.txt").st_mode) == permissions
  292. def test_iter_keepopenfile(tmpdir):
  293. expected = list(map(str, range(10)))
  294. p = tmpdir.mkdir("testdir").join("testfile")
  295. p.write("\n".join(expected))
  296. with p.open() as f:
  297. for e_line, a_line in zip(expected, click.utils.KeepOpenFile(f)):
  298. assert e_line == a_line.strip()
  299. def test_iter_lazyfile(tmpdir):
  300. expected = list(map(str, range(10)))
  301. p = tmpdir.mkdir("testdir").join("testfile")
  302. p.write("\n".join(expected))
  303. with p.open() as f:
  304. with click.utils.LazyFile(f.name) as lf:
  305. for e_line, a_line in zip(expected, lf):
  306. assert e_line == a_line.strip()