copier/tests/test_output.py
2025-04-22 13:52:51 +02:00

461 lines
14 KiB
Python

import re
from pathlib import Path
import pexpect
import pytest
from plumbum import local
from copier._main import run_copy, run_recopy, run_update
from copier.errors import InvalidTypeError
from .helpers import COPIER_PATH, Spawn, build_file_tree, expect_prompt, git, render
def test_output(capsys: pytest.CaptureFixture[str], tmp_path: Path) -> None:
render(tmp_path, quiet=False)
_, err = capsys.readouterr()
assert re.search(r"create[^\s]* config\.py", err)
assert re.search(r"create[^\s]* pyproject\.toml", err)
assert re.search(r"create[^\s]* doc[/\\]images[/\\]nslogo\.gif", err)
def test_output_pretend(capsys: pytest.CaptureFixture[str], tmp_path: Path) -> None:
render(tmp_path, quiet=False, pretend=True)
_, err = capsys.readouterr()
assert re.search(r"create[^\s]* config\.py", err)
assert re.search(r"create[^\s]* pyproject\.toml", err)
assert re.search(r"create[^\s]* doc[/\\]images[/\\]nslogo\.gif", err)
def test_output_force(capsys: pytest.CaptureFixture[str], tmp_path: Path) -> None:
render(tmp_path)
capsys.readouterr()
render(tmp_path, quiet=False, defaults=True, overwrite=True)
_, err = capsys.readouterr()
assert re.search(r"conflict[^\s]* config\.py", err)
assert re.search(r"overwrite[^\s]* config\.py", err)
assert re.search(r"identical[^\s]* pyproject\.toml", err)
assert re.search(r"identical[^\s]* doc[/\\]images[/\\]nslogo\.gif", err)
def test_output_skip(capsys: pytest.CaptureFixture[str], tmp_path: Path) -> None:
render(tmp_path)
capsys.readouterr()
render(tmp_path, quiet=False, skip_if_exists=["config.py"])
_, err = capsys.readouterr()
assert re.search(r"conflict[^\s]* config\.py", err)
assert re.search(r"skip[^\s]* config\.py", err)
assert re.search(r"identical[^\s]* pyproject\.toml", err)
assert re.search(r"identical[^\s]* doc[/\\]images[/\\]nslogo\.gif", err)
def test_output_quiet(capsys: pytest.CaptureFixture[str], tmp_path: Path) -> None:
render(tmp_path, quiet=True)
out, err = capsys.readouterr()
assert out == ""
assert err == ""
def test_question_with_invalid_type(tmp_path_factory: pytest.TempPathFactory) -> None:
src, dst = map(tmp_path_factory.mktemp, ["src", "dst"])
build_file_tree(
{
src / "copier.yaml": """
bad:
type: invalid
default: 1
"""
}
)
with pytest.raises(
InvalidTypeError, match='Unsupported type "invalid" in question "bad"'
):
run_copy(str(src), dst, defaults=True, overwrite=True)
def test_answer_with_invalid_type(tmp_path_factory: pytest.TempPathFactory) -> None:
src, dst = map(tmp_path_factory.mktemp, ["src", "dst"])
build_file_tree(
{
src / "copier.yaml": """
bad:
type: int
default: null
"""
}
)
with pytest.raises(
InvalidTypeError,
match='Invalid answer "None" of type "<class \'NoneType\'>" to question "bad" of type "int"',
):
run_copy(str(src), dst, defaults=True, overwrite=True)
@pytest.mark.parametrize("interactive", [False, True])
def test_messages_with_inline_text(
tmp_path_factory: pytest.TempPathFactory,
capsys: pytest.CaptureFixture[str],
spawn: Spawn,
interactive: bool,
) -> None:
src, dst = map(tmp_path_factory.mktemp, ["src", "dst"])
build_file_tree(
{
(src / "copier.yaml"): (
"""\
project_name:
type: str
_message_before_copy: Thank you for using our template on {{ _copier_conf.os }}
_message_after_copy: Project {{ project_name }} successfully created
_message_before_update: Updating on {{ _copier_conf.os }}
_message_after_update: Project {{ project_name }} successfully updated
"""
),
(src / "{{ _copier_conf.answers_file }}.jinja"): (
"""\
# Changes here will be overwritten by Copier
{{ _copier_answers|to_nice_yaml }}
"""
),
(src / "version.txt"): "v1",
}
)
with local.cwd(src):
git("init")
git("add", ".")
git("commit", "-m1")
git("tag", "v1")
build_file_tree(
{
(src / "version.txt"): "v2",
}
)
with local.cwd(src):
git("add", ".")
git("commit", "-m2")
git("tag", "v2")
# clear capture output log
capsys.readouterr()
# copy
if interactive:
tui = spawn(COPIER_PATH + ("copy", "-r", "v1", str(src), str(dst)), timeout=10)
expect_prompt(tui, "project_name", "str")
tui.sendline("myproj")
tui.expect_exact(pexpect.EOF)
else:
run_copy(str(src), dst, data={"project_name": "myproj"}, vcs_ref="v1")
assert (dst / "version.txt").read_text() == "v1"
_, err = capsys.readouterr()
assert re.search(
r"""
^Thank\ you\ for\ using\ our\ template\ on\ (linux|macos|windows).+
Project\ myproj\ successfully\ created\s*$
""",
err,
flags=re.S | re.X,
)
# recopy
if interactive:
tui = spawn(COPIER_PATH + ("recopy", "-r", "v1", str(dst)), timeout=10)
expect_prompt(tui, "project_name", "str")
tui.sendline("_new")
tui.expect_exact(pexpect.EOF)
else:
run_recopy(dst, data={"project_name": "myproj_new"}, vcs_ref="v1")
assert (dst / "version.txt").read_text() == "v1"
_, err = capsys.readouterr()
assert re.search(
r"""
^Thank\ you\ for\ using\ our\ template\ on\ (linux|macos|windows).+
Project\ myproj_new\ successfully\ created\s*$
""",
err,
flags=re.S | re.X,
)
with local.cwd(dst):
git("init")
git("add", ".")
git("commit", "-m1")
# clear capture output log
capsys.readouterr()
# update
if interactive:
tui = spawn(COPIER_PATH + ("update", str(dst)), timeout=10)
expect_prompt(tui, "project_name", "str")
tui.sendline("_update")
tui.expect_exact(pexpect.EOF)
else:
run_update(dst, data={"project_name": "myproj_new_update"}, overwrite=True)
assert (dst / "version.txt").read_text() == "v2"
_, err = capsys.readouterr()
assert re.search(
r"""
^Updating\ on\ (linux|macos|windows).+
Project\ myproj_new_update\ successfully\ updated\s*$
""",
err,
flags=re.S | re.X,
)
@pytest.mark.parametrize("interactive", [False, True])
def test_messages_with_included_text(
tmp_path_factory: pytest.TempPathFactory,
capsys: pytest.CaptureFixture[str],
spawn: Spawn,
interactive: bool,
) -> None:
src, dst = map(tmp_path_factory.mktemp, ["src", "dst"])
build_file_tree(
{
(src / "copier.yaml"): (
"""\
project_name:
type: str
_exclude: [".git", "*.md"]
_message_before_copy: "{% include 'message_before_copy.md.jinja' %}"
_message_after_copy: "{% include 'message_after_copy.md.jinja' %}"
_message_before_update: "{% include 'message_before_update.md.jinja' %}"
_message_after_update: "{% include 'message_after_update.md.jinja' %}"
"""
),
(src / "message_before_copy.md.jinja"): (
"""\
Thank you for using our template on {{ _copier_conf.os }}
"""
),
(src / "message_after_copy.md.jinja"): (
"""\
Project {{ project_name }} successfully created
"""
),
(src / "message_before_update.md.jinja"): (
"""\
Updating on {{ _copier_conf.os }}
"""
),
(src / "message_after_update.md.jinja"): (
"""\
Project {{ project_name }} successfully updated
"""
),
(src / "{{ _copier_conf.answers_file }}.jinja"): (
"""\
# Changes here will be overwritten by Copier
{{ _copier_answers|to_nice_yaml }}
"""
),
(src / "version.txt"): "v1",
}
)
with local.cwd(src):
git("init")
git("add", ".")
git("commit", "-m1")
git("tag", "v1")
build_file_tree(
{
(src / "version.txt"): "v2",
}
)
with local.cwd(src):
git("add", ".")
git("commit", "-m2")
git("tag", "v2")
# clear capture output log
capsys.readouterr()
# copy
if interactive:
tui = spawn(COPIER_PATH + ("copy", "-r", "v1", str(src), str(dst)), timeout=10)
expect_prompt(tui, "project_name", "str")
tui.sendline("myproj")
tui.expect_exact(pexpect.EOF)
else:
run_copy(str(src), dst, data={"project_name": "myproj"}, vcs_ref="v1")
assert (dst / "version.txt").read_text() == "v1"
_, err = capsys.readouterr()
assert re.search(
r"""
^Thank\ you\ for\ using\ our\ template\ on\ (linux|macos|windows).+
Project\ myproj\ successfully\ created\s*$
""",
err,
flags=re.S | re.X,
)
# recopy
if interactive:
tui = spawn(COPIER_PATH + ("recopy", "-r", "v1", str(dst)), timeout=10)
expect_prompt(tui, "project_name", "str")
tui.sendline("_new")
tui.expect_exact(pexpect.EOF)
else:
run_recopy(dst, data={"project_name": "myproj_new"}, vcs_ref="v1")
assert (dst / "version.txt").read_text() == "v1"
_, err = capsys.readouterr()
assert re.search(
r"""
^Thank\ you\ for\ using\ our\ template\ on\ (linux|macos|windows).+
Project\ myproj_new\ successfully\ created\s*$
""",
err,
flags=re.S | re.X,
)
with local.cwd(dst):
git("init")
git("add", ".")
git("commit", "-m1")
# clear capture output log
capsys.readouterr()
# update
if interactive:
tui = spawn(COPIER_PATH + ("update", str(dst)), timeout=10)
expect_prompt(tui, "project_name", "str")
tui.sendline("_update")
tui.expect_exact(pexpect.EOF)
else:
run_update(dst, data={"project_name": "myproj_new_update"}, overwrite=True)
assert (dst / "version.txt").read_text() == "v2"
_, err = capsys.readouterr()
assert re.search(
r"""
^Updating\ on\ (linux|macos|windows).+
Project\ myproj_new_update\ successfully\ updated\s*$
""",
err,
flags=re.S | re.X,
)
@pytest.mark.parametrize("interactive", [False, True])
def test_messages_quiet(
tmp_path_factory: pytest.TempPathFactory,
capsys: pytest.CaptureFixture[str],
spawn: Spawn,
interactive: bool,
) -> None:
src, dst = map(tmp_path_factory.mktemp, ["src", "dst"])
build_file_tree(
{
(src / "copier.yaml"): (
"""\
project_name:
type: str
_message_before_copy: Thank you for using our template on {{ _copier_conf.os }}
_message_after_copy: Project {{ project_name }} successfully created
_message_before_update: Updating on {{ _copier_conf.os }}
_message_after_update: Project {{ project_name }} successfully updated
"""
),
(src / "{{ _copier_conf.answers_file }}.jinja"): (
"""\
# Changes here will be overwritten by Copier
{{ _copier_answers|to_nice_yaml }}
"""
),
(src / "version.txt"): "v1",
}
)
with local.cwd(src):
git("init")
git("add", ".")
git("commit", "-m1")
git("tag", "v1")
build_file_tree(
{
(src / "version.txt"): "v2",
}
)
with local.cwd(src):
git("add", ".")
git("commit", "-m2")
git("tag", "v2")
# clear capture output log
capsys.readouterr()
# copy
if interactive:
tui = spawn(
COPIER_PATH + ("copy", "--quiet", "-r", "v1", str(src), str(dst)),
timeout=10,
)
expect_prompt(tui, "project_name", "str")
tui.sendline("myproj")
tui.expect_exact(pexpect.EOF)
else:
run_copy(
str(src), dst, data={"project_name": "myproj"}, vcs_ref="v1", quiet=True
)
assert (dst / "version.txt").read_text() == "v1"
_, err = capsys.readouterr()
assert "Thank you for using our template" not in err
assert "Project myproj successfully created" not in err
# recopy
if interactive:
tui = spawn(
COPIER_PATH + ("recopy", "--quiet", "-r", "v1", str(dst)), timeout=10
)
expect_prompt(tui, "project_name", "str")
tui.sendline("_new")
tui.expect_exact(pexpect.EOF)
else:
run_recopy(dst, data={"project_name": "myproj_new"}, vcs_ref="v1", quiet=True)
assert (dst / "version.txt").read_text() == "v1"
_, err = capsys.readouterr()
assert "Thank you for using our template" not in err
assert "Project myproj_new successfully created" not in err
with local.cwd(dst):
git("init")
git("add", ".")
git("commit", "-m1")
# clear capture output log
capsys.readouterr()
# update
if interactive:
tui = spawn(COPIER_PATH + ("update", "--quiet", str(dst)), timeout=10)
expect_prompt(tui, "project_name", "str")
tui.sendline("_update")
tui.expect_exact(pexpect.EOF)
else:
run_update(
dst, data={"project_name": "myproj_new_update"}, overwrite=True, quiet=True
)
assert (dst / "version.txt").read_text() == "v2"
_, err = capsys.readouterr()
assert "Updating on" not in err
assert "Project myproj_new_update successfully updated" not in err