def test_empty_suffix(tmp_path_factory): root = tmp_path_factory.mktemp("demo_empty_suffix") build_file_tree({ root / "copier.yaml": """ _templates_suffix: "" name: type: str default: pingu """, root / "render_me": "Hello {{name}}!", root / "{{name}}.txt": "Hello {{name}}!", root / "{{name}}" / "render_me.txt": "Hello {{name}}!", }) dest = tmp_path_factory.mktemp("dst") copier.copy(str(root), dest, defaults=True, overwrite=True) assert not (dest / "copier.yaml").exists() assert (dest / "render_me").exists() assert (dest / "pingu.txt").exists() assert (dest / "pingu" / "render_me.txt").exists() expected = "Hello pingu!" assert (dest / "render_me").read_text() == expected assert (dest / "pingu.txt").read_text() == expected assert (dest / "pingu" / "render_me.txt").read_text() == expected
def test_api(dst): """Test copier correctly processes advanced questions and answers through API.""" copy( SRC, dst, { "love_me": False, "your_name": "LeChuck", "your_age": 220, "your_height": 1.9, "more_json_info": ["bad", "guy"], "anything_else": { "hates": "all" }, }, force=True, ) results_file = dst / "results.txt" assert results_file.read_text() == dedent(""" love_me: false your_name: "LeChuck" your_age: 220 your_height: 1.9 more_json_info: ["bad", "guy"] anything_else: {"hates": "all"} choose_list: "first" choose_tuple: "second" choose_dict: "third" choose_number: null optional_value: null """)
def test_copy_with_extra_paths_from_config(dst): copier.copy(CHILD_DIR_CONFIG, dst) gen_file = dst / "child.txt" result = gen_file.read_text() expected = Path(PARENT_DIR + "/parent.txt").read_text() assert result == expected
def test_copy_with_extra_paths(dst): copier.copy(CHILD_DIR, dst, extra_paths=[PARENT_DIR]) gen_file = dst / "child.txt" result = gen_file.read_text() expected = Path(PARENT_DIR + "/parent.txt").read_text() assert result == expected
def run(opts): bindings = { 'target': opts.get('<target>'), 'from': convert_path(opts.get('<from>')), 'checkout': opts.get('<checkout>'), 'app': 'app', 'proj': inflection.camelize(opts.get('<target>').split('/')[-1]), 'file': opts.get('--file') } if bindings.get('file'): with open(bindings.get('file')) as f: conf = json.load(f) check_conflict(conf) bindings.update(conf) opt_json = json.loads(opts.get('--json')) check_conflict(opt_json) bindings.update(opt_json) location = get_location(bindings) try: copy(f'{location}/new', bindings.get('target'), data=bindings, exclude=['*/__pycache__/*']) except ValueError as err: print(f'Error: {err}')
def test_api_str_data(tmp_path, template_path): """Test copier when all data comes as a string. This happens i.e. when using the --data CLI argument. """ copy( template_path, tmp_path, data={ "love_me": "false", "your_name": "LeChuck", "your_age": "220", "your_height": "1.9", "more_json_info": '["bad", "guy"]', "anything_else": "{'hates': 'all'}", "choose_number": "0", }, force=True, ) results_file = tmp_path / "results.txt" assert results_file.read_text() == dedent("""\ love_me: "false" your_name: "LeChuck" your_age: "220" your_height: "1.9" more_json_info: "[\\"bad\\", \\"guy\\"]" anything_else: "{\\u0027hates\\u0027: \\u0027all\\u0027}" choose_list: "first" choose_tuple: "second" choose_dict: "third" choose_number: "0" minutes_under_water: 10 optional_value: null """)
def test_copy_subdirectory_api_option(tmp_path): copier.copy("./tests/demo_subdirectory", tmp_path, force=True, subdirectory="api_project") assert (tmp_path / "api_readme.md").exists() assert not (tmp_path / "conf_readme.md").exists()
def run(opts): bindings = { 'target': opts.get('<target>'), 'secret_key': secrets.token_urlsafe(12), 'prod_secret_key': secrets.token_urlsafe(24), 'app': inflection.underscore(opts.get('--app')), 'proj': inflection.camelize( opts.get('--proj') or opts.get('<target>').split('/')[-1]), 'bare': opts.get('--bare'), 'pypi': opts.get('--pypi'), } ignore_list = ['*/__pycache__/*'] if bindings.get('bare'): ignore_list.extend([ f'{bindings.get("app")}/helpers.py', '*/models/user.py', '*/views/user.py', '*/views/session.py', '*/schemas/user.py', '*/schemas/session.py', 'test/views/test_user.py', 'test/views/test_session.py' ]) copy(f'{TPL_PATH}/new', bindings.get('target'), data=bindings, exclude=ignore_list)
def test_read_data(tmp_path_factory, config_suffix): src, dst = map(tmp_path_factory.mktemp, ("src", "dst")) build_file_tree({ src / f"copier.{config_suffix}": f"""\ # This is a comment _envops: {BRACKET_ENVOPS_JSON} a_string: lorem ipsum a_number: 12345 a_boolean: true a_list: - one - two - three """, src / "user_data.txt.jinja": """\ A string: [[ a_string ]] A number: [[ a_number ]] A boolean: [[ a_boolean ]] A list: [[ ", ".join(a_list) ]] """, }) copier.copy(str(src), dst, defaults=True, overwrite=True) gen_file = dst / "user_data.txt" result = gen_file.read_text() assert result == dedent("""\ A string: lorem ipsum A number: 12345 A boolean: True A list: one, two, three """)
def test_config_include(dst, monkeypatch): def fake_data(*_args, **_kwargs): return {"_exclude": ["!.svn"]} monkeypatch.setattr(copier.config.factory, "load_config_data", fake_data) copier.copy(str(PROJECT_TEMPLATE), dst, data=DATA, quiet=True) assert (dst / ".svn").exists()
def test_copy_subdirectory_api_option(demo_template, tmp_path): copier.copy(demo_template, tmp_path, force=True, data={"choose_subdir": "api_project"}) assert (tmp_path / "api_readme.md").exists() assert not (tmp_path / "conf_readme.md").exists()
def new_project(destination_path: str, template: str, namespace: str = "vernacular-ai") -> None: """ Create project scaffolding. This function uses [`copier.copy`](https://copier.readthedocs.io/en/stable/) to use an existing template. Args: template (str): Look for "dialogy-template-*" here: https://github.com/Vernacular-ai Example: "dialogy-template-simple-transformers" destination_path (str): namespace (str, optional): A github/gitlab Organization or Username. Defaults to "vernacular-ai". """ if not os.path.exists(destination_path): os.mkdir(destination_path) if os.listdir(destination_path): log.error("There are files on the destination path. Aborting !") return None copy(f"gh:{namespace}/{template}.git", destination_path) return None
def test_api_str_data(dst): """Test copier when all data comes as a string. This happens i.e. when using the --data CLI argument. """ copy( SRC, dst, data={ "love_me": "false", "your_name": "LeChuck", "your_age": "220", "your_height": "1.9", "more_json_info": '["bad", "guy"]', "anything_else": "{'hates': 'all'}", "choose_number": "0", }, force=True, ) results_file = dst / "results.txt" assert results_file.read_text() == dedent(r""" love_me: false your_name: "LeChuck" your_age: 220 your_height: 1.9 more_json_info: ["bad", "guy"] anything_else: {"hates": "all"} choose_list: "first" choose_tuple: "second" choose_dict: "third" choose_number: 0.0 optional_value: null """)
def test_v1_5_2_migration(tmp_path: Path, cloned_template: Path, supported_odoo_version: float): """Test migration to v1.5.2.""" auto = tmp_path / "odoo" / "auto" empty = auto / ".empty" # This file existed in doodba-scaffolding with local.cwd(tmp_path): # Copy v1.5.1 copy( src_path=str(cloned_template), vcs_ref="v1.5.1", force=True, data={"odoo_version": supported_odoo_version}, ) auto.mkdir() empty.touch() assert empty.exists() git("add", ".") git("add", "-f", empty) git("commit", "-am", "reformat", retcode=1) git("commit", "-am", "copied from template in v1.5.1") # Update to v1.5.2 copy(vcs_ref="v1.5.2", force=True) assert not empty.exists() assert not auto.exists() invoke("develop") assert auto.exists() assert not empty.exists()
def test_config_exclude(tmp_path, monkeypatch): def fake_data(*_args, **_kwargs): return {"_exclude": ["*.txt"]} monkeypatch.setattr(copier.config.factory, "load_config_data", fake_data) copier.copy(str(PROJECT_TEMPLATE), tmp_path, data=DATA, quiet=True) assert not (tmp_path / "aaaa.txt").exists()
def test_read_data(dst, template): copier.copy(template, dst, force=True) gen_file = dst / "user_data.txt" result = gen_file.read_text() expected = Path("tests/user_data.ref.txt").read_text() assert result == expected
def test_config_exclude_overridden(dst): def fake_data(*_args, **_kwargs): return {"_exclude": ["*.txt"]} # copier.user_data._load_config_data = copier.user_data.load_config_data # copier.user_data.load_config_data = fake_data copier.copy(str(PROJECT_TEMPLATE), dst, data=DATA, quiet=True, exclude=[]) assert (dst / "aaaa.txt").exists()
def test_no_cleanup_when_folder_existed(tmp_path): """Copier will not delete a folder if it didn't create it.""" with pytest.raises(CalledProcessError): copier.copy("./tests/demo_cleanup", tmp_path, quiet=True, cleanup_on_error=True) assert (tmp_path).exists()
def test_bootstrap(tmp_path: Path, odoo_version: float, cloned_template: Path): """Test that a project is properly bootstrapped.""" data = { "odoo_version": odoo_version, "repo_slug": REPO_SLUG, "repo_name": "Test repo", "repo_description": "Test repo description", } copy(str(cloned_template), tmp_path, data=data, force=True) # When loading YAML files, we are also testing their syntax is correct, which # can be a little bit tricky due to the way both Jinja and YAML handle whitespace answers = yaml.safe_load((tmp_path / ".copier-answers.yml").read_text()) for key, value in data.items(): assert answers[key] == value # Assert linter config files look like they were looking before pylintrc_mandatory = (tmp_path / ".pylintrc-mandatory").read_text() assert "disable=all\n\nenable=" in pylintrc_mandatory assert f"valid_odoo_versions={odoo_version}" in pylintrc_mandatory assert SOME_PYLINT_OPTIONAL_CHECK not in pylintrc_mandatory pylintrc_optional = (tmp_path / ".pylintrc").read_text() assert "disable=all\n\n# This .pylintrc" in pylintrc_optional assert f"valid_odoo_versions={odoo_version}" in pylintrc_optional assert SOME_PYLINT_OPTIONAL_CHECK in pylintrc_optional flake8 = (tmp_path / ".flake8").read_text() assert "[flake8]" in flake8 isort = (tmp_path / ".isort.cfg").read_text() assert "[settings]" in isort assert not (tmp_path / ".gitmodules").is_file() # Assert other files contributing = tmp_path / "CONTRIBUTING.md" assert contributing.is_file() license_ = (tmp_path / "LICENSE").read_text() assert "GNU AFFERO GENERAL PUBLIC LICENSE" in license_ # Assert badges in readme; this is testing the repo_id macro readme = (tmp_path / "README.md").read_text() assert ( f"[![Runbot Status](https://runbot.odoo-community.org/runbot/badge/flat/{REPO_ID}/{odoo_version}.svg)](https://runbot.odoo-community.org/runbot/repo/github-com-oca-{REPO_SLUG}-{REPO_ID})" # noqa: B950 in readme) assert ( f"[![Build Status](https://travis-ci.com/OCA/{REPO_SLUG}.svg?branch={odoo_version})](https://travis-ci.com/OCA/{REPO_SLUG})" # noqa: B950 in readme) assert ( f"[![codecov](https://codecov.io/gh/OCA/{REPO_SLUG}/branch/{odoo_version}/graph/badge.svg)](https://codecov.io/gh/OCA/{REPO_SLUG})" # noqa: B950 in readme) assert "# Test repo" in readme assert data["repo_description"] in readme # Assert no stuff specific for this repo is found garbage = ( "setup.cfg", "tests", "vendor", "copier.yml", ".gitmodules", "poetry.lock", "pyproject.toml", ) for file_ in garbage: assert not (tmp_path / file_).exists()
def test_config_exclude(dst): def fake_data(*_args, **_kw): return {"_exclude": ["*.txt"]} copier.main._load_config_data = copier.main.load_config_data copier.main.load_config_data = fake_data copier.copy(PROJECT_TEMPLATE, dst, data=DATA, quiet=True) assert not (dst / "aaaa.txt").exists() copier.main.load_config_data = copier.main._load_config_data
def test_config_include(dst): def fake_data(*_args, **_kwargs): return {"_include": [".svn"]} copier.config.factory._load_config_data = copier.config.factory.load_config_data copier.config.factory.load_config_data = fake_data copier.copy(str(PROJECT_TEMPLATE), dst, data=DATA, quiet=True) assert (dst / ".svn").exists() copier.config.factory.load_config_data = copier.config.factory._load_config_data
def test_additional_jinja2_extensions(tmp_path): copier.copy( str(PROJECT_TEMPLATE) + "_extensions_additional", tmp_path, ) super_file = tmp_path / "super_file.md" assert super_file.exists() expected = "super var! super func! super filter!" assert super_file.read_text() == expected
def test_copy_repo(tmp_path): copier.copy( "gh:copier-org/copier.git", tmp_path, vcs_ref="HEAD", quiet=True, exclude=["*", "!README.*"], ) assert (tmp_path / "README.md").exists()
def test_default_jinja2_extensions(tmp_path): copier.copy( str(PROJECT_TEMPLATE) + "_extensions_default", tmp_path, ) super_file = tmp_path / "super_file.md" assert super_file.exists() expected = "path" assert super_file.read_text() == expected
def test_v2_0_0_migration( tmp_path: Path, cloned_template: Path, supported_odoo_version: float, domain_prod, domain_prod_alternatives, domain_test, ): """Test migration to v2.0.0.""" # Construct data dict, removing MISSING values data = { "domain_prod": domain_prod, "domain_prod_alternatives": domain_prod_alternatives, "domain_test": domain_test, "odoo_version": supported_odoo_version, } for key, value in tuple(data.items()): if value is MISSING: data.pop(key, None) # This part makes sense only when v2.0.0 is not yet released with local.cwd(cloned_template): if "v2.0.0" not in git("tag").split(): git("tag", "-d", "test") git("tag", "v2.0.0") with local.cwd(tmp_path): # Copy v1.6.0 copy( src_path=str(cloned_template), vcs_ref="v1.6.0", force=True, answers_file=".custom.copier-answers.yaml", data=data, ) git("config", "commit.gpgsign", "false") git("add", ".") git("commit", "-am", "reformat", retcode=1) git("commit", "-am", "copied from template in v1.6.0") # Update to v2.0.0 copy(answers_file=".custom.copier-answers.yaml", vcs_ref="v2.0.0", force=True) git("add", ".") git("commit", "-am", "reformat", retcode=1) git("commit", "-am", "updated from template in v2.0.0") # Assert domain structure migration answers = yaml.safe_load( Path(".custom.copier-answers.yaml").read_text()) assert "domain_prod" not in answers assert "domain_prod_alternatives" not in answers assert "domain_test" not in answers assert answers["domains_prod"] == { data.get("domain_prod"): data.get("domain_prod_alternatives") or [] } assert answers["domains_staging"] == ([domain_test] if data.get("domain_test") else [])
def test_skip_if_exists(dst): copier.copy("tests/demo_skip_dst", dst) copier.copy("tests/demo_skip_src", dst, skip_if_exists=["b.txt", "meh/c.txt"], force=True) assert (dst / "a.txt").read_text() == "OVERWRITTEN" assert (dst / "b.txt").read_text() == "SKIPPED" assert (dst / "meh" / "c.txt").read_text() == "SKIPPED"
def test_copy_tasks(tmp_path, demo_template): copier.copy(demo_template, tmp_path, quiet=True, defaults=True, overwrite=True) assert (tmp_path / "hello").exists() assert (tmp_path / "hello").is_dir() assert (tmp_path / "hello" / "world").exists() assert (tmp_path / "bye").is_file()
def test_update_subdirectory(demo_template, tmp_path): copier.copy(demo_template, tmp_path, force=True) with local.cwd(tmp_path): git_init() copier.copy(dst_path=tmp_path, force=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_do_not_cleanup(tmp_path): """Copier creates dst_path, fails to copy and keeps it.""" dst = tmp_path / "new_folder" with pytest.raises(CalledProcessError): copier.copy( "./tests/demo_cleanup", dst, quiet=True, cleanup_on_error=False, ) assert (dst).exists()
def test_multiple_domains_prod( cloned_template: Path, supported_odoo_version: float, tmp_path: Path, traefik_host: str, ): """Test multiple domains are produced properly.""" data = { "traefik_cert_resolver": None, "odoo_version": supported_odoo_version, "project_name": uuid.uuid4().hex, "domains_prod": { f"main.{traefik_host}.sslip.io": [ f"alt0.main.{traefik_host}.sslip.io", f"alt1.main.{traefik_host}.sslip.io", ], f"secondary.{traefik_host}.sslip.io": [ f"alt0.secondary.{traefik_host}.sslip.io", f"alt1.secondary.{traefik_host}.sslip.io", ], f"third.{traefik_host}.sslip.io": [], }, } dc = docker_compose["-f", "prod.yaml"] with local.cwd(tmp_path): copy( src_path=str(cloned_template), dst_path=".", vcs_ref="test", force=True, data=data, ) try: dc("build") dc( "run", "--rm", "odoo", "--stop-after-init", "-i", "base", ) dc("up", "-d") time.sleep(10) for main_domain, alt_list in data["domains_prod"].items(): for alt_domain in alt_list + [main_domain]: response = requests.get(f"http://{alt_domain}/web/login") assert response.ok assert response.url == f"http://{main_domain}/web/login" bad_response = requests.get( f"http://missing.{traefik_host}.sslip.io") assert bad_response.status_code == 404 finally: dc("down", "--volumes", "--remove-orphans")