copier/tests/test_migrations.py
Jairo Llopis 0441b86f0c
Refactor (#314)
* Refactor
* Fix #110.
* Rewrite test_config_exclude, test_config_exclude_overridden and test_config_include. These tests were badly designed, using a monkeypatch that would never happen in the real world, and actually producing false positives. I moved them to test_exclude.py and rewritten to test the what and not the how.
* Fix #214 by removing skip option. Relevant tests use the better skip_if_exists=["**"].
* Remove subdirectory flag from API/CLI. It was confusing and could lead to bad maintenance situations. Fixes #315.
* Remove extra_paths and fix #321
* Remember that you cannot use _copier_conf.src_path as a path now
* use dataclasses
* Create errors module, simplify some tests, fix many others
* Fix some tests, complete EnvOps removal
* Fix #214 and some tests related to it
* Reorder code
* Update docs and imports
* Modularize test_complex_questions
* Interlink worker and questionary a bit better
* Removal of Questionary class, which only had 1 meaningful method that is now merged into Worker to avoid circular dependencies.
* Fix #280 in a simple way: only user answers are type-casted inside API, and CLI transforms all `--data` using YAML always. Predictable.
* Use prereleases correctly.
* Reorder AnswersMap to have a more significative repr.
* Simpler cache for old `Question.get_choices()` method (renamed now).
* fix wrong test
* Fix test_subdirectory
* Fix test_tasks (and remove tests/demo_tasks)
* Fix path filter tests, and move it to test_exclude, where it belongs
* Make test_config pass
* Fix more wrongly designed tests
* Use cached_property backport if needed
* xfail test known to fail on mac
* force posix paths on windows
* Add typing_extensions for python < 3.8
* Sort dependencies in pyproject.toml
* Support python 3.6 str-to-datetime conversion
* Workaround https://bugs.python.org/issue43095
* xfail test_path_filter on windows
* Upgrade mkdocs and other dependencies to fix https://github.com/pawamoy/mkdocstrings/issues/222
* Add missing reference docs.
* Add workaround for https://github.com/pawamoy/mkdocstrings/pull/209
* Docs.
* Remove validators module
* Add workaround for https://github.com/pawamoy/mkdocstrings/issues/225
* Restore docs autorefs as explained in https://github.com/pawamoy/mkdocstrings/issues/226#issuecomment-775413562.
* Workaround https://github.com/pawamoy/pytkdocs/issues/86
2021-02-09 18:22:47 +00:00

221 lines
8.8 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 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), force=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()
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 ]].tmpl": "[[ _copier_answers|tojson ]]",
"copier.yml": """\
best_song: la vie en rose
""",
"songs.json.tmpl": "[ [[ 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), force=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": """\
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.tmpl": "[[ best_song_list|tojson ]]",
}
)
git("add", ".")
git("commit", "-m2")
git("tag", "v2")
# User updates subproject to v2 template
with local.cwd(subproject):
copy(force=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 ]].tmpl": "[[_copier_answers|to_nice_yaml]]",
"copier.yaml": """
_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, force=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(force=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, force=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, force=True)