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

336 lines
11 KiB
Python

from pathlib import Path
from textwrap import dedent
from typing import Literal
import pytest
from plumbum import local
import copier
from copier._user_data import load_answersfile_data
from .helpers import BRACKET_ENVOPS_JSON, SUFFIX_TMPL, build_file_tree, git, git_init
@pytest.fixture(scope="module")
def template_path(tmp_path_factory: pytest.TempPathFactory) -> str:
root = tmp_path_factory.mktemp("template")
build_file_tree(
{
(root / "api_project" / "api_readme.md"): "",
(root / "api_project" / "[[ _copier_conf.answers_file ]].tmpl"): (
"[[ _copier_answers|to_nice_yaml ]]"
),
(root / "conf_project" / "conf_readme.md"): (
"""\
# Template subdirectory
This is the template README.
"""
),
(root / "conf_project" / "conf_readme.md.tmpl"): (
"""\
# Demo subdirectory
Generated using previous answers `_subdirectory` value.
"""
),
(root / "conf_project" / "[[ _copier_conf.answers_file ]].tmpl"): (
"[[ _copier_answers|to_nice_yaml ]]"
),
(root / "conf_project" / "[[ filename ]].tmpl"): (
"[[ filename ]] contents"
),
(root / "copier.yml"): (
f"""\
_templates_suffix: {SUFFIX_TMPL}
_envops: {BRACKET_ENVOPS_JSON}
choose_subdir:
type: str
default: conf_project
choices:
- api_project
- conf_project
_subdirectory: "[[ choose_subdir ]]"
filename:
type: str
default: mock_filename
"""
),
}
)
with local.cwd(root):
git_init()
return str(root)
def test_copy_subdirectory_api_option(template_path: str, tmp_path: Path) -> None:
copier.run_copy(
template_path,
tmp_path,
defaults=True,
overwrite=True,
data={"choose_subdir": "api_project"},
)
assert (tmp_path / "api_readme.md").exists()
assert not (tmp_path / "conf_readme.md").exists()
def test_copy_subdirectory_config(template_path: str, tmp_path: Path) -> None:
copier.run_copy(template_path, tmp_path, defaults=True, overwrite=True)
assert (tmp_path / "conf_readme.md").exists()
assert not (tmp_path / "api_readme.md").exists()
def test_copy_subdirectory_config_no_overwrite(
template_path: str, tmp_path: Path
) -> None:
copier.run_copy(template_path, tmp_path, defaults=True, overwrite=False)
assert (tmp_path / "conf_readme.md").exists()
assert (tmp_path / "mock_filename").exists()
assert "mock_filename contents" in (tmp_path / "mock_filename").read_text()
assert "# Demo subdirectory" in (tmp_path / "conf_readme.md").read_text()
assert "# Template subdirectory" not in (tmp_path / "conf_readme.md").read_text()
assert not (tmp_path / "api_readme.md").exists()
def test_update_subdirectory(template_path: str, tmp_path: Path) -> None:
copier.run_copy(template_path, tmp_path, defaults=True, overwrite=True)
with local.cwd(tmp_path):
git_init()
copier.run_update(dst_path=tmp_path, defaults=True, overwrite=True)
assert not (tmp_path / "conf_project").exists()
assert not (tmp_path / "api_project").exists()
assert not (tmp_path / "api_readme.md").exists()
assert (tmp_path / "conf_readme.md").exists()
def test_update_subdirectory_from_root_path(
tmp_path_factory: pytest.TempPathFactory,
) -> None:
src, dst = map(tmp_path_factory.mktemp, ("src", "dst"))
with local.cwd(src):
build_file_tree(
{
"copier.yaml": (
"""\
q1:
type: str
default: a1
"""
),
"file1.jinja": (
"""\
version 1
hello
{{ q1 }}
bye
"""
),
"{{ _copier_conf.answers_file }}.jinja": "{{ _copier_answers|to_nice_yaml }}",
}
)
git("init")
git("add", ".")
git("commit", "-m1")
git("tag", "1")
build_file_tree(
{
"file1.jinja": (
"""\
version 2
hello
{{ q1 }}
bye
"""
),
}
)
git("commit", "-am2")
git("tag", "2")
with local.cwd(dst):
build_file_tree({"dst_top_file": "one"})
git("init")
git("add", ".")
git("commit", "-m0")
copier.run_copy(
str(src),
dst / "subfolder",
vcs_ref="1",
defaults=True,
overwrite=True,
answers_file=".custom.copier-answers.yaml",
)
assert (dst / "subfolder" / "file1").read_text() == "version 1\nhello\na1\nbye\n"
with local.cwd(dst):
git("add", ".")
git("commit", "-m1")
copier.run_update(
"subfolder",
defaults=True,
overwrite=True,
answers_file=".custom.copier-answers.yaml",
)
answers = load_answersfile_data(dst / "subfolder", ".custom.copier-answers.yaml")
assert answers["_commit"] == "2"
assert (dst / "subfolder" / "file1").read_text() == "version 2\nhello\na1\nbye\n"
@pytest.mark.parametrize(
"conflict, readme, expect_reject",
[
(
"rej",
"upstream version 2\n",
True,
),
(
"inline",
dedent(
"""\
<<<<<<< before updating
downstream version 1
=======
upstream version 2
>>>>>>> after updating
"""
),
False,
),
],
)
def test_new_version_uses_subdirectory(
tmp_path_factory: pytest.TempPathFactory,
conflict: Literal["rej", "inline"],
readme: str,
expect_reject: bool,
) -> None:
# Template in v1 doesn't have a _subdirectory;
# in v2 it moves all things into a subdir and adds that key to copier.yml.
# Some files change. Downstream project has evolved too. Does that work as expected?
src, dst = map(tmp_path_factory.mktemp, ("src", "dst"))
# First, create the template with an initial README
build_file_tree(
{
(src / "README.md"): "upstream version 1",
(src / "{{_copier_conf.answers_file}}.jinja"): (
"{{_copier_answers|to_nice_yaml}}"
),
}
)
with local.cwd(src):
git_init("hello template")
git("tag", "v1")
# Generate the project a first time, assert the README exists
copier.run_copy(str(src), dst, defaults=True, overwrite=True)
assert (dst / "README.md").exists()
assert load_answersfile_data(dst).get("_commit") == "v1"
# Start versioning the generated project
with local.cwd(dst):
git_init("hello project")
# After first commit, change the README, commit again
Path("README.md").write_text("downstream version 1")
git("commit", "-am", "updated readme")
# Now change the template
with local.cwd(src):
# Update the README
Path("README.md").write_text("upstream version 2")
# Create a subdirectory, move files into it
subdir = Path("subdir")
subdir.mkdir()
Path("README.md").rename(subdir / "README.md")
Path("{{_copier_conf.answers_file}}.jinja").rename(
subdir / "{{_copier_conf.answers_file}}.jinja"
)
# Add the subdirectory option to copier.yml
Path("copier.yml").write_text(f"_subdirectory: {subdir}")
# Commit the changes
git("add", ".", "-A")
git("commit", "-m", "use a subdirectory now")
git("tag", "v2")
# Finally, update the generated project
copier.run_update(dst_path=dst, defaults=True, overwrite=True, conflict=conflict)
assert load_answersfile_data(dst).get("_commit") == "v2"
# Assert that the README still exists, and the conflicts were handled
# correctly.
assert (dst / "README.md").exists()
assert (dst / "README.md").read_text().splitlines() == readme.splitlines()
assert (dst / "README.md.rej").exists() == expect_reject
# Also assert the subdirectory itself was not rendered
assert not (dst / subdir).exists()
def test_new_version_changes_subdirectory(
tmp_path_factory: pytest.TempPathFactory,
) -> None:
# Template in v3 changes from one subdirectory to another.
# Some file evolves also. Sub-project evolves separately.
# Sub-project is updated. Does that work as expected?
src, dst = map(tmp_path_factory.mktemp, ("src", "dst"))
# First, create the template with an initial subdirectory and README inside it
build_file_tree(
{
(src / "copier.yml"): "_subdirectory: subdir1\n",
(src / "subdir1" / "[[_copier_conf.answers_file]].tmpl"): (
"[[_copier_answers|to_nice_yaml]]\n"
),
(src / "subdir1" / "README.md"): "upstream version 1\n",
}
)
with local.cwd(src):
git_init("hello template")
# Generate the project a first time, assert the README exists
copier.run_copy(str(src), dst, defaults=True, overwrite=True)
assert (dst / "README.md").exists()
# Start versioning the generated project
with local.cwd(dst):
git_init("hello project")
# After first commit, change the README, commit again
Path("README.md").write_text("downstream version 1\n")
git("commit", "-am", "updated readme")
# Now change the template
with local.cwd(src):
# Update the README
Path("subdir1", "README.md").write_text("upstream version 2\n")
# Rename the subdirectory
Path("subdir1").rename("subdir2")
# Update copier.yml to reflect this change
Path("copier.yml").write_text("_subdirectory: subdir2\n")
# Commit the changes
git("add", ".", "-A")
git("commit", "-m", "changed from subdir1 to subdir2")
# Finally, update the generated project
copier.run_copy(
str(src), dst, defaults=True, overwrite=True, skip_if_exists=["README.md"]
)
# Assert that the README still exists, and was NOT force updated
assert (dst / "README.md").exists()
assert (dst / "README.md").read_text() == "downstream version 1\n"
# Also assert the subdirectories themselves were not rendered
assert not (dst / "subdir1").exists()
assert not (dst / "subdir2").exists()