copier/tests/test_migrations.py
Jairo Llopis 67cc4ffde3
feat: nix support
- Provide a dev shell.
- Provide a nix package.
- Provide a nix flake.
- Development environment based on direnv.
- Docs.
- Configure Gitpod to use direnv and nix.
- Configure Cachix out of the box, and document how to use it.
- Add direnv and nix to CI.
- Satisfy some linters that came from Precommix, even when Precommix was later discarded.
- Mark some tests as impure.
- Run only pure tests when building Copier with Nix.
- Add poetry loader to direnv.
- Update contribution guide.
2023-01-18 09:40:08 +00:00

226 lines
9.2 KiB
Python

import json
import platform
from glob import glob
from pathlib import Path
from shutil import copytree
import pytest
import yaml
from plumbum import local
from plumbum.cmd import git
from copier import copy
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,
)
def test_migrations_and_tasks(tmp_path: Path):
"""Check migrations and tasks are run properly."""
# Convert demo_migrations in a git repository with 2 versions
git_src, dst = tmp_path / "src", tmp_path / "tmp_path"
copytree(SRC, git_src)
with local.cwd(git_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
copy(src_path=str(git_src), dst_path=str(dst), vcs_ref="v1.0.0")
# Check copy was OK
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 glob(str(dst / "*-before.txt"))
assert not glob(str(dst / "*-after.txt"))
answers = yaml.safe_load((dst / ".copier-answers.yml").read_text())
assert answers == {"_commit": "v1.0.0", "_src_path": str(git_src)}
# Save changes in downstream repo
with local.cwd(dst):
git("add", ".")
git("config", "user.name", "Copier Test")
git("config", "user.email", "test@copier")
git("commit", "-m1")
# Update it to v2
copy(dst_path=str(dst), defaults=True, overwrite=True)
# Check update was OK
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(git_src)}
def test_pre_migration_modifies_answers(tmp_path_factory):
"""Test support for answers modifications in pre-migrations."""
template = tmp_path_factory.mktemp("template")
subproject = tmp_path_factory.mktemp("subproject")
# v1 of template asks for a favourite song and writes it to songs.json
with local.cwd(template):
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(subproject):
copy(src_path=str(template), 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(template):
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(subproject):
copy(defaults=True, overwrite=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: Path):
"""Test prereleases support for copying and updating."""
src, dst = tmp_path / "src", tmp_path / "dst"
src.mkdir()
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
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
copy(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
copy(dst_path=dst, defaults=True, overwrite=True, use_prereleases=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):
copy(dst_path=dst, defaults=True, overwrite=True)