copier/tests/test_migrations.py
Axel H 4ca2e35ff1
feat(tasks): add support for skipping tasks (#1561)
Co-authored-by: Timothée Mazzucotelli <dev@pawamoy.fr>
Co-authored-by: Sigurd Spieckermann <2206639+sisp@users.noreply.github.com>
2024-04-04 17:35:16 +02:00

312 lines
12 KiB
Python

import json
import platform
from pathlib import Path
from shutil import copytree
import pytest
import yaml
from plumbum import local
from plumbum.cmd import git
from copier import run_copy, run_update
from copier.errors import UserMessageError
from .helpers import BRACKET_ENVOPS_JSON, PROJECT_TEMPLATE, build_file_tree
SRC = Path(f"{PROJECT_TEMPLATE}_migrations").absolute()
# This fails on windows CI because, when the test tries to execute
# `migrations.py`, it doesn't understand that it should be interpreted
# by python.exe. Or maybe it fails because CI is using Git bash instead
# of WSL bash, which happened to work fine in real world tests.
# FIXME Some generous Windows power user please fix this test!
@pytest.mark.xfail(
condition=platform.system() == "Windows",
reason="Windows ignores shebang?",
strict=True,
)
@pytest.mark.parametrize("skip_tasks", [True, False])
def test_migrations_and_tasks(tmp_path: Path, skip_tasks: bool) -> None:
"""Check migrations and tasks are run properly."""
# Convert demo_migrations in a git repository with 2 versions
src, dst = tmp_path / "src", tmp_path / "dst"
copytree(SRC, src)
with local.cwd(src):
git("init")
git("config", "user.name", "Copier Test")
git("config", "user.email", "test@copier")
git("add", ".")
git("commit", "-m1")
git("tag", "v1.0.0")
git("commit", "--allow-empty", "-m2")
git("tag", "v2.0")
# Copy it in v1
run_copy(
src_path=str(src),
dst_path=dst,
vcs_ref="v1.0.0",
unsafe=True,
skip_tasks=skip_tasks,
)
# Check copy was OK
if skip_tasks:
assert not (dst / "created-with-tasks.txt").exists()
assert (dst / "delete-in-tasks.txt").exists()
else:
assert (dst / "created-with-tasks.txt").read_text() == "task 1\ntask 2\n"
assert not (dst / "delete-in-tasks.txt").exists()
assert (dst / "delete-in-migration-v2.txt").is_file()
assert not (dst / "migrations.py").exists()
assert not (dst / "tasks.py").exists()
assert not list(dst.glob("*-before.txt"))
assert not list(dst.glob("*-after.txt"))
answers = yaml.safe_load((dst / ".copier-answers.yml").read_text())
assert answers == {"_commit": "v1.0.0", "_src_path": str(src)}
# Save changes in downstream repo
with local.cwd(dst):
git("init")
git("add", ".")
git("config", "user.name", "Copier Test")
git("config", "user.email", "test@copier")
git("commit", "-m1")
# Update it to v2
run_update(
dst_path=dst, defaults=True, overwrite=True, unsafe=True, skip_tasks=skip_tasks
)
# Check update was OK
if skip_tasks:
assert not (dst / "created-with-tasks.txt").exists()
assert (dst / "delete-in-tasks.txt").exists()
else:
assert (dst / "created-with-tasks.txt").read_text() == "task 1\ntask 2\n" * 2
assert not (dst / "delete-in-tasks.txt").exists()
assert not (dst / "delete-in-migration-v2.txt").exists()
assert not (dst / "migrations.py").exists()
assert not (dst / "tasks.py").exists()
assert (dst / "v1.0.0-v2-v2.0-before.json").is_file()
assert (dst / "v1.0.0-v2-v2.0-after.json").is_file()
assert (dst / "PEP440-1.0.0-2-2.0-before.json").is_file()
assert (dst / "PEP440-1.0.0-2-2.0-after.json").is_file()
answers = yaml.safe_load((dst / ".copier-answers.yml").read_text())
assert answers == {"_commit": "v2.0", "_src_path": str(src)}
def test_pre_migration_modifies_answers(
tmp_path_factory: pytest.TempPathFactory,
) -> None:
"""Test support for answers modifications in pre-migrations."""
src, dst = map(tmp_path_factory.mktemp, ("src", "dst"))
# v1 of template asks for a favourite song and writes it to songs.json
with local.cwd(src):
build_file_tree(
{
"[[ _copier_conf.answers_file ]].jinja": (
"[[ _copier_answers|tojson ]]"
),
"copier.yml": (
f"""\
_envops: {BRACKET_ENVOPS_JSON}
best_song: la vie en rose
"""
),
"songs.json.jinja": "[ [[ best_song|tojson ]] ]",
}
)
git("init")
git("add", ".")
git("commit", "-m1")
git("tag", "v1")
# User copies v1 template into subproject
with local.cwd(dst):
run_copy(src_path=str(src), defaults=True, overwrite=True)
answers = json.loads(Path(".copier-answers.yml").read_text())
assert answers["_commit"] == "v1"
assert answers["best_song"] == "la vie en rose"
assert json.loads(Path("songs.json").read_text()) == ["la vie en rose"]
git("init")
git("add", ".")
git("commit", "-m1")
with local.cwd(src):
build_file_tree(
{
# v2 of template supports multiple songs, has a different default
# and includes a data format migration script
"copier.yml": (
f"""\
_envops: {BRACKET_ENVOPS_JSON}
best_song_list:
default: [paranoid android]
_migrations:
- version: v2
before:
- - python
- -c
- |
import sys, json, pathlib
answers_path = pathlib.Path(*sys.argv[1:])
answers = json.loads(answers_path.read_text())
answers["best_song_list"] = [answers.pop("best_song")]
answers_path.write_text(json.dumps(answers))
- "[[ _copier_conf.dst_path ]]"
- "[[ _copier_conf.answers_file ]]"
"""
),
"songs.json.jinja": "[[ best_song_list|tojson ]]",
}
)
git("add", ".")
git("commit", "-m2")
git("tag", "v2")
# User updates subproject to v2 template
with local.cwd(dst):
run_update(defaults=True, overwrite=True, unsafe=True)
answers = json.loads(Path(".copier-answers.yml").read_text())
assert answers["_commit"] == "v2"
assert "best_song" not in answers
assert answers["best_song_list"] == ["la vie en rose"]
assert json.loads(Path("songs.json").read_text()) == ["la vie en rose"]
def test_prereleases(tmp_path_factory: pytest.TempPathFactory) -> None:
"""Test prereleases support for copying and updating."""
src, dst = map(tmp_path_factory.mktemp, ("src", "dst"))
with local.cwd(src):
# Build template in v1.0.0
build_file_tree(
{
"version.txt": "v1.0.0",
"[[ _copier_conf.answers_file ]].jinja": "[[_copier_answers|to_nice_yaml]]",
"copier.yaml": (
f"""\
_envops: {BRACKET_ENVOPS_JSON}
_migrations:
- version: v1.9
before:
- [python, -c, "import pathlib; pathlib.Path('v1.9').touch()"]
- version: v2.dev0
before:
- [python, -c, "import pathlib; pathlib.Path('v2.dev0').touch()"]
- version: v2.dev2
before:
- [python, -c, "import pathlib; pathlib.Path('v2.dev2').touch()"]
- version: v2.a1
before:
- [python, -c, "import pathlib; pathlib.Path('v2.a1').touch()"]
- version: v2.a2
before:
- [python, -c, "import pathlib; pathlib.Path('v2.a2').touch()"]
"""
),
}
)
git("init")
git("add", ".")
git("commit", "-mv1")
git("tag", "v1.0.0")
# Evolve template to v2.0.0.dev1
build_file_tree({"version.txt": "v2.0.0.dev1"})
git("commit", "-amv2dev1")
git("tag", "v2.0.0.dev1")
# Evolve template to v2.0.0.alpha1
build_file_tree({"version.txt": "v2.0.0.alpha1"})
git("commit", "-amv2a1")
git("tag", "v2.0.0.alpha1")
# Copying with use_prereleases=False copies v1
run_copy(src_path=str(src), dst_path=dst, defaults=True, overwrite=True)
answers = yaml.safe_load((dst / ".copier-answers.yml").read_text())
assert answers["_commit"] == "v1.0.0"
assert (dst / "version.txt").read_text() == "v1.0.0"
assert not (dst / "v1.9").exists()
assert not (dst / "v2.dev0").exists()
assert not (dst / "v2.dev2").exists()
assert not (dst / "v2.a1").exists()
assert not (dst / "v2.a2").exists()
with local.cwd(dst):
# Commit subproject
git("init")
git("add", ".")
git("commit", "-mv1")
# Update it without prereleases; nothing changes
run_update(defaults=True, overwrite=True)
assert not git("status", "--porcelain")
assert not (dst / "v1.9").exists()
assert not (dst / "v2.dev0").exists()
assert not (dst / "v2.dev2").exists()
assert not (dst / "v2.a1").exists()
assert not (dst / "v2.a2").exists()
# Update it with prereleases
run_update(
dst_path=dst, defaults=True, overwrite=True, use_prereleases=True, unsafe=True
)
answers = yaml.safe_load((dst / ".copier-answers.yml").read_text())
assert answers["_commit"] == "v2.0.0.alpha1"
assert (dst / "version.txt").read_text() == "v2.0.0.alpha1"
assert (dst / "v1.9").exists()
assert (dst / "v2.dev0").exists()
assert (dst / "v2.dev2").exists()
assert (dst / "v2.a1").exists()
assert not (dst / "v2.a2").exists()
# It should fail if downgrading
with pytest.raises(UserMessageError):
run_update(dst_path=dst, defaults=True, overwrite=True)
def test_pretend_mode(tmp_path_factory: pytest.TempPathFactory) -> None:
src, dst = map(tmp_path_factory.mktemp, ("src", "dst"))
# Build template in v1
with local.cwd(src):
git("init")
build_file_tree(
{
"[[ _copier_conf.answers_file ]].jinja": "[[_copier_answers|to_nice_yaml]]",
"copier.yml": (
f"""\
_envops: {BRACKET_ENVOPS_JSON}
"""
),
}
)
git("add", ".")
git("commit", "-mv1")
git("tag", "v1")
run_copy(str(src), dst)
answers = yaml.safe_load((dst / ".copier-answers.yml").read_text())
assert answers["_commit"] == "v1"
with local.cwd(dst):
git("init")
git("add", ".")
git("commit", "-mv1")
# Evolve template to v2
with local.cwd(src):
build_file_tree(
{
"[[ _copier_conf.answers_file ]].jinja": "[[_copier_answers|to_nice_yaml]]",
"copier.yml": (
f"""\
_envops: {BRACKET_ENVOPS_JSON}
_migrations:
- version: v2
before:
- touch v2-before.txt
after:
- touch v2-after.txt
"""
),
}
)
git("add", ".")
git("commit", "-mv2")
git("tag", "v2")
run_update(dst_path=dst, overwrite=True, pretend=True, unsafe=True)
answers = yaml.safe_load((dst / ".copier-answers.yml").read_text())
assert answers["_commit"] == "v1"
assert not (dst / "v2-before.txt").exists()
assert not (dst / "v2-after.txt").exists()