Skip to content

Commit 7925a34

Browse files
authored
Merge stable into main. (#3674)
2 parents a44e9f0 + ea811f9 commit 7925a34

15 files changed

Lines changed: 898 additions & 857 deletions

tests/test_utils.py

Lines changed: 0 additions & 857 deletions
This file was deleted.

tests/test_utils/__init__.py

Whitespace-only changes.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import click
2+
3+
4+
def test_iter_keepopenfile(tmpdir):
5+
expected = list(map(str, range(10)))
6+
p = tmpdir.mkdir("testdir").join("testfile")
7+
p.write("\n".join(expected))
8+
with p.open() as f:
9+
for e_line, a_line in zip(expected, click.utils.KeepOpenFile(f), strict=False):
10+
assert e_line == a_line.strip()

tests/test_utils/test_LazyFile.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import click
2+
3+
4+
def test_iter_lazyfile(tmpdir):
5+
expected = list(map(str, range(10)))
6+
p = tmpdir.mkdir("testdir").join("testfile")
7+
p.write("\n".join(expected))
8+
with p.open() as f:
9+
with click.utils.LazyFile(f.name) as lf:
10+
for e_line, a_line in zip(expected, lf, strict=False):
11+
assert e_line == a_line.strip()
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import pathlib
2+
3+
import pytest
4+
5+
import click
6+
7+
8+
class MockMain:
9+
__slots__ = "__package__"
10+
11+
def __init__(self, package_name):
12+
self.__package__ = package_name
13+
14+
15+
@pytest.mark.parametrize(
16+
("path", "main", "expected"),
17+
[
18+
("example.py", None, "example.py"),
19+
(str(pathlib.Path("/foo/bar/example.py")), None, "example.py"),
20+
("example", None, "example"),
21+
(str(pathlib.Path("example/__main__.py")), "example", "python -m example"),
22+
(str(pathlib.Path("example/cli.py")), "example", "python -m example.cli"),
23+
(str(pathlib.Path("./example")), "", "example"),
24+
],
25+
)
26+
def test_detect_program_name(path, main, expected):
27+
assert click.utils._detect_program_name(path, _main=MockMain(main)) == expected
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import os
2+
3+
import click
4+
5+
6+
def test_expand_args(monkeypatch):
7+
user = os.path.expanduser("~")
8+
assert user in click.utils._expand_args(["~"])
9+
monkeypatch.setenv("CLICK_TEST", "hello")
10+
assert "hello" in click.utils._expand_args(["$CLICK_TEST"])
11+
assert "pyproject.toml" in click.utils._expand_args(["*.toml"])
12+
assert os.path.join("tests", "conftest.py") in click.utils._expand_args(
13+
["**/conftest.py"]
14+
)
15+
assert "*.not-found" in click.utils._expand_args(["*.not-found"])
16+
# a bad glob pattern, such as a pytest identifier, should return itself
17+
assert click.utils._expand_args(["test.py::test_bad"])[0] == "test.py::test_bad"

tests/test_utils/test_confirm.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import click
2+
3+
4+
def test_prompts(runner):
5+
@click.command()
6+
def test():
7+
if click.confirm("Foo"):
8+
click.echo("yes!")
9+
else:
10+
click.echo("no :(")
11+
12+
result = runner.invoke(test, input="y\n")
13+
assert not result.exception
14+
assert result.output == "Foo [y/N]: y\nyes!\n"
15+
16+
result = runner.invoke(test, input="\n")
17+
assert not result.exception
18+
assert result.output == "Foo [y/N]: \nno :(\n"
19+
20+
result = runner.invoke(test, input="n\n")
21+
assert not result.exception
22+
assert result.output == "Foo [y/N]: n\nno :(\n"
23+
24+
@click.command()
25+
def test_no():
26+
if click.confirm("Foo", default=True):
27+
click.echo("yes!")
28+
else:
29+
click.echo("no :(")
30+
31+
result = runner.invoke(test_no, input="y\n")
32+
assert not result.exception
33+
assert result.output == "Foo [Y/n]: y\nyes!\n"
34+
35+
result = runner.invoke(test_no, input="\n")
36+
assert not result.exception
37+
assert result.output == "Foo [Y/n]: \nyes!\n"
38+
39+
result = runner.invoke(test_no, input="n\n")
40+
assert not result.exception
41+
assert result.output == "Foo [Y/n]: n\nno :(\n"
42+
43+
44+
def test_confirm_repeat(runner):
45+
cli = click.Command(
46+
"cli", params=[click.Option(["--a/--no-a"], default=None, prompt=True)]
47+
)
48+
result = runner.invoke(cli, input="\ny\n")
49+
assert result.output == "A [y/n]: \nError: invalid input\nA [y/n]: y\n"

tests/test_utils/test_echo.py

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
import sys
2+
from io import BytesIO
3+
from io import StringIO
4+
5+
import pytest
6+
7+
import click._termui_impl
8+
import click.utils
9+
from click._compat import WIN
10+
11+
12+
def test_echo(runner):
13+
with runner.isolation() as outstreams:
14+
click.echo("\N{SNOWMAN}")
15+
click.echo(b"\x44\x44")
16+
click.echo(42, nl=False)
17+
click.echo(b"a", nl=False)
18+
click.echo("\x1b[31mx\x1b[39m", nl=False)
19+
bytes = outstreams[0].getvalue().replace(b"\r\n", b"\n")
20+
assert bytes == b"\xe2\x98\x83\nDD\n42ax"
21+
22+
# if wrapped, we expect bytes to survive.
23+
@click.command()
24+
def cli():
25+
click.echo(b"\xf6")
26+
27+
result = runner.invoke(cli, [])
28+
assert result.stdout_bytes == b"\xf6\n"
29+
30+
# Ensure we do not strip for bytes.
31+
with runner.isolation() as outstreams:
32+
click.echo(bytearray(b"\x1b[31mx\x1b[39m"), nl=False)
33+
assert outstreams[0].getvalue() == b"\x1b[31mx\x1b[39m"
34+
35+
36+
def test_echo_custom_file():
37+
f = StringIO()
38+
click.echo("hello", file=f)
39+
assert f.getvalue() == "hello\n"
40+
41+
b = BytesIO()
42+
click.echo(b"", b)
43+
assert b.getvalue() == b"\n"
44+
45+
46+
def test_echo_no_streams(monkeypatch, runner):
47+
"""echo should not fail when stdout and stderr are None with pythonw on Windows."""
48+
with runner.isolation():
49+
sys.stdout = None
50+
sys.stderr = None
51+
click.echo("test")
52+
click.echo("test", err=True)
53+
54+
55+
def test_echo_with_capsys(capsys):
56+
click.echo("Capture me.")
57+
out, err = capsys.readouterr()
58+
assert out == "Capture me.\n"
59+
60+
61+
def test_echo_color_flag(monkeypatch, capfd):
62+
isatty = True
63+
monkeypatch.setattr(click._compat, "isatty", lambda x: isatty)
64+
65+
text = "foo"
66+
styled_text = click.style(text, fg="red")
67+
assert styled_text == "\x1b[31mfoo\x1b[0m"
68+
69+
click.echo(styled_text, color=False)
70+
out, err = capfd.readouterr()
71+
assert out == f"{text}\n"
72+
73+
click.echo(styled_text, color=True)
74+
out, err = capfd.readouterr()
75+
assert out == f"{styled_text}\n"
76+
77+
isatty = True
78+
click.echo(styled_text)
79+
out, err = capfd.readouterr()
80+
assert out == f"{styled_text}\n"
81+
82+
isatty = False
83+
# Faking isatty() is not enough on Windows;
84+
# the implementation caches the colorama wrapped stream
85+
# so we have to use a new stream for each test
86+
stream = StringIO()
87+
click.echo(styled_text, file=stream)
88+
assert stream.getvalue() == f"{text}\n"
89+
90+
stream = StringIO()
91+
click.echo(styled_text, file=stream, color=True)
92+
assert stream.getvalue() == f"{styled_text}\n"
93+
94+
95+
@pytest.mark.skipif(WIN, reason="Test too complex to make work windows.")
96+
def test_echo_writing_to_standard_error(capfd, monkeypatch):
97+
def emulate_input(text):
98+
"""Emulate keyboard input."""
99+
monkeypatch.setattr(sys, "stdin", StringIO(text))
100+
101+
click.echo("Echo to standard output")
102+
out, err = capfd.readouterr()
103+
assert out == "Echo to standard output\n"
104+
assert err == ""
105+
106+
click.echo("Echo to standard error", err=True)
107+
out, err = capfd.readouterr()
108+
assert out == ""
109+
assert err == "Echo to standard error\n"
110+
111+
emulate_input("asdlkj\n")
112+
click.prompt("Prompt to stdin")
113+
out, err = capfd.readouterr()
114+
assert out == "Prompt to stdin: "
115+
assert err == ""
116+
117+
emulate_input("asdlkj\n")
118+
click.prompt("Prompt to stdin with no suffix", prompt_suffix="")
119+
out, err = capfd.readouterr()
120+
assert out == "Prompt to stdin with no suffix"
121+
assert err == ""
122+
123+
# On non-Windows the full prompt goes through redirect_stdout so
124+
# nothing leaks to stdout when err=True.
125+
# https://github.com/pallets/click/issues/2968
126+
emulate_input("asdlkj\n")
127+
click.prompt("Prompt to stderr", err=True)
128+
out, err = capfd.readouterr()
129+
assert out == ""
130+
assert err == "Prompt to stderr: "
131+
132+
# https://github.com/pallets/click/issues/3019
133+
emulate_input("asdlkj\n")
134+
click.prompt("Prompt to stderr with no suffix", prompt_suffix="", err=True)
135+
out, err = capfd.readouterr()
136+
assert out == ""
137+
assert err == "Prompt to stderr with no suffix"
138+
139+
emulate_input("y\n")
140+
click.confirm("Prompt to stdin")
141+
out, err = capfd.readouterr()
142+
assert out == "Prompt to stdin [y/N]: "
143+
assert err == ""
144+
145+
emulate_input("y\n")
146+
click.confirm("Prompt to stdin with no suffix", prompt_suffix="")
147+
out, err = capfd.readouterr()
148+
assert out == "Prompt to stdin with no suffix [y/N]"
149+
assert err == ""
150+
151+
# https://github.com/pallets/click/issues/2968
152+
emulate_input("y\n")
153+
click.confirm("Prompt to stderr", err=True)
154+
out, err = capfd.readouterr()
155+
assert out == ""
156+
assert err == "Prompt to stderr [y/N]: "
157+
158+
# https://github.com/pallets/click/issues/3019
159+
emulate_input("y\n")
160+
click.confirm("Prompt to stderr with no suffix", prompt_suffix="", err=True)
161+
out, err = capfd.readouterr()
162+
assert out == ""
163+
assert err == "Prompt to stderr with no suffix [y/N]"
164+
165+
monkeypatch.setattr(click.termui, "isatty", lambda x: True)
166+
monkeypatch.setattr(click.termui, "getchar", lambda: " ")
167+
168+
click.pause("Pause to stdout")
169+
out, err = capfd.readouterr()
170+
assert out == "Pause to stdout\n"
171+
assert err == ""
172+
173+
click.pause("Pause to stderr", err=True)
174+
out, err = capfd.readouterr()
175+
assert out == ""
176+
assert err == "Pause to stderr\n"

0 commit comments

Comments
 (0)