Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Test Windows & Mac OSX #224

Merged
merged 6 commits into from
Aug 8, 2020
Merged

Test Windows & Mac OSX #224

merged 6 commits into from
Aug 8, 2020

Conversation

yajo
Copy link
Member

@yajo yajo commented Jun 23, 2020

Let's see if tests work under windows or mac.

Continuation of #213

@yajo yajo mentioned this pull request Jun 23, 2020
@yajo yajo self-assigned this Jun 23, 2020
@yajo yajo added automerge maintenance Boring maintenance routine labels Jun 23, 2020
@yajo yajo changed the title Cross os test Test Windows & Mac OSX Jun 23, 2020
@pawamoy
Copy link
Contributor

pawamoy commented Jul 23, 2020

So, here's the complete log of the test suite running on Windows 10, Python 3.6.8 👍

It's almost only "template not found" errors and permission errors following the template ones (trying to remove the destination folder). You don't have to read the whole log, just look at the comments in between.

$ poetry run pytest copier tests
============================= test session starts =============================
platform win32 -- Python 3.6.8, pytest-5.4.3, py-1.8.2, pluggy-0.13.1
rootdir: C:\Users\user\copier
plugins: cov-2.10.0, forked-1.1.3, timeout-1.4.1, xdist-1.32.0
collected 113 items

tests\test_answersfile.py ..                                             [  1%]
tests\test_cleanup.py ..                                                 [  3%]
tests\test_cli.py ..                                                     [  5%]
tests\test_complex_questions.py ....                                     [  8%]
tests\test_config.py ...F............................                    [ 37%]
tests\test_copy.py .FFFFFF..FFFFFF.                                      [ 51%]
tests\test_demo_update_tasks.py F                                        [ 52%]
tests\test_exclude.py ..                                                 [ 53%]
tests\test_extra_paths.py ....                                           [ 57%]
tests\test_migrations.py FF                                              [ 59%]
tests\test_minimum_version.py ....                                       [ 62%]
tests\test_normal_jinja2.py .                                            [ 63%]
tests\test_output.py FFFFF                                               [ 68%]
tests\test_prompt.py FFF                                                 [ 70%]
tests\test_subdirectory.py ..FFF                                         [ 75%]
tests\test_tasks.py F.                                                   [ 76%]
tests\test_templated_prompt.py .............                             [ 88%]
tests\test_tools.py .........                                            [ 96%]
tests\test_updatediff.py FF                                              [ 98%]
tests\test_vcs.py FF                                                     [100%]
test_invalid_yaml

================================== FAILURES ===================================
______________________________ test_invalid_yaml ______________________________

capsys = <_pytest.capture.CaptureFixture object at 0x000001F82B8625C0>

def test_invalid_yaml(capsys):
    conf_path = Path("tests/demo_invalid/copier.yml")
    with pytest.raises(InvalidConfigFileError):
        load_yaml_data(conf_path)
    out, _ = capsys.readouterr()
  assert re.search(r"INVALID.*tests/demo_invalid/copier\.yml", out)

E assert None
E + where None = <function search at 0x000001F82968A8C8>('INVALID.*tests/demo_invalid/copier\.yml', "\n\x1b[31m\x1b[1mINVALID CONFIG FILE\x1b[39m\x1b[0m tests\demo_invalid\copier.yml\n------------------------------------------\n(WindowsPath('tests/demo_invalid/copier.yml'), False)\n------------------------------------------\n")
E + where <function search at 0x000001F82968A8C8> = re.search

tests\test_config.py:52: AssertionError

I'd say it's just the test itself that is wrong because it's using forward slashes.

test_copy

__________________________________ test_copy __________________________________

tmp_path = WindowsPath('C:/Users/user/AppData/Local/Temp/1/pytest-of-user/pytest-1/test_copy0')

def test_copy(tmp_path):
  render(tmp_path)

tests\test_copy.py:20:


tests\helpers.py:26: in render
copier.copy(str(PROJECT_TEMPLATE), tmp_path, data=DATA, **kwargs)
copier\main.py:142: in copy
copy_local(conf=conf)
copier\main.py:190: in copy_local
render_file(conf, rel_path, source_path, render, must_skip)
copier\main.py:325: in render_file
content = render(src_path)
copier\tools.py:158: in call
tmpl = self.env.get_template(relpath)
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\environment.py:883: in get_template
return self._load_template(name, self.make_globals(globals))
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\environment.py:857: in _load_template
template = self.loader.load(self, name, globals)
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\loaders.py:115: in load
source, filename, uptodate = self.get_source(environment, name)
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\loaders.py:177: in get_source
pieces = split_template_path(template)


template = '[% if not py3 %]py2_folder[% endif %]\thing.py.tmpl'

def split_template_path(template):
    """Split a path into segments and perform a sanity check.  If it detects
    '..' in the path it will raise a `TemplateNotFound` error.
    """
    pieces = []
    for piece in template.split("/"):
        if (
            path.sep in piece
            or (path.altsep and path.altsep in piece)
            or piece == path.pardir
        ):
          raise TemplateNotFound(template)

E jinja2.exceptions.TemplateNotFound: [% if not py3 %]py2_folder[% endif %]\thing.py.tmpl

...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\loaders.py:32: TemplateNotFound
---------------------------- Captured stdout call -----------------------------
Something went wrong. Removing destination folder.

The usual "template not found" error.

test_copy_repo

_______________________________ test_copy_repo ________________________________

tmp_path = WindowsPath('C:/Users/user/AppData/Local/Temp/1/pytest-of-user/pytest-1/test_copy_repo0')

def test_copy_repo(tmp_path):
    copier.copy(
        "gh:copier-org/copier.git",
        tmp_path,
        vcs_ref="HEAD",
        quiet=True,
      exclude=["*", "!README.*"],
    )

tests\test_copy.py:60:


copier\main.py:150: in copy
shutil.rmtree(conf.src_path)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:500: in rmtree
return _rmtree_unsafe(path, onerror)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:390: in _rmtree_unsafe
_rmtree_unsafe(fullname, onerror)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:390: in _rmtree_unsafe
_rmtree_unsafe(fullname, onerror)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:390: in _rmtree_unsafe
_rmtree_unsafe(fullname, onerror)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:395: in _rmtree_unsafe
onerror(os.unlink, fullname, sys.exc_info())


path = 'C:\Users\user\AppData\Local\Temp\1\copier.vcs.clone.9kpeguda\.git\objects\pack'
onerror = <function rmtree..onerror at 0x000001F82B931BF8>

def _rmtree_unsafe(path, onerror):
    try:
        if os.path.islink(path):
            # symlinks to directories are forbidden, see bug #1669
            raise OSError("Cannot call rmtree on a symbolic link")
    except OSError:
        onerror(os.path.islink, path, sys.exc_info())
        # can't continue even if onerror hook returns
        return
    names = []
    try:
        names = os.listdir(path)
    except OSError:
        onerror(os.listdir, path, sys.exc_info())
    for name in names:
        fullname = os.path.join(path, name)
        try:
            mode = os.lstat(fullname).st_mode
        except OSError:
            mode = 0
        if stat.S_ISDIR(mode):
            _rmtree_unsafe(fullname, onerror)
        else:
            try:
              os.unlink(fullname)

E PermissionError: [WinError 5] Accès refusé: 'C:\Users\user\AppData\Local\Temp\1\copier.vcs.clone.9kpeguda\.git\objects\pack\pack-ad21e377bf323a731ceb92acc46d87aa4550aa15.idx'

..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:393: PermissionError

The usual permission error.

OS error at the end

____________________________ test_default_exclude _____________________________

tmp_path = WindowsPath('C:/Users/user/AppData/Local/Temp/1/pytest-of-user/pytest-1/test_default_exclude0')

def test_default_exclude(tmp_path):
  render(tmp_path)

tests\test_copy.py:66:


tests\helpers.py:26: in render
copier.copy(str(PROJECT_TEMPLATE), tmp_path, data=DATA, **kwargs)
copier\main.py:142: in copy
copy_local(conf=conf)
copier\main.py:190: in copy_local
render_file(conf, rel_path, source_path, render, must_skip)
copier\main.py:325: in render_file
content = render(src_path)
copier\tools.py:158: in call
tmpl = self.env.get_template(relpath)
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\environment.py:883: in get_template
return self._load_template(name, self.make_globals(globals))
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\environment.py:857: in _load_template
template = self.loader.load(self, name, globals)
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\loaders.py:115: in load
source, filename, uptodate = self.get_source(environment, name)
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\loaders.py:177: in get_source
pieces = split_template_path(template)


template = '[% if not py3 %]py2_folder[% endif %]\thing.py.tmpl'

def split_template_path(template):
    """Split a path into segments and perform a sanity check.  If it detects
    '..' in the path it will raise a `TemplateNotFound` error.
    """
    pieces = []
    for piece in template.split("/"):
        if (
            path.sep in piece
            or (path.altsep and path.altsep in piece)
            or piece == path.pardir
        ):
          raise TemplateNotFound(template)

E jinja2.exceptions.TemplateNotFound: [% if not py3 %]py2_folder[% endif %]\thing.py.tmpl

...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\loaders.py:32: TemplateNotFound
---------------------------- Captured stdout call -----------------------------
Something went wrong. Removing destination folder.
______________________________ test_include_file ______________________________

tmp_path = WindowsPath('C:/Users/user/AppData/Local/Temp/1/pytest-of-user/pytest-1/test_include_file0')

def test_include_file(tmp_path):
  render(tmp_path, exclude=["!.svn"])

tests\test_copy.py:71:


tests\helpers.py:26: in render
copier.copy(str(PROJECT_TEMPLATE), tmp_path, data=DATA, **kwargs)
copier\main.py:142: in copy
copy_local(conf=conf)
copier\main.py:190: in copy_local
render_file(conf, rel_path, source_path, render, must_skip)
copier\main.py:325: in render_file
content = render(src_path)
copier\tools.py:158: in call
tmpl = self.env.get_template(relpath)
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\environment.py:883: in get_template
return self._load_template(name, self.make_globals(globals))
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\environment.py:857: in _load_template
template = self.loader.load(self, name, globals)
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\loaders.py:115: in load
source, filename, uptodate = self.get_source(environment, name)
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\loaders.py:177: in get_source
pieces = split_template_path(template)


template = '[% if not py3 %]py2_folder[% endif %]\thing.py.tmpl'

def split_template_path(template):
    """Split a path into segments and perform a sanity check.  If it detects
    '..' in the path it will raise a `TemplateNotFound` error.
    """
    pieces = []
    for piece in template.split("/"):
        if (
            path.sep in piece
            or (path.altsep and path.altsep in piece)
            or piece == path.pardir
        ):
          raise TemplateNotFound(template)

E jinja2.exceptions.TemplateNotFound: [% if not py3 %]py2_folder[% endif %]\thing.py.tmpl

...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\loaders.py:32: TemplateNotFound
---------------------------- Captured stdout call -----------------------------
Something went wrong. Removing destination folder.
____________________________ test_include_pattern _____________________________

tmp_path = WindowsPath('C:/Users/user/AppData/Local/Temp/1/pytest-of-user/pytest-1/test_include_pattern0')

def test_include_pattern(tmp_path):
  render(tmp_path, exclude=["!.*"])

tests\test_copy.py:76:


tests\helpers.py:26: in render
copier.copy(str(PROJECT_TEMPLATE), tmp_path, data=DATA, **kwargs)
copier\main.py:142: in copy
copy_local(conf=conf)
copier\main.py:190: in copy_local
render_file(conf, rel_path, source_path, render, must_skip)
copier\main.py:325: in render_file
content = render(src_path)
copier\tools.py:158: in call
tmpl = self.env.get_template(relpath)
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\environment.py:883: in get_template
return self._load_template(name, self.make_globals(globals))
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\environment.py:857: in _load_template
template = self.loader.load(self, name, globals)
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\loaders.py:115: in load
source, filename, uptodate = self.get_source(environment, name)
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\loaders.py:177: in get_source
pieces = split_template_path(template)


template = '[% if not py3 %]py2_folder[% endif %]\thing.py.tmpl'

def split_template_path(template):
    """Split a path into segments and perform a sanity check.  If it detects
    '..' in the path it will raise a `TemplateNotFound` error.
    """
    pieces = []
    for piece in template.split("/"):
        if (
            path.sep in piece
            or (path.altsep and path.altsep in piece)
            or piece == path.pardir
        ):
          raise TemplateNotFound(template)

E jinja2.exceptions.TemplateNotFound: [% if not py3 %]py2_folder[% endif %]\thing.py.tmpl

...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\loaders.py:32: TemplateNotFound
---------------------------- Captured stdout call -----------------------------
Something went wrong. Removing destination folder.
______________________________ test_exclude_file ______________________________

tmp_path = WindowsPath('C:/Users/user/AppData/Local/Temp/1/pytest-of-user/pytest-1/test_exclude_file0')

def test_exclude_file(tmp_path):
  render(tmp_path, exclude=["mañana.txt"])

tests\test_copy.py:81:


tests\helpers.py:26: in render
copier.copy(str(PROJECT_TEMPLATE), tmp_path, data=DATA, **kwargs)
copier\main.py:142: in copy
copy_local(conf=conf)
copier\main.py:190: in copy_local
render_file(conf, rel_path, source_path, render, must_skip)
copier\main.py:325: in render_file
content = render(src_path)
copier\tools.py:158: in call
tmpl = self.env.get_template(relpath)
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\environment.py:883: in get_template
return self._load_template(name, self.make_globals(globals))
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\environment.py:857: in _load_template
template = self.loader.load(self, name, globals)
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\loaders.py:115: in load
source, filename, uptodate = self.get_source(environment, name)
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\loaders.py:177: in get_source
pieces = split_template_path(template)


template = '[% if not py3 %]py2_folder[% endif %]\thing.py.tmpl'

def split_template_path(template):
    """Split a path into segments and perform a sanity check.  If it detects
    '..' in the path it will raise a `TemplateNotFound` error.
    """
    pieces = []
    for piece in template.split("/"):
        if (
            path.sep in piece
            or (path.altsep and path.altsep in piece)
            or piece == path.pardir
        ):
          raise TemplateNotFound(template)

E jinja2.exceptions.TemplateNotFound: [% if not py3 %]py2_folder[% endif %]\thing.py.tmpl

...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\loaders.py:32: TemplateNotFound
---------------------------- Captured stdout call -----------------------------
Something went wrong. Removing destination folder.
_____________________________ test_config_exclude _____________________________

tmp_path = WindowsPath('C:/Users/user/AppData/Local/Temp/1/pytest-of-user/pytest-1/test_config_exclude0')
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x000001F82B933C18>

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)

tests\test_copy.py:120:


copier\main.py:142: in copy
copy_local(conf=conf)
copier\main.py:190: in copy_local
render_file(conf, rel_path, source_path, render, must_skip)
copier\main.py:325: in render_file
content = render(src_path)
copier\tools.py:158: in call
tmpl = self.env.get_template(relpath)
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\environment.py:883: in get_template
return self._load_template(name, self.make_globals(globals))
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\environment.py:857: in _load_template
template = self.loader.load(self, name, globals)
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\loaders.py:115: in load
source, filename, uptodate = self.get_source(environment, name)
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\loaders.py:177: in get_source
pieces = split_template_path(template)


template = '[% if not py3 %]py2_folder[% endif %]\thing.py.tmpl'

def split_template_path(template):
    """Split a path into segments and perform a sanity check.  If it detects
    '..' in the path it will raise a `TemplateNotFound` error.
    """
    pieces = []
    for piece in template.split("/"):
        if (
            path.sep in piece
            or (path.altsep and path.altsep in piece)
            or piece == path.pardir
        ):
          raise TemplateNotFound(template)

E jinja2.exceptions.TemplateNotFound: [% if not py3 %]py2_folder[% endif %]\thing.py.tmpl

...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\loaders.py:32: TemplateNotFound
---------------------------- Captured stdout call -----------------------------
Something went wrong. Removing destination folder.
_______________________ test_config_exclude_overridden ________________________

tmp_path = WindowsPath('C:/Users/user/AppData/Local/Temp/1/pytest-of-user/pytest-1/test_config_exclude_overridden0')

def test_config_exclude_overridden(tmp_path):
    def fake_data(*_args, **_kwargs):
        return {"_exclude": ["*.txt"]}
  copier.copy(str(PROJECT_TEMPLATE), tmp_path, data=DATA, quiet=True, exclude=[])

tests\test_copy.py:128:


copier\main.py:142: in copy
copy_local(conf=conf)
copier\main.py:190: in copy_local
render_file(conf, rel_path, source_path, render, must_skip)
copier\main.py:325: in render_file
content = render(src_path)
copier\tools.py:158: in call
tmpl = self.env.get_template(relpath)
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\environment.py:883: in get_template
return self._load_template(name, self.make_globals(globals))
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\environment.py:857: in _load_template
template = self.loader.load(self, name, globals)
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\loaders.py:115: in load
source, filename, uptodate = self.get_source(environment, name)
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\loaders.py:177: in get_source
pieces = split_template_path(template)


template = '[% if not py3 %]py2_folder[% endif %]\thing.py.tmpl'

def split_template_path(template):
    """Split a path into segments and perform a sanity check.  If it detects
    '..' in the path it will raise a `TemplateNotFound` error.
    """
    pieces = []
    for piece in template.split("/"):
        if (
            path.sep in piece
            or (path.altsep and path.altsep in piece)
            or piece == path.pardir
        ):
          raise TemplateNotFound(template)

E jinja2.exceptions.TemplateNotFound: [% if not py3 %]py2_folder[% endif %]\thing.py.tmpl

...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\loaders.py:32: TemplateNotFound
---------------------------- Captured stdout call -----------------------------
Something went wrong. Removing destination folder.
_____________________________ test_config_include _____________________________

tmp_path = WindowsPath('C:/Users/user/AppData/Local/Temp/1/pytest-of-user/pytest-1/test_config_include0')
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x000001F82BAEAD68>

def test_config_include(tmp_path, monkeypatch):
    def fake_data(*_args, **_kwargs):
        return {"_exclude": ["!.svn"]}

    monkeypatch.setattr(copier.config.factory, "load_config_data", fake_data)
  copier.copy(str(PROJECT_TEMPLATE), tmp_path, data=DATA, quiet=True)

tests\test_copy.py:137:


copier\main.py:142: in copy
copy_local(conf=conf)
copier\main.py:190: in copy_local
render_file(conf, rel_path, source_path, render, must_skip)
copier\main.py:325: in render_file
content = render(src_path)
copier\tools.py:158: in call
tmpl = self.env.get_template(relpath)
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\environment.py:883: in get_template
return self._load_template(name, self.make_globals(globals))
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\environment.py:857: in _load_template
template = self.loader.load(self, name, globals)
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\loaders.py:115: in load
source, filename, uptodate = self.get_source(environment, name)
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\loaders.py:177: in get_source
pieces = split_template_path(template)


template = '[% if not py3 %]py2_folder[% endif %]\thing.py.tmpl'

def split_template_path(template):
    """Split a path into segments and perform a sanity check.  If it detects
    '..' in the path it will raise a `TemplateNotFound` error.
    """
    pieces = []
    for piece in template.split("/"):
        if (
            path.sep in piece
            or (path.altsep and path.altsep in piece)
            or piece == path.pardir
        ):
          raise TemplateNotFound(template)

E jinja2.exceptions.TemplateNotFound: [% if not py3 %]py2_folder[% endif %]\thing.py.tmpl

...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\loaders.py:32: TemplateNotFound
---------------------------- Captured stdout call -----------------------------
Something went wrong. Removing destination folder.
______________________________ test_skip_option _______________________________

tmp_path = WindowsPath('C:/Users/user/AppData/Local/Temp/1/pytest-of-user/pytest-1/test_skip_option0')

def test_skip_option(tmp_path):
  render(tmp_path)

tests\test_copy.py:142:


tests\helpers.py:26: in render
copier.copy(str(PROJECT_TEMPLATE), tmp_path, data=DATA, **kwargs)
copier\main.py:142: in copy
copy_local(conf=conf)
copier\main.py:190: in copy_local
render_file(conf, rel_path, source_path, render, must_skip)
copier\main.py:325: in render_file
content = render(src_path)
copier\tools.py:158: in call
tmpl = self.env.get_template(relpath)
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\environment.py:883: in get_template
return self._load_template(name, self.make_globals(globals))
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\environment.py:857: in _load_template
template = self.loader.load(self, name, globals)
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\loaders.py:115: in load
source, filename, uptodate = self.get_source(environment, name)
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\loaders.py:177: in get_source
pieces = split_template_path(template)


template = '[% if not py3 %]py2_folder[% endif %]\thing.py.tmpl'

def split_template_path(template):
    """Split a path into segments and perform a sanity check.  If it detects
    '..' in the path it will raise a `TemplateNotFound` error.
    """
    pieces = []
    for piece in template.split("/"):
        if (
            path.sep in piece
            or (path.altsep and path.altsep in piece)
            or piece == path.pardir
        ):
          raise TemplateNotFound(template)

E jinja2.exceptions.TemplateNotFound: [% if not py3 %]py2_folder[% endif %]\thing.py.tmpl

...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\loaders.py:32: TemplateNotFound
---------------------------- Captured stdout call -----------------------------
Something went wrong. Removing destination folder.
______________________________ test_force_option ______________________________

tmp_path = WindowsPath('C:/Users/user/AppData/Local/Temp/1/pytest-of-user/pytest-1/test_force_option0')

def test_force_option(tmp_path):
  render(tmp_path)

tests\test_copy.py:151:


tests\helpers.py:26: in render
copier.copy(str(PROJECT_TEMPLATE), tmp_path, data=DATA, **kwargs)
copier\main.py:142: in copy
copy_local(conf=conf)
copier\main.py:190: in copy_local
render_file(conf, rel_path, source_path, render, must_skip)
copier\main.py:325: in render_file
content = render(src_path)
copier\tools.py:158: in call
tmpl = self.env.get_template(relpath)
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\environment.py:883: in get_template
return self._load_template(name, self.make_globals(globals))
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\environment.py:857: in _load_template
template = self.loader.load(self, name, globals)
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\loaders.py:115: in load
source, filename, uptodate = self.get_source(environment, name)
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\loaders.py:177: in get_source
pieces = split_template_path(template)


template = '[% if not py3 %]py2_folder[% endif %]\thing.py.tmpl'

def split_template_path(template):
    """Split a path into segments and perform a sanity check.  If it detects
    '..' in the path it will raise a `TemplateNotFound` error.
    """
    pieces = []
    for piece in template.split("/"):
        if (
            path.sep in piece
            or (path.altsep and path.altsep in piece)
            or piece == path.pardir
        ):
          raise TemplateNotFound(template)

E jinja2.exceptions.TemplateNotFound: [% if not py3 %]py2_folder[% endif %]\thing.py.tmpl

...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\loaders.py:32: TemplateNotFound
---------------------------- Captured stdout call -----------------------------
Something went wrong. Removing destination folder.
_____________________________ test_pretend_option _____________________________

tmp_path = WindowsPath('C:/Users/user/AppData/Local/Temp/1/pytest-of-user/pytest-1/test_pretend_option0')

def test_pretend_option(tmp_path):
  render(tmp_path, pretend=True)

tests\test_copy.py:160:


tests\helpers.py:26: in render
copier.copy(str(PROJECT_TEMPLATE), tmp_path, data=DATA, **kwargs)
copier\main.py:142: in copy
copy_local(conf=conf)
copier\main.py:190: in copy_local
render_file(conf, rel_path, source_path, render, must_skip)
copier\main.py:325: in render_file
content = render(src_path)
copier\tools.py:158: in call
tmpl = self.env.get_template(relpath)
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\environment.py:883: in get_template
return self._load_template(name, self.make_globals(globals))
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\environment.py:857: in _load_template
template = self.loader.load(self, name, globals)
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\loaders.py:115: in load
source, filename, uptodate = self.get_source(environment, name)
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\loaders.py:177: in get_source
pieces = split_template_path(template)


template = '[% if not py3 %]py2_folder[% endif %]\thing.py.tmpl'

def split_template_path(template):
    """Split a path into segments and perform a sanity check.  If it detects
    '..' in the path it will raise a `TemplateNotFound` error.
    """
    pieces = []
    for piece in template.split("/"):
        if (
            path.sep in piece
            or (path.altsep and path.altsep in piece)
            or piece == path.pardir
        ):
          raise TemplateNotFound(template)

E jinja2.exceptions.TemplateNotFound: [% if not py3 %]py2_folder[% endif %]\thing.py.tmpl

...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\loaders.py:32: TemplateNotFound
---------------------------- Captured stdout call -----------------------------
Something went wrong. Removing destination folder.
______________________________ test_update_tasks ______________________________

tmpdir = local('C:\Users\user\AppData\Local\Temp\1\pytest-of-user\pytest-1\test_update_tasks0')

def test_update_tasks(tmpdir):
    """Test that updating a template runs tasks from the expected version."""
    tmp_path = tmpdir / "tmp_path"
    # Copy the 1st version
    copy(
      str(REPO_BUNDLE_PATH), tmp_path, force=True, vcs_ref="v1",
    )

tests\test_demo_update_tasks.py:18:


copier\main.py:150: in copy
shutil.rmtree(conf.src_path)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:500: in rmtree
return _rmtree_unsafe(path, onerror)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:390: in _rmtree_unsafe
_rmtree_unsafe(fullname, onerror)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:390: in _rmtree_unsafe
_rmtree_unsafe(fullname, onerror)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:390: in _rmtree_unsafe
_rmtree_unsafe(fullname, onerror)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:395: in _rmtree_unsafe
onerror(os.unlink, fullname, sys.exc_info())


path = 'C:\Users\user\AppData\Local\Temp\1\copier.vcs.clone.du08s5pi\.git\objects\pack'
onerror = <function rmtree..onerror at 0x000001F82B8C1840>

def _rmtree_unsafe(path, onerror):
    try:
        if os.path.islink(path):
            # symlinks to directories are forbidden, see bug #1669
            raise OSError("Cannot call rmtree on a symbolic link")
    except OSError:
        onerror(os.path.islink, path, sys.exc_info())
        # can't continue even if onerror hook returns
        return
    names = []
    try:
        names = os.listdir(path)
    except OSError:
        onerror(os.listdir, path, sys.exc_info())
    for name in names:
        fullname = os.path.join(path, name)
        try:
            mode = os.lstat(fullname).st_mode
        except OSError:
            mode = 0
        if stat.S_ISDIR(mode):
            _rmtree_unsafe(fullname, onerror)
        else:
            try:
              os.unlink(fullname)

E PermissionError: [WinError 5] Accès refusé: 'C:\Users\user\AppData\Local\Temp\1\copier.vcs.clone.du08s5pi\.git\objects\pack\pack-70a1f508c11b752fb1f2347aa9cf3d30a0f3ae2c.idx'

..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:393: PermissionError
---------------------------- Captured stdout call -----------------------------

create  .copier-answers.yml
create  v1.txt

file only in v1

---------------------------- Captured stderr call -----------------------------

Running task 1 of 1: cat v1.txt
__________________________ test_migrations_and_tasks __________________________

src_path = 'C:\Users\user\AppData\Local\Temp\1\pytest-of-user\pytest-1\test_migrations_and_tasks0\src'
dst_path = 'C:\Users\user\AppData\Local\Temp\1\pytest-of-user\pytest-1\test_migrations_and_tasks0\tmp_path'
data = None

def copy(
    src_path: OptStr = None,
    dst_path: StrOrPath = ".",
    data: AnyByStrDict = None,
    *,
    answers_file: OptStr = None,
    exclude: OptStrSeq = None,
    skip_if_exists: OptStrSeq = None,
    tasks: OptStrSeq = None,
    envops: AnyByStrDict = None,
    extra_paths: OptStrSeq = None,
    pretend: OptBool = False,
    force: OptBool = False,
    skip: OptBool = False,
    quiet: OptBool = False,
    cleanup_on_error: OptBool = True,
    vcs_ref: OptStr = None,
    only_diff: OptBool = True,
    subdirectory: OptStr = None,
) -> None:
    """
    Uses the template in src_path to generate a new project at dst_path.

    Arguments:

    - src_path (str):
        Absolute path to the project skeleton. May be a version control system URL.
        If `None`, it will be taken from `dst_path/answers_file` or fail.

    - dst_path (str):
        Absolute path to where to render the skeleton

    - data (dict):
        Optional. Data to be passed to the templates in addtion to the user data
        from a `copier.json`.

    - answers_file (str):
        Path where to obtain the answers recorded from the last update. The path
        must be relative to `dst_path`.

    - exclude (list):
        A list of names or gitignore-style patterns matching files or folders that
        must not be copied.

    - skip_if_exists (list):
        A list of names or gitignore-style patterns matching files or folders,
         that are skipped if another with the same name already exists in the
         destination folder. (It only makes sense if you are copying to a folder
        that already exists).

    - tasks (list):
        Optional lists of commands to run in order after finishing the copy.
        Like in the templates files, you can use variables on the commands that
        will be replaced by the real values before running the command.
        If one of the commands fail, the rest of them will not run.

    - envops (dict):
        Extra options for the Jinja template environment.

    - extra_paths (list):
        Optional. Additional paths, outside the `src_path`, from where to search
        for templates. This is intended to be used with shared parent templates,
        files with macros, etc. outside the copied project skeleton.

    - pretend (bool):
        Run but do not make any changes

    - force (bool):
        Overwrite files that already exist, without asking

    - skip (bool):
        Skip files that already exist, without asking

    - quiet (bool):
        Suppress the status output

    - cleanup_on_error (bool):
        Remove the destination folder if the copy process or one of the tasks fail.

    - vcs_ref (str):
        VCS reference to checkout in the template.

    - only_diff (bool):
        Try to update only the template diff.

    - subdirectory (str):
        Specify a subdirectory to use when generating the project.
    """
    conf = make_config(**locals())
    is_update = conf.original_src_path != conf.src_path and vcs.is_git_repo_root(
        conf.src_path
    )
    do_diff_update = (
        conf.only_diff
        and is_update
        and conf.old_commit
        and vcs.is_git_repo_root(Path(conf.dst_path))
    )
    try:
        if do_diff_update:
            update_diff(conf=conf)
        else:
          copy_local(conf=conf)

copier\main.py:142:


conf = ConfigData(src_path=WindowsPath('C:/Users/user/AppData/Local/Temp/1/copier.vcs.clone.ga7flwwd'), subdirectory=None..._asking_user={}, data_from_answers_file={}, data_from_template_defaults={'_folder_name': 'tmp_path'}, _data_mutable={})

def copy_local(conf: ConfigData) -> None:

    must_filter = create_path_filter(conf.exclude)

    render = Renderer(conf)
    skip_patterns = [render.string(pattern) for pattern in conf.skip_if_exists]
    must_skip = create_path_filter(skip_patterns)

    if not conf.quiet:
        print("")  # padding space

    folder: StrOrPath
    rel_folder: StrOrPath

    src_path = conf.src_path
    if conf.subdirectory is not None:
        src_path /= conf.subdirectory

    for folder, sub_dirs, files in os.walk(src_path):
        rel_folder = str(folder).replace(str(src_path), "", 1).lstrip(os.path.sep)
        rel_folder = render.string(rel_folder)
        rel_folder = str(rel_folder).replace("." + os.path.sep, ".", 1)

        if must_filter(rel_folder):
            # Folder is excluded, so stop walking it
            sub_dirs[:] = []
            continue

        folder = Path(folder)
        rel_folder = Path(rel_folder)

        render_folder(rel_folder, conf)

        source_paths = get_source_paths(
            conf, folder, rel_folder, files, render, must_filter
        )
        for source_path, rel_path in source_paths:
            render_file(conf, rel_path, source_path, render, must_skip)

    if not conf.quiet:
        print("")  # padding space

    run_tasks(
      conf, render, [{"task": t, "extra_env": {"STAGE": "task"}} for t in conf.tasks]
    )

copier\main.py:196:


conf = ConfigData(src_path=WindowsPath('C:/Users/user/AppData/Local/Temp/1/copier.vcs.clone.ga7flwwd'), subdirectory=None..._asking_user={}, data_from_answers_file={}, data_from_template_defaults={'_folder_name': 'tmp_path'}, _data_mutable={})
render = <copier.tools.Renderer object at 0x000001F82B9BC668>
tasks = [{'extra_env': {'STAGE': 'task'}, 'task': "[[ _copier_conf.src_path / 'tasks.sh' ]] 1"}, {'extra_env': {'STAGE': 'task'}, 'task': ["[[ _copier_conf.src_path / 'tasks.sh' ]]", '2']}]

def run_tasks(conf: ConfigData, render: Renderer, tasks: Sequence[Dict]) -> None:
    for i, task in enumerate(tasks):
        task_cmd = task["task"]
        use_shell = isinstance(task_cmd, str)
        if use_shell:
            task_cmd = render.string(task_cmd)
        else:
            task_cmd = [render.string(part) for part in task_cmd]
        if not conf.quiet:
            print(
                colors.info | f" > Running task {i + 1} of {len(tasks)}: {task_cmd}",
                file=sys.stderr,
            )
        with local.cwd(conf.dst_path), local.env(**task.get("extra_env", {})):
          subprocess.run(task_cmd, shell=use_shell, check=True, env=local.env)

copier\main.py:377:


input = None, timeout = None, check = True
popenargs = (['C:\Users\user\AppData\Local\Temp\1\copier.vcs.clone.ga7flwwd\tasks.sh', '2'],)
kwargs = {'env': <plumbum.machines.local.LocalEnv object at 0x000001F82A8B5480>, 'shell': False}

def run(*popenargs, input=None, timeout=None, check=False, **kwargs):
    """Run command with arguments and return a CompletedProcess instance.

    The returned instance will have attributes args, returncode, stdout and
    stderr. By default, stdout and stderr are not captured, and those attributes
    will be None. Pass stdout=PIPE and/or stderr=PIPE in order to capture them.

    If check is True and the exit code was non-zero, it raises a
    CalledProcessError. The CalledProcessError object will have the return code
    in the returncode attribute, and output & stderr attributes if those streams
    were captured.

    If timeout is given, and the process takes too long, a TimeoutExpired
    exception will be raised.

    There is an optional argument "input", allowing you to
    pass a string to the subprocess's stdin.  If you use this argument
    you may not also use the Popen constructor's "stdin" argument, as
    it will be used internally.

    The other arguments are the same as for the Popen constructor.

    If universal_newlines=True is passed, the "input" argument must be a
    string and stdout/stderr in the returned object will be strings rather than
    bytes.
    """
    if input is not None:
        if 'stdin' in kwargs:
            raise ValueError('stdin and input arguments may not both be used.')
        kwargs['stdin'] = PIPE
  with Popen(*popenargs, **kwargs) as process:

..\AppData\Local\Programs\Python\Python36\Lib\subprocess.py:423:


self = <subprocess.Popen object at 0x000001F82BAECA20>
args = ['C:\Users\user\AppData\Local\Temp\1\copier.vcs.clone.ga7flwwd\tasks.sh', '2']
bufsize = -1, executable = None, stdin = None, stdout = None, stderr = None
preexec_fn = None, close_fds = True, shell = False, cwd = None
env = <plumbum.machines.local.LocalEnv object at 0x000001F82A8B5480>
universal_newlines = False, startupinfo = None, creationflags = 0
restore_signals = True, start_new_session = False, pass_fds = ()

def __init__(self, args, bufsize=-1, executable=None,
             stdin=None, stdout=None, stderr=None,
             preexec_fn=None, close_fds=_PLATFORM_DEFAULT_CLOSE_FDS,
             shell=False, cwd=None, env=None, universal_newlines=False,
             startupinfo=None, creationflags=0,
             restore_signals=True, start_new_session=False,
             pass_fds=(), *, encoding=None, errors=None):
    """Create new Popen instance."""
    _cleanup()
    # Held while anything is calling waitpid before returncode has been
    # updated to prevent clobbering returncode if wait() or poll() are
    # called from multiple threads at once.  After acquiring the lock,
    # code must re-check self.returncode to see if another thread just
    # finished a waitpid() call.
    self._waitpid_lock = threading.Lock()

    self._input = None
    self._communication_started = False
    if bufsize is None:
        bufsize = -1  # Restore default
    if not isinstance(bufsize, int):
        raise TypeError("bufsize must be an integer")

    if _mswindows:
        if preexec_fn is not None:
            raise ValueError("preexec_fn is not supported on Windows "
                             "platforms")
        any_stdio_set = (stdin is not None or stdout is not None or
                         stderr is not None)
        if close_fds is _PLATFORM_DEFAULT_CLOSE_FDS:
            if any_stdio_set:
                close_fds = False
            else:
                close_fds = True
        elif close_fds and any_stdio_set:
            raise ValueError(
                    "close_fds is not supported on Windows platforms"
                    " if you redirect stdin/stdout/stderr")
    else:
        # POSIX
        if close_fds is _PLATFORM_DEFAULT_CLOSE_FDS:
            close_fds = True
        if pass_fds and not close_fds:
            warnings.warn("pass_fds overriding close_fds.", RuntimeWarning)
            close_fds = True
        if startupinfo is not None:
            raise ValueError("startupinfo is only supported on Windows "
                             "platforms")
        if creationflags != 0:
            raise ValueError("creationflags is only supported on Windows "
                             "platforms")

    self.args = args
    self.stdin = None
    self.stdout = None
    self.stderr = None
    self.pid = None
    self.returncode = None
    self.universal_newlines = universal_newlines
    self.encoding = encoding
    self.errors = errors

    # Input and output objects. The general principle is like
    # this:
    #
    # Parent                   Child
    # ------                   -----
    # p2cwrite   ---stdin--->  p2cread
    # c2pread    <--stdout---  c2pwrite
    # errread    <--stderr---  errwrite
    #
    # On POSIX, the child objects are file descriptors.  On
    # Windows, these are Windows file handles.  The parent objects
    # are file descriptors on both platforms.  The parent objects
    # are -1 when not using PIPEs. The child objects are -1
    # when not redirecting.

    (p2cread, p2cwrite,
     c2pread, c2pwrite,
     errread, errwrite) = self._get_handles(stdin, stdout, stderr)

    # We wrap OS handles *before* launching the child, otherwise a
    # quickly terminating child could make our fds unwrappable
    # (see #8458).

    if _mswindows:
        if p2cwrite != -1:
            p2cwrite = msvcrt.open_osfhandle(p2cwrite.Detach(), 0)
        if c2pread != -1:
            c2pread = msvcrt.open_osfhandle(c2pread.Detach(), 0)
        if errread != -1:
            errread = msvcrt.open_osfhandle(errread.Detach(), 0)

    text_mode = encoding or errors or universal_newlines

    self._closed_child_pipe_fds = False

    try:
        if p2cwrite != -1:
            self.stdin = io.open(p2cwrite, 'wb', bufsize)
            if text_mode:
                self.stdin = io.TextIOWrapper(self.stdin, write_through=True,
                        line_buffering=(bufsize == 1),
                        encoding=encoding, errors=errors)
        if c2pread != -1:
            self.stdout = io.open(c2pread, 'rb', bufsize)
            if text_mode:
                self.stdout = io.TextIOWrapper(self.stdout,
                        encoding=encoding, errors=errors)
        if errread != -1:
            self.stderr = io.open(errread, 'rb', bufsize)
            if text_mode:
                self.stderr = io.TextIOWrapper(self.stderr,
                        encoding=encoding, errors=errors)

        self._execute_child(args, executable, preexec_fn, close_fds,
                            pass_fds, cwd, env,
                            startupinfo, creationflags, shell,
                            p2cread, p2cwrite,
                            c2pread, c2pwrite,
                            errread, errwrite,
                          restore_signals, start_new_session)

..\AppData\Local\Programs\Python\Python36\Lib\subprocess.py:729:


self = <subprocess.Popen object at 0x000001F82BAECA20>
args = 'C:\Users\user\AppData\Local\Temp\1\copier.vcs.clone.ga7flwwd\tasks.sh 2'
executable = None, preexec_fn = None, close_fds = True, pass_fds = ()
cwd = None, env = <plumbum.machines.local.LocalEnv object at 0x000001F82A8B5480>
startupinfo = <subprocess.STARTUPINFO object at 0x000001F82B9BC208>
creationflags = 0, shell = False, p2cread = -1, p2cwrite = -1, c2pread = -1
c2pwrite = -1, errread = -1, errwrite = -1, unused_restore_signals = True
unused_start_new_session = False

def _execute_child(self, args, executable, preexec_fn, close_fds,
                   pass_fds, cwd, env,
                   startupinfo, creationflags, shell,
                   p2cread, p2cwrite,
                   c2pread, c2pwrite,
                   errread, errwrite,
                   unused_restore_signals, unused_start_new_session):
    """Execute program (MS Windows version)"""

    assert not pass_fds, "pass_fds not supported on Windows."

    if not isinstance(args, str):
        args = list2cmdline(args)

    # Process startup details
    if startupinfo is None:
        startupinfo = STARTUPINFO()
    if -1 not in (p2cread, c2pwrite, errwrite):
        startupinfo.dwFlags |= _winapi.STARTF_USESTDHANDLES
        startupinfo.hStdInput = p2cread
        startupinfo.hStdOutput = c2pwrite
        startupinfo.hStdError = errwrite

    if shell:
        startupinfo.dwFlags |= _winapi.STARTF_USESHOWWINDOW
        startupinfo.wShowWindow = _winapi.SW_HIDE
        comspec = os.environ.get("COMSPEC", "cmd.exe")
        args = '{} /c "{}"'.format (comspec, args)

    # Start the process
    try:
        hp, ht, pid, tid = _winapi.CreateProcess(executable, args,
                                 # no special security
                                 None, None,
                                 int(not close_fds),
                                 creationflags,
                                 env,
                                 os.fspath(cwd) if cwd is not None else None,
                               startupinfo)

E OSError: [WinError 193] %1 n’est pas une application Win32 valide

..\AppData\Local\Programs\Python\Python36\Lib\subprocess.py:1017: OSError

another OS Error

During handling of the above exception, another exception occurred:

tmpdir = local('C:\Users\user\AppData\Local\Temp\1\pytest-of-user\pytest-1\test_migrations_and_tasks0')

def test_migrations_and_tasks(tmpdir: py.path.local):
    """Check migrations and tasks are run properly."""
    # Convert demo_migrations in a git repository with 2 versions
    git_src, tmp_path = tmpdir / "src", tmpdir / "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(tmp_path), vcs_ref="v1.0.0")

tests\test_migrations.py:32:


copier\main.py:150: in copy
shutil.rmtree(conf.src_path)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:500: in rmtree
return _rmtree_unsafe(path, onerror)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:390: in _rmtree_unsafe
_rmtree_unsafe(fullname, onerror)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:390: in _rmtree_unsafe
_rmtree_unsafe(fullname, onerror)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:390: in _rmtree_unsafe
_rmtree_unsafe(fullname, onerror)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:395: in _rmtree_unsafe
onerror(os.unlink, fullname, sys.exc_info())


path = 'C:\Users\user\AppData\Local\Temp\1\copier.vcs.clone.ga7flwwd\.git\objects\0e'
onerror = <function rmtree..onerror at 0x000001F82B945488>

def _rmtree_unsafe(path, onerror):
    try:
        if os.path.islink(path):
            # symlinks to directories are forbidden, see bug #1669
            raise OSError("Cannot call rmtree on a symbolic link")
    except OSError:
        onerror(os.path.islink, path, sys.exc_info())
        # can't continue even if onerror hook returns
        return
    names = []
    try:
        names = os.listdir(path)
    except OSError:
        onerror(os.listdir, path, sys.exc_info())
    for name in names:
        fullname = os.path.join(path, name)
        try:
            mode = os.lstat(fullname).st_mode
        except OSError:
            mode = 0
        if stat.S_ISDIR(mode):
            _rmtree_unsafe(fullname, onerror)
        else:
            try:
              os.unlink(fullname)

E PermissionError: [WinError 5] Accès refusé: 'C:\Users\user\AppData\Local\Temp\1\copier.vcs.clone.ga7flwwd\.git\objects\0e\92118043fe6fb317c5d9a76df346fa8d39ca74'

..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:393: PermissionError
---------------------------- Captured stdout call -----------------------------

create  copier.yaml
create  delete-in-migration-v2.txt
create  delete-in-tasks.txt
create  .copier-answers.yml

Something went wrong. Removing destination folder.
---------------------------- Captured stderr call -----------------------------

Running task 1 of 2: C:\Users\user\AppData\Local\Temp\1\copier.vcs.clone.ga7flwwd\tasks.sh 1
Running task 2 of 2: ['C:\Users\user\AppData\Local\Temp\1\copier.vcs.clone.ga7flwwd\tasks.sh', '2']
_____________________ test_pre_migration_modifies_answers _____________________

tmp_path_factory = TempPathFactory(_given_basetemp=None, _trace=<pluggy._tracing.TagTracerSub object at 0x000001F82A681F60>, _basetemp=WindowsPath('C:/Users/user/AppData/Local/Temp/1/pytest-of-user/pytest-1'))

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.yaml
    with local.cwd(template):
        build_file_tree(
            {
                "[[ _copier_conf.answers_file ]].tmpl": "[[ _copier_answers|to_nice_yaml ]]",
                "copier.yml": """\
                    best_song: la vie en rose
                    """,
                "songs.yaml.tmpl": "- [[ best_song ]]",
            }
        )
        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)

tests\test_migrations.py:84:


copier\main.py:150: in copy
shutil.rmtree(conf.src_path)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:500: in rmtree
return _rmtree_unsafe(path, onerror)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:390: in _rmtree_unsafe
_rmtree_unsafe(fullname, onerror)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:390: in _rmtree_unsafe
_rmtree_unsafe(fullname, onerror)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:390: in _rmtree_unsafe
_rmtree_unsafe(fullname, onerror)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:395: in _rmtree_unsafe
onerror(os.unlink, fullname, sys.exc_info())


path = 'C:\Users\user\AppData\Local\Temp\1\copier.vcs.clone.ui5mgkir\.git\objects\11'
onerror = <function rmtree..onerror at 0x000001F82B988E18>

def _rmtree_unsafe(path, onerror):
    try:
        if os.path.islink(path):
            # symlinks to directories are forbidden, see bug #1669
            raise OSError("Cannot call rmtree on a symbolic link")
    except OSError:
        onerror(os.path.islink, path, sys.exc_info())
        # can't continue even if onerror hook returns
        return
    names = []
    try:
        names = os.listdir(path)
    except OSError:
        onerror(os.listdir, path, sys.exc_info())
    for name in names:
        fullname = os.path.join(path, name)
        try:
            mode = os.lstat(fullname).st_mode
        except OSError:
            mode = 0
        if stat.S_ISDIR(mode):
            _rmtree_unsafe(fullname, onerror)
        else:
            try:
              os.unlink(fullname)

E PermissionError: [WinError 5] Accès refusé: 'C:\Users\user\AppData\Local\Temp\1\copier.vcs.clone.ui5mgkir\.git\objects\11\975b5af23cdaf9c47adbc1c77929d2434998ce'

..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:393: PermissionError
---------------------------- Captured stdout call -----------------------------

create  songs.yaml
create  .copier-answers.yml

_________________________________ test_output _________________________________

capsys = <_pytest.capture.CaptureFixture object at 0x000001F82B9B25C0>
tmp_path = WindowsPath('C:/Users/user/AppData/Local/Temp/1/pytest-of-user/pytest-1/test_output0')

def test_output(capsys, tmp_path):
  render(tmp_path, quiet=False)

tests\test_output.py:7:


tests\helpers.py:26: in render
copier.copy(str(PROJECT_TEMPLATE), tmp_path, data=DATA, **kwargs)
copier\main.py:142: in copy
copy_local(conf=conf)
copier\main.py:190: in copy_local
render_file(conf, rel_path, source_path, render, must_skip)
copier\main.py:325: in render_file
content = render(src_path)
copier\tools.py:158: in call
tmpl = self.env.get_template(relpath)
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\environment.py:883: in get_template
return self._load_template(name, self.make_globals(globals))
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\environment.py:857: in _load_template
template = self.loader.load(self, name, globals)
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\loaders.py:115: in load
source, filename, uptodate = self.get_source(environment, name)
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\loaders.py:177: in get_source
pieces = split_template_path(template)


template = '[% if not py3 %]py2_folder[% endif %]\thing.py.tmpl'

def split_template_path(template):
    """Split a path into segments and perform a sanity check.  If it detects
    '..' in the path it will raise a `TemplateNotFound` error.
    """
    pieces = []
    for piece in template.split("/"):
        if (
            path.sep in piece
            or (path.altsep and path.altsep in piece)
            or piece == path.pardir
        ):
          raise TemplateNotFound(template)

E jinja2.exceptions.TemplateNotFound: [% if not py3 %]py2_folder[% endif %]\thing.py.tmpl

...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\loaders.py:32: TemplateNotFound
---------------------------- Captured stdout call -----------------------------

create  .gitignore
create  aaaa.txt
create  config.py
create  pyproject.toml
create  README.txt
create  py3_only.py
create  awesome.txt
create  doc\
create  doc\manana.txt
create  doc\mañana.txt
create  doc\mañana.txt
create  doc\images\
create  doc\images\nslogo.gif

Something went wrong. Removing destination folder.
_____________________________ test_output_pretend _____________________________

capsys = <_pytest.capture.CaptureFixture object at 0x000001F82B9B0E48>
tmp_path = WindowsPath('C:/Users/user/AppData/Local/Temp/1/pytest-of-user/pytest-1/test_output_pretend0')

def test_output_pretend(capsys, tmp_path):
  render(tmp_path, quiet=False, pretend=True)

tests\test_output.py:15:


tests\helpers.py:26: in render
copier.copy(str(PROJECT_TEMPLATE), tmp_path, data=DATA, **kwargs)
copier\main.py:142: in copy
copy_local(conf=conf)
copier\main.py:190: in copy_local
render_file(conf, rel_path, source_path, render, must_skip)
copier\main.py:325: in render_file
content = render(src_path)
copier\tools.py:158: in call
tmpl = self.env.get_template(relpath)
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\environment.py:883: in get_template
return self._load_template(name, self.make_globals(globals))
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\environment.py:857: in _load_template
template = self.loader.load(self, name, globals)
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\loaders.py:115: in load
source, filename, uptodate = self.get_source(environment, name)
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\loaders.py:177: in get_source
pieces = split_template_path(template)


template = '[% if not py3 %]py2_folder[% endif %]\thing.py.tmpl'

def split_template_path(template):
    """Split a path into segments and perform a sanity check.  If it detects
    '..' in the path it will raise a `TemplateNotFound` error.
    """
    pieces = []
    for piece in template.split("/"):
        if (
            path.sep in piece
            or (path.altsep and path.altsep in piece)
            or piece == path.pardir
        ):
          raise TemplateNotFound(template)

E jinja2.exceptions.TemplateNotFound: [% if not py3 %]py2_folder[% endif %]\thing.py.tmpl

...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\loaders.py:32: TemplateNotFound
---------------------------- Captured stdout call -----------------------------

create  .gitignore
create  aaaa.txt
create  config.py
create  pyproject.toml
create  README.txt
create  py3_only.py
create  awesome.txt
create  doc\
create  doc\manana.txt
create  doc\mañana.txt
create  doc\mañana.txt
create  doc\images\
create  doc\images\nslogo.gif

Something went wrong. Removing destination folder.
______________________________ test_output_force ______________________________

capsys = <_pytest.capture.CaptureFixture object at 0x000001F82B8F7B00>
tmp_path = WindowsPath('C:/Users/user/AppData/Local/Temp/1/pytest-of-user/pytest-1/test_output_force0')

def test_output_force(capsys, tmp_path):
  render(tmp_path)

tests\test_output.py:23:


tests\helpers.py:26: in render
copier.copy(str(PROJECT_TEMPLATE), tmp_path, data=DATA, **kwargs)
copier\main.py:142: in copy
copy_local(conf=conf)
copier\main.py:190: in copy_local
render_file(conf, rel_path, source_path, render, must_skip)
copier\main.py:325: in render_file
content = render(src_path)
copier\tools.py:158: in call
tmpl = self.env.get_template(relpath)
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\environment.py:883: in get_template
return self._load_template(name, self.make_globals(globals))
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\environment.py:857: in _load_template
template = self.loader.load(self, name, globals)
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\loaders.py:115: in load
source, filename, uptodate = self.get_source(environment, name)
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\loaders.py:177: in get_source
pieces = split_template_path(template)


template = '[% if not py3 %]py2_folder[% endif %]\thing.py.tmpl'

def split_template_path(template):
    """Split a path into segments and perform a sanity check.  If it detects
    '..' in the path it will raise a `TemplateNotFound` error.
    """
    pieces = []
    for piece in template.split("/"):
        if (
            path.sep in piece
            or (path.altsep and path.altsep in piece)
            or piece == path.pardir
        ):
          raise TemplateNotFound(template)

E jinja2.exceptions.TemplateNotFound: [% if not py3 %]py2_folder[% endif %]\thing.py.tmpl

...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\loaders.py:32: TemplateNotFound
---------------------------- Captured stdout call -----------------------------
Something went wrong. Removing destination folder.
______________________________ test_output_skip _______________________________

capsys = <_pytest.capture.CaptureFixture object at 0x000001F82B8FED30>
tmp_path = WindowsPath('C:/Users/user/AppData/Local/Temp/1/pytest-of-user/pytest-1/test_output_skip0')

def test_output_skip(capsys, tmp_path):
  render(tmp_path)

tests\test_output.py:34:


tests\helpers.py:26: in render
copier.copy(str(PROJECT_TEMPLATE), tmp_path, data=DATA, **kwargs)
copier\main.py:142: in copy
copy_local(conf=conf)
copier\main.py:190: in copy_local
render_file(conf, rel_path, source_path, render, must_skip)
copier\main.py:325: in render_file
content = render(src_path)
copier\tools.py:158: in call
tmpl = self.env.get_template(relpath)
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\environment.py:883: in get_template
return self._load_template(name, self.make_globals(globals))
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\environment.py:857: in _load_template
template = self.loader.load(self, name, globals)
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\loaders.py:115: in load
source, filename, uptodate = self.get_source(environment, name)
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\loaders.py:177: in get_source
pieces = split_template_path(template)


template = '[% if not py3 %]py2_folder[% endif %]\thing.py.tmpl'

def split_template_path(template):
    """Split a path into segments and perform a sanity check.  If it detects
    '..' in the path it will raise a `TemplateNotFound` error.
    """
    pieces = []
    for piece in template.split("/"):
        if (
            path.sep in piece
            or (path.altsep and path.altsep in piece)
            or piece == path.pardir
        ):
          raise TemplateNotFound(template)

E jinja2.exceptions.TemplateNotFound: [% if not py3 %]py2_folder[% endif %]\thing.py.tmpl

...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\loaders.py:32: TemplateNotFound
---------------------------- Captured stdout call -----------------------------
Something went wrong. Removing destination folder.
______________________________ test_output_quiet ______________________________

capsys = <_pytest.capture.CaptureFixture object at 0x000001F82BAEA978>
tmp_path = WindowsPath('C:/Users/user/AppData/Local/Temp/1/pytest-of-user/pytest-1/test_output_quiet0')

def test_output_quiet(capsys, tmp_path):
  render(tmp_path, quiet=True)

tests\test_output.py:45:


tests\helpers.py:26: in render
copier.copy(str(PROJECT_TEMPLATE), tmp_path, data=DATA, **kwargs)
copier\main.py:142: in copy
copy_local(conf=conf)
copier\main.py:190: in copy_local
render_file(conf, rel_path, source_path, render, must_skip)
copier\main.py:325: in render_file
content = render(src_path)
copier\tools.py:158: in call
tmpl = self.env.get_template(relpath)
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\environment.py:883: in get_template
return self._load_template(name, self.make_globals(globals))
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\environment.py:857: in _load_template
template = self.loader.load(self, name, globals)
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\loaders.py:115: in load
source, filename, uptodate = self.get_source(environment, name)
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\loaders.py:177: in get_source
pieces = split_template_path(template)


template = '[% if not py3 %]py2_folder[% endif %]\thing.py.tmpl'

def split_template_path(template):
    """Split a path into segments and perform a sanity check.  If it detects
    '..' in the path it will raise a `TemplateNotFound` error.
    """
    pieces = []
    for piece in template.split("/"):
        if (
            path.sep in piece
            or (path.altsep and path.altsep in piece)
            or piece == path.pardir
        ):
          raise TemplateNotFound(template)

E jinja2.exceptions.TemplateNotFound: [% if not py3 %]py2_folder[% endif %]\thing.py.tmpl

...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\loaders.py:32: TemplateNotFound
---------------------------- Captured stdout call -----------------------------
Something went wrong. Removing destination folder.
_____________________ test_copy_default_advertised[name0] _____________________

tmp_path_factory = TempPathFactory(_given_basetemp=None, _trace=<pluggy._tracing.TagTracerSub object at 0x000001F82A681F60>, _basetemp=WindowsPath('C:/Users/user/AppData/Local/Temp/1/pytest-of-user/pytest-1'))
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x000001F82BB2AF60>
capsys = <_pytest.capture.CaptureFixture object at 0x000001F82BB2A7F0>
name = 'Mario'

@pytest.mark.parametrize("name", [DEFAULT, None, "Luigi"])
def test_copy_default_advertised(tmp_path_factory, monkeypatch, capsys, name):
    """Test that the questions for the user are OK"""
    monkeypatch.setattr("sys.stdin", StringIO("\n" * 3))
    template, subproject = (
        tmp_path_factory.mktemp("template"),
        tmp_path_factory.mktemp("subproject"),
    )
    with local.cwd(template):
        build_file_tree(MARIO_TREE)
        git("init")
        git("add", ".")
        git("commit", "-m", "v1")
        git("tag", "v1")
        git("commit", "--allow-empty", "-m", "v2")
        git("tag", "v2")
    with local.cwd(subproject):
        # Copy the v1 template
        kwargs = {}
        if name is not DEFAULT:
            kwargs["data"] = {"your_name": name}
        else:
            name = "Mario"  # Default in the template
      copy(str(template), ".", vcs_ref="v1", **kwargs)

tests\test_prompt.py:59:


copier\main.py:150: in copy
shutil.rmtree(conf.src_path)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:500: in rmtree
return _rmtree_unsafe(path, onerror)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:390: in _rmtree_unsafe
_rmtree_unsafe(fullname, onerror)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:390: in _rmtree_unsafe
_rmtree_unsafe(fullname, onerror)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:390: in _rmtree_unsafe
_rmtree_unsafe(fullname, onerror)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:395: in _rmtree_unsafe
onerror(os.unlink, fullname, sys.exc_info())


path = 'C:\Users\user\AppData\Local\Temp\1\copier.vcs.clone.u4hzdiaw\.git\objects\32'
onerror = <function rmtree..onerror at 0x000001F82BB2CF28>

def _rmtree_unsafe(path, onerror):
    try:
        if os.path.islink(path):
            # symlinks to directories are forbidden, see bug #1669
            raise OSError("Cannot call rmtree on a symbolic link")
    except OSError:
        onerror(os.path.islink, path, sys.exc_info())
        # can't continue even if onerror hook returns
        return
    names = []
    try:
        names = os.listdir(path)
    except OSError:
        onerror(os.listdir, path, sys.exc_info())
    for name in names:
        fullname = os.path.join(path, name)
        try:
            mode = os.lstat(fullname).st_mode
        except OSError:
            mode = 0
        if stat.S_ISDIR(mode):
            _rmtree_unsafe(fullname, onerror)
        else:
            try:
              os.unlink(fullname)

E PermissionError: [WinError 5] Accès refusé: 'C:\Users\user\AppData\Local\Temp\1\copier.vcs.clone.u4hzdiaw\.git\objects\32\2718f759cc66a0be249ce794ce31a802054eef'

..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:393: PermissionError
---------------------------- Captured stdout call -----------------------------

in_love? Format: bool
🎤? [Y/n]
If you have a name, tell me now.
your_name? Format: str
🎤 [Mario]:
Secret enemy name
your_enemy? Format: str
🕵️ [Bowser]:
what_enemy_does? Format: str
🎤 [Bowser hates Mario]:
create .copier-answers.yml

_____________________ test_copy_default_advertised[None] ______________________

tmp_path_factory = TempPathFactory(_given_basetemp=None, _trace=<pluggy._tracing.TagTracerSub object at 0x000001F82A681F60>, _basetemp=WindowsPath('C:/Users/user/AppData/Local/Temp/1/pytest-of-user/pytest-1'))
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x000001F82B8A9128>
capsys = <_pytest.capture.CaptureFixture object at 0x000001F82B8A9780>
name = None

@pytest.mark.parametrize("name", [DEFAULT, None, "Luigi"])
def test_copy_default_advertised(tmp_path_factory, monkeypatch, capsys, name):
    """Test that the questions for the user are OK"""
    monkeypatch.setattr("sys.stdin", StringIO("\n" * 3))
    template, subproject = (
        tmp_path_factory.mktemp("template"),
        tmp_path_factory.mktemp("subproject"),
    )
    with local.cwd(template):
        build_file_tree(MARIO_TREE)
        git("init")
        git("add", ".")
        git("commit", "-m", "v1")
        git("tag", "v1")
        git("commit", "--allow-empty", "-m", "v2")
        git("tag", "v2")
    with local.cwd(subproject):
        # Copy the v1 template
        kwargs = {}
        if name is not DEFAULT:
            kwargs["data"] = {"your_name": name}
        else:
            name = "Mario"  # Default in the template
      copy(str(template), ".", vcs_ref="v1", **kwargs)

tests\test_prompt.py:59:


copier\main.py:150: in copy
shutil.rmtree(conf.src_path)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:500: in rmtree
return _rmtree_unsafe(path, onerror)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:390: in _rmtree_unsafe
_rmtree_unsafe(fullname, onerror)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:390: in _rmtree_unsafe
_rmtree_unsafe(fullname, onerror)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:390: in _rmtree_unsafe
_rmtree_unsafe(fullname, onerror)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:395: in _rmtree_unsafe
onerror(os.unlink, fullname, sys.exc_info())


path = 'C:\Users\user\AppData\Local\Temp\1\copier.vcs.clone.2chqf6ck\.git\objects\32'
onerror = <function rmtree..onerror at 0x000001F82B96BF28>

def _rmtree_unsafe(path, onerror):
    try:
        if os.path.islink(path):
            # symlinks to directories are forbidden, see bug #1669
            raise OSError("Cannot call rmtree on a symbolic link")
    except OSError:
        onerror(os.path.islink, path, sys.exc_info())
        # can't continue even if onerror hook returns
        return
    names = []
    try:
        names = os.listdir(path)
    except OSError:
        onerror(os.listdir, path, sys.exc_info())
    for name in names:
        fullname = os.path.join(path, name)
        try:
            mode = os.lstat(fullname).st_mode
        except OSError:
            mode = 0
        if stat.S_ISDIR(mode):
            _rmtree_unsafe(fullname, onerror)
        else:
            try:
              os.unlink(fullname)

E PermissionError: [WinError 5] Accès refusé: 'C:\Users\user\AppData\Local\Temp\1\copier.vcs.clone.2chqf6ck\.git\objects\32\2718f759cc66a0be249ce794ce31a802054eef'

..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:393: PermissionError
---------------------------- Captured stdout call -----------------------------

in_love? Format: bool
🎤? [Y/n]
Secret enemy name
your_enemy? Format: str
🕵️ [Bowser]:
what_enemy_does? Format: str
🎤 [Bowser hates None]:
create .copier-answers.yml

_____________________ test_copy_default_advertised[Luigi] _____________________

tmp_path_factory = TempPathFactory(_given_basetemp=None, _trace=<pluggy._tracing.TagTracerSub object at 0x000001F82A681F60>, _basetemp=WindowsPath('C:/Users/user/AppData/Local/Temp/1/pytest-of-user/pytest-1'))
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x000001F82B8E4BE0>
capsys = <_pytest.capture.CaptureFixture object at 0x000001F82B8E4CC0>
name = 'Luigi'

@pytest.mark.parametrize("name", [DEFAULT, None, "Luigi"])
def test_copy_default_advertised(tmp_path_factory, monkeypatch, capsys, name):
    """Test that the questions for the user are OK"""
    monkeypatch.setattr("sys.stdin", StringIO("\n" * 3))
    template, subproject = (
        tmp_path_factory.mktemp("template"),
        tmp_path_factory.mktemp("subproject"),
    )
    with local.cwd(template):
        build_file_tree(MARIO_TREE)
        git("init")
        git("add", ".")
        git("commit", "-m", "v1")
        git("tag", "v1")
        git("commit", "--allow-empty", "-m", "v2")
        git("tag", "v2")
    with local.cwd(subproject):
        # Copy the v1 template
        kwargs = {}
        if name is not DEFAULT:
            kwargs["data"] = {"your_name": name}
        else:
            name = "Mario"  # Default in the template
      copy(str(template), ".", vcs_ref="v1", **kwargs)

tests\test_prompt.py:59:


copier\main.py:150: in copy
shutil.rmtree(conf.src_path)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:500: in rmtree
return _rmtree_unsafe(path, onerror)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:390: in _rmtree_unsafe
_rmtree_unsafe(fullname, onerror)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:390: in _rmtree_unsafe
_rmtree_unsafe(fullname, onerror)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:390: in _rmtree_unsafe
_rmtree_unsafe(fullname, onerror)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:395: in _rmtree_unsafe
onerror(os.unlink, fullname, sys.exc_info())


path = 'C:\Users\user\AppData\Local\Temp\1\copier.vcs.clone.r7ky45wz\.git\objects\00'
onerror = <function rmtree..onerror at 0x000001F82BB2CB70>

def _rmtree_unsafe(path, onerror):
    try:
        if os.path.islink(path):
            # symlinks to directories are forbidden, see bug #1669
            raise OSError("Cannot call rmtree on a symbolic link")
    except OSError:
        onerror(os.path.islink, path, sys.exc_info())
        # can't continue even if onerror hook returns
        return
    names = []
    try:
        names = os.listdir(path)
    except OSError:
        onerror(os.listdir, path, sys.exc_info())
    for name in names:
        fullname = os.path.join(path, name)
        try:
            mode = os.lstat(fullname).st_mode
        except OSError:
            mode = 0
        if stat.S_ISDIR(mode):
            _rmtree_unsafe(fullname, onerror)
        else:
            try:
              os.unlink(fullname)

E PermissionError: [WinError 5] Accès refusé: 'C:\Users\user\AppData\Local\Temp\1\copier.vcs.clone.r7ky45wz\.git\objects\00\0f3e3caac0656ae992210f0a5d3586c2f90cc3'

..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:393: PermissionError
---------------------------- Captured stdout call -----------------------------

in_love? Format: bool
🎤? [Y/n]
Secret enemy name
your_enemy? Format: str
🕵️ [Bowser]:
what_enemy_does? Format: str
🎤 [Bowser hates Luigi]:
create .copier-answers.yml

__________________________ test_update_subdirectory ___________________________

tmp_path = WindowsPath('C:/Users/user/AppData/Local/Temp/1/pytest-of-user/pytest-1/test_update_subdirectory0')

def test_update_subdirectory(tmp_path):
    copier.copy("./tests/demo_subdirectory", tmp_path, force=True)

    with local.cwd(tmp_path):
        git_init()

    conf = make_config("./tests/demo_subdirectory", str(tmp_path), force=True)
  update_diff(conf)

tests\test_subdirectory.py:40:


copier\main.py:244: in update_diff
diff = diff_cmd("--inter-hunk-context=0")
..\AppData\Local\Programs\Python\Python36\Lib\tempfile.py:809: in exit
self.cleanup()
..\AppData\Local\Programs\Python\Python36\Lib\tempfile.py:813: in cleanup
_shutil.rmtree(self.name)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:500: in rmtree
return _rmtree_unsafe(path, onerror)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:390: in _rmtree_unsafe
_rmtree_unsafe(fullname, onerror)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:390: in _rmtree_unsafe
_rmtree_unsafe(fullname, onerror)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:390: in _rmtree_unsafe
_rmtree_unsafe(fullname, onerror)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:395: in _rmtree_unsafe
onerror(os.unlink, fullname, sys.exc_info())


path = 'C:\Users\user\AppData\Local\Temp\1\copier.main.update_diff.3se06lhi\.git\objects\2b'
onerror = <function rmtree..onerror at 0x000001F82B7F0F28>

def _rmtree_unsafe(path, onerror):
    try:
        if os.path.islink(path):
            # symlinks to directories are forbidden, see bug #1669
            raise OSError("Cannot call rmtree on a symbolic link")
    except OSError:
        onerror(os.path.islink, path, sys.exc_info())
        # can't continue even if onerror hook returns
        return
    names = []
    try:
        names = os.listdir(path)
    except OSError:
        onerror(os.listdir, path, sys.exc_info())
    for name in names:
        fullname = os.path.join(path, name)
        try:
            mode = os.lstat(fullname).st_mode
        except OSError:
            mode = 0
        if stat.S_ISDIR(mode):
            _rmtree_unsafe(fullname, onerror)
        else:
            try:
              os.unlink(fullname)

E PermissionError: [WinError 5] Accès refusé: 'C:\Users\user\AppData\Local\Temp\1\copier.main.update_diff.3se06lhi\.git\objects\2b\d7d826946c28fc7c10e4e9d8ac142f595020cb'

..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:393: PermissionError
---------------------------- Captured stdout call -----------------------------

create  conf_readme.md

_____________________ test_new_version_uses_subdirectory ______________________

tmp_path_factory = TempPathFactory(_given_basetemp=None, _trace=<pluggy._tracing.TagTracerSub object at 0x000001F82A681F60>, _basetemp=WindowsPath('C:/Users/user/AppData/Local/Temp/1/pytest-of-user/pytest-1'))

def test_new_version_uses_subdirectory(tmp_path_factory):
    # 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?
    template_path = tmp_path_factory.mktemp("subdirectory_template")
    project_path = tmp_path_factory.mktemp("subdirectory_project")

    # First, create the template with an initial README
    with local.cwd(template_path):
        with open("README.md", "w") as fd:
            fd.write("upstream version 1\n")

        with open("[[_copier_conf.answers_file]].tmpl", "w") as fd:
            fd.write("[[_copier_answers|to_nice_yaml]]\n")

        git_init("hello template")
        git("tag", "v1")

    # Generate the project a first time, assert the README exists
  copier.copy(str(template_path), project_path, force=True)

tests\test_subdirectory.py:66:


copier\main.py:150: in copy
shutil.rmtree(conf.src_path)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:500: in rmtree
return _rmtree_unsafe(path, onerror)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:390: in _rmtree_unsafe
_rmtree_unsafe(fullname, onerror)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:390: in _rmtree_unsafe
_rmtree_unsafe(fullname, onerror)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:390: in _rmtree_unsafe
_rmtree_unsafe(fullname, onerror)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:395: in _rmtree_unsafe
onerror(os.unlink, fullname, sys.exc_info())


path = 'C:\Users\user\AppData\Local\Temp\1\copier.vcs.clone.v2gpta5f\.git\objects\80'
onerror = <function rmtree..onerror at 0x000001F82B945158>

def _rmtree_unsafe(path, onerror):
    try:
        if os.path.islink(path):
            # symlinks to directories are forbidden, see bug #1669
            raise OSError("Cannot call rmtree on a symbolic link")
    except OSError:
        onerror(os.path.islink, path, sys.exc_info())
        # can't continue even if onerror hook returns
        return
    names = []
    try:
        names = os.listdir(path)
    except OSError:
        onerror(os.listdir, path, sys.exc_info())
    for name in names:
        fullname = os.path.join(path, name)
        try:
            mode = os.lstat(fullname).st_mode
        except OSError:
            mode = 0
        if stat.S_ISDIR(mode):
            _rmtree_unsafe(fullname, onerror)
        else:
            try:
              os.unlink(fullname)

E PermissionError: [WinError 5] Accès refusé: 'C:\Users\user\AppData\Local\Temp\1\copier.vcs.clone.v2gpta5f\.git\objects\80\4d4137a341445e13e423dd7029683315068d6b'

..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:393: PermissionError
---------------------------- Captured stdout call -----------------------------

create  README.md
create  .copier-answers.yml

____________________ test_new_version_changes_subdirectory ____________________

src_path = 'C:\Users\user\AppData\Local\Temp\1\pytest-of-user\pytest-1\subdirectory_template1'
dst_path = WindowsPath('C:/Users/user/AppData/Local/Temp/1/pytest-of-user/pytest-1/subdirectory_project1')
data = None

def copy(
    src_path: OptStr = None,
    dst_path: StrOrPath = ".",
    data: AnyByStrDict = None,
    *,
    answers_file: OptStr = None,
    exclude: OptStrSeq = None,
    skip_if_exists: OptStrSeq = None,
    tasks: OptStrSeq = None,
    envops: AnyByStrDict = None,
    extra_paths: OptStrSeq = None,
    pretend: OptBool = False,
    force: OptBool = False,
    skip: OptBool = False,
    quiet: OptBool = False,
    cleanup_on_error: OptBool = True,
    vcs_ref: OptStr = None,
    only_diff: OptBool = True,
    subdirectory: OptStr = None,
) -> None:
    """
    Uses the template in src_path to generate a new project at dst_path.

    Arguments:

    - src_path (str):
        Absolute path to the project skeleton. May be a version control system URL.
        If `None`, it will be taken from `dst_path/answers_file` or fail.

    - dst_path (str):
        Absolute path to where to render the skeleton

    - data (dict):
        Optional. Data to be passed to the templates in addtion to the user data
        from a `copier.json`.

    - answers_file (str):
        Path where to obtain the answers recorded from the last update. The path
        must be relative to `dst_path`.

    - exclude (list):
        A list of names or gitignore-style patterns matching files or folders that
        must not be copied.

    - skip_if_exists (list):
        A list of names or gitignore-style patterns matching files or folders,
         that are skipped if another with the same name already exists in the
         destination folder. (It only makes sense if you are copying to a folder
        that already exists).

    - tasks (list):
        Optional lists of commands to run in order after finishing the copy.
        Like in the templates files, you can use variables on the commands that
        will be replaced by the real values before running the command.
        If one of the commands fail, the rest of them will not run.

    - envops (dict):
        Extra options for the Jinja template environment.

    - extra_paths (list):
        Optional. Additional paths, outside the `src_path`, from where to search
        for templates. This is intended to be used with shared parent templates,
        files with macros, etc. outside the copied project skeleton.

    - pretend (bool):
        Run but do not make any changes

    - force (bool):
        Overwrite files that already exist, without asking

    - skip (bool):
        Skip files that already exist, without asking

    - quiet (bool):
        Suppress the status output

    - cleanup_on_error (bool):
        Remove the destination folder if the copy process or one of the tasks fail.

    - vcs_ref (str):
        VCS reference to checkout in the template.

    - only_diff (bool):
        Try to update only the template diff.

    - subdirectory (str):
        Specify a subdirectory to use when generating the project.
    """
    conf = make_config(**locals())
    is_update = conf.original_src_path != conf.src_path and vcs.is_git_repo_root(
        conf.src_path
    )
    do_diff_update = (
        conf.only_diff
        and is_update
        and conf.old_commit
        and vcs.is_git_repo_root(Path(conf.dst_path))
    )
    try:
        if do_diff_update:
            update_diff(conf=conf)
        else:
          copy_local(conf=conf)

copier\main.py:142:


conf = ConfigData(src_path=WindowsPath('C:/Users/user/AppData/Local/Temp/1/copier.vcs.clone.cj9r8gxa'), subdirectory='sub...{}, data_from_answers_file={}, data_from_template_defaults={'_folder_name': 'subdirectory_project1'}, _data_mutable={})

def copy_local(conf: ConfigData) -> None:

    must_filter = create_path_filter(conf.exclude)

    render = Renderer(conf)
    skip_patterns = [render.string(pattern) for pattern in conf.skip_if_exists]
    must_skip = create_path_filter(skip_patterns)

    if not conf.quiet:
        print("")  # padding space

    folder: StrOrPath
    rel_folder: StrOrPath

    src_path = conf.src_path
    if conf.subdirectory is not None:
        src_path /= conf.subdirectory

    for folder, sub_dirs, files in os.walk(src_path):
        rel_folder = str(folder).replace(str(src_path), "", 1).lstrip(os.path.sep)
        rel_folder = render.string(rel_folder)
        rel_folder = str(rel_folder).replace("." + os.path.sep, ".", 1)

        if must_filter(rel_folder):
            # Folder is excluded, so stop walking it
            sub_dirs[:] = []
            continue

        folder = Path(folder)
        rel_folder = Path(rel_folder)

        render_folder(rel_folder, conf)

        source_paths = get_source_paths(
            conf, folder, rel_folder, files, render, must_filter
        )
        for source_path, rel_path in source_paths:
          render_file(conf, rel_path, source_path, render, must_skip)

copier\main.py:190:


conf = ConfigData(src_path=WindowsPath('C:/Users/user/AppData/Local/Temp/1/copier.vcs.clone.cj9r8gxa'), subdirectory='sub...{}, data_from_answers_file={}, data_from_template_defaults={'_folder_name': 'subdirectory_project1'}, _data_mutable={})
rel_path = WindowsPath('.copier-answers.yml')
src_path = WindowsPath('C:/Users/user/AppData/Local/Temp/1/copier.vcs.clone.cj9r8gxa/subdir1/[[_copier_conf.answers_file]].tmpl')
render = <copier.tools.Renderer object at 0x000001F82BB540B8>
must_skip = <function create_path_filter..match at 0x000001F82B945C80>

def render_file(
    conf: ConfigData,
    rel_path: Path,
    src_path: Path,
    render: Renderer,
    must_skip: CheckPathFunc,
) -> None:
    """Process or copy a file of the skeleton."""
    content: Optional[str] = None
    if str(src_path).endswith(conf.templates_suffix):
      content = render(src_path)

copier\main.py:325:


self = <copier.tools.Renderer object at 0x000001F82BB540B8>
fullpath = WindowsPath('C:/Users/user/AppData/Local/Temp/1/copier.vcs.clone.cj9r8gxa/subdir1/[[_copier_conf.answers_file]].tmpl')

def __call__(self, fullpath: StrOrPath) -> str:
    relpath = (
        str(fullpath).replace(str(self.conf.src_path), "", 1).lstrip(os.path.sep)
    )
  tmpl = self.env.get_template(relpath)

copier\tools.py:158:


self = <jinja2.sandbox.SandboxedEnvironment object at 0x000001F82B9B0C88>
name = 'subdir1\[[_copier_conf.answers_file]].tmpl', parent = None
globals = None

@internalcode
def get_template(self, name, parent=None, globals=None):
    """Load a template from the loader.  If a loader is configured this
    method asks the loader for the template and returns a :class:`Template`.
    If the `parent` parameter is not `None`, :meth:`join_path` is called
    to get the real template name before loading.

    The `globals` parameter can be used to provide template wide globals.
    These variables are available in the context at render time.

    If the template does not exist a :exc:`TemplateNotFound` exception is
    raised.

    .. versionchanged:: 2.4
       If `name` is a :class:`Template` object it is returned from the
       function unchanged.
    """
    if isinstance(name, Template):
        return name
    if parent is not None:
        name = self.join_path(name, parent)
  return self._load_template(name, self.make_globals(globals))

...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\environment.py:883:


self = <jinja2.sandbox.SandboxedEnvironment object at 0x000001F82B9B0C88>
name = 'subdir1\[[_copier_conf.answers_file]].tmpl'
globals = {'cycler': <class 'jinja2.utils.Cycler'>, 'dict': <class 'dict'>, 'joiner': <class 'jinja2.utils.Joiner'>, 'lipsum': <function generate_lorem_ipsum at 0x000001F82AC40D08>, ...}

@internalcode
def _load_template(self, name, globals):
    if self.loader is None:
        raise TypeError("no loader for this environment specified")
    cache_key = (weakref.ref(self.loader), name)
    if self.cache is not None:
        template = self.cache.get(cache_key)
        if template is not None and (
            not self.auto_reload or template.is_up_to_date
        ):
            return template
  template = self.loader.load(self, name, globals)

...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\environment.py:857:


self = <jinja2.loaders.FileSystemLoader object at 0x000001F82B9B05C0>
environment = <jinja2.sandbox.SandboxedEnvironment object at 0x000001F82B9B0C88>
name = 'subdir1\[[_copier_conf.answers_file]].tmpl'
globals = {'cycler': <class 'jinja2.utils.Cycler'>, 'dict': <class 'dict'>, 'joiner': <class 'jinja2.utils.Joiner'>, 'lipsum': <function generate_lorem_ipsum at 0x000001F82AC40D08>, ...}

@internalcode
def load(self, environment, name, globals=None):
    """Loads a template.  This method looks up the template in the cache
    or loads one by calling :meth:`get_source`.  Subclasses should not
    override this method as loaders working on collections of other
    loaders (such as :class:`PrefixLoader` or :class:`ChoiceLoader`)
    will not call this method but `get_source` directly.
    """
    code = None
    if globals is None:
        globals = {}

    # first we try to get the source for this template together
    # with the filename and the uptodate function.
  source, filename, uptodate = self.get_source(environment, name)

...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\loaders.py:115:


self = <jinja2.loaders.FileSystemLoader object at 0x000001F82B9B05C0>
environment = <jinja2.sandbox.SandboxedEnvironment object at 0x000001F82B9B0C88>
template = 'subdir1\[[_copier_conf.answers_file]].tmpl'

def get_source(self, environment, template):
  pieces = split_template_path(template)

...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\loaders.py:177:


template = 'subdir1\[[_copier_conf.answers_file]].tmpl'

def split_template_path(template):
    """Split a path into segments and perform a sanity check.  If it detects
    '..' in the path it will raise a `TemplateNotFound` error.
    """
    pieces = []
    for piece in template.split("/"):
        if (
            path.sep in piece
            or (path.altsep and path.altsep in piece)
            or piece == path.pardir
        ):
          raise TemplateNotFound(template)

E jinja2.exceptions.TemplateNotFound: subdir1[[_copier_conf.answers_file]].tmpl

...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\loaders.py:32: TemplateNotFound

During handling of the above exception, another exception occurred:

tmp_path_factory = TempPathFactory(_given_basetemp=None, _trace=<pluggy._tracing.TagTracerSub object at 0x000001F82A681F60>, _basetemp=WindowsPath('C:/Users/user/AppData/Local/Temp/1/pytest-of-user/pytest-1'))

def test_new_version_changes_subdirectory(tmp_path_factory):
    # 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?
    template_path = tmp_path_factory.mktemp("subdirectory_template")
    project_path = tmp_path_factory.mktemp("subdirectory_project")

    # First, create the template with an initial subdirectory and README inside it
    with local.cwd(template_path):
        os.mkdir("subdir1")

        with open("subdir1/README.md", "w") as fd:
            fd.write("upstream version 1\n")

        with open("subdir1/[[_copier_conf.answers_file]].tmpl", "w") as fd:
            fd.write("[[_copier_answers|to_nice_yaml]]\n")

        # Add the subdirectory option to copier.yml
        with open("copier.yml", "w") as fd:
            fd.write("_subdirectory: subdir1\n")

        git_init("hello template")

    # Generate the project a first time, assert the README exists
  copier.copy(str(template_path), project_path, force=True)

tests\test_subdirectory.py:140:


copier\main.py:150: in copy
shutil.rmtree(conf.src_path)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:500: in rmtree
return _rmtree_unsafe(path, onerror)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:390: in _rmtree_unsafe
_rmtree_unsafe(fullname, onerror)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:390: in _rmtree_unsafe
_rmtree_unsafe(fullname, onerror)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:390: in _rmtree_unsafe
_rmtree_unsafe(fullname, onerror)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:395: in _rmtree_unsafe
onerror(os.unlink, fullname, sys.exc_info())


path = 'C:\Users\user\AppData\Local\Temp\1\copier.vcs.clone.cj9r8gxa\.git\objects\56'
onerror = <function rmtree..onerror at 0x000001F82B8C17B8>

def _rmtree_unsafe(path, onerror):
    try:
        if os.path.islink(path):
            # symlinks to directories are forbidden, see bug #1669
            raise OSError("Cannot call rmtree on a symbolic link")
    except OSError:
        onerror(os.path.islink, path, sys.exc_info())
        # can't continue even if onerror hook returns
        return
    names = []
    try:
        names = os.listdir(path)
    except OSError:
        onerror(os.listdir, path, sys.exc_info())
    for name in names:
        fullname = os.path.join(path, name)
        try:
            mode = os.lstat(fullname).st_mode
        except OSError:
            mode = 0
        if stat.S_ISDIR(mode):
            _rmtree_unsafe(fullname, onerror)
        else:
            try:
              os.unlink(fullname)

E PermissionError: [WinError 5] Accès refusé: 'C:\Users\user\AppData\Local\Temp\1\copier.vcs.clone.cj9r8gxa\.git\objects\56\833abac2b79a6a1d12804857bcdc86051937e8'

..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:393: PermissionError
---------------------------- Captured stdout call -----------------------------
No git tags found in template; using HEAD as ref

create  README.md

Something went wrong. Removing destination folder.
______________________________ test_render_tasks ______________________________

tmp_path = WindowsPath('C:/Users/user/AppData/Local/Temp/1/pytest-of-user/pytest-1/test_render_tasks0')

def test_render_tasks(tmp_path):
    tasks = ["touch [[ myvar ]]/1.txt", "touch [[ myvar ]]/2.txt"]
  render(tmp_path, tasks=tasks)

tests\test_tasks.py:8:


tests\helpers.py:26: in render
copier.copy(str(PROJECT_TEMPLATE), tmp_path, data=DATA, **kwargs)
copier\main.py:142: in copy
copy_local(conf=conf)
copier\main.py:190: in copy_local
render_file(conf, rel_path, source_path, render, must_skip)
copier\main.py:325: in render_file
content = render(src_path)
copier\tools.py:158: in call
tmpl = self.env.get_template(relpath)
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\environment.py:883: in get_template
return self._load_template(name, self.make_globals(globals))
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\environment.py:857: in _load_template
template = self.loader.load(self, name, globals)
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\loaders.py:115: in load
source, filename, uptodate = self.get_source(environment, name)
...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\loaders.py:177: in get_source
pieces = split_template_path(template)


template = '[% if not py3 %]py2_folder[% endif %]\thing.py.tmpl'

def split_template_path(template):
    """Split a path into segments and perform a sanity check.  If it detects
    '..' in the path it will raise a `TemplateNotFound` error.
    """
    pieces = []
    for piece in template.split("/"):
        if (
            path.sep in piece
            or (path.altsep and path.altsep in piece)
            or piece == path.pardir
        ):
          raise TemplateNotFound(template)

E jinja2.exceptions.TemplateNotFound: [% if not py3 %]py2_folder[% endif %]\thing.py.tmpl

...venvs\copier-6wp17jh--py3.6\lib\site-packages\jinja2\loaders.py:32: TemplateNotFound
---------------------------- Captured stdout call -----------------------------
Something went wrong. Removing destination folder.
_______________________________ test_updatediff _______________________________

tmpdir = local('C:\Users\user\AppData\Local\Temp\1\pytest-of-user\pytest-1\test_updatediff0')

def test_updatediff(tmpdir):
    tmp_path = Path(tmpdir)
    target = tmp_path / "target"
    readme = target / "README.txt"
    answers = target / ".copier-answers.yml"
    commit = git["commit", "--all"]
    # Run copier 1st time, with specific tag
    CopierApp.invoke(
      "copy", str(REPO_BUNDLE_PATH), str(target), force=True, vcs_ref="v0.0.1"
    )

tests\test_updatediff.py:23:


...venvs\copier-6wp17jh--py3.6\lib\site-packages\plumbum\cli\application.py:615: in invoke
inst, retcode = subapp.run(argv, exit=False)
...venvs\copier-6wp17jh--py3.6\lib\site-packages\plumbum\cli\application.py:572: in run
retcode = inst.main(*tailargs)
copier\cli.py:17: in _wrapper
return method(*args, **kwargs)
copier\cli.py:158: in main
self.parent._copy(template_src, destination_path)
copier\cli.py:121: in _copy
**kwargs,
copier\main.py:150: in copy
shutil.rmtree(conf.src_path)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:500: in rmtree
return _rmtree_unsafe(path, onerror)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:390: in _rmtree_unsafe
_rmtree_unsafe(fullname, onerror)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:390: in _rmtree_unsafe
_rmtree_unsafe(fullname, onerror)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:390: in _rmtree_unsafe
_rmtree_unsafe(fullname, onerror)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:395: in _rmtree_unsafe
onerror(os.unlink, fullname, sys.exc_info())


path = 'C:\Users\user\AppData\Local\Temp\1\copier.vcs.clone.i1drx918\.git\objects\pack'
onerror = <function rmtree..onerror at 0x000001F82B84FBF8>

def _rmtree_unsafe(path, onerror):
    try:
        if os.path.islink(path):
            # symlinks to directories are forbidden, see bug #1669
            raise OSError("Cannot call rmtree on a symbolic link")
    except OSError:
        onerror(os.path.islink, path, sys.exc_info())
        # can't continue even if onerror hook returns
        return
    names = []
    try:
        names = os.listdir(path)
    except OSError:
        onerror(os.listdir, path, sys.exc_info())
    for name in names:
        fullname = os.path.join(path, name)
        try:
            mode = os.lstat(fullname).st_mode
        except OSError:
            mode = 0
        if stat.S_ISDIR(mode):
            _rmtree_unsafe(fullname, onerror)
        else:
            try:
              os.unlink(fullname)

E PermissionError: [WinError 5] Accès refusé: 'C:\Users\user\AppData\Local\Temp\1\copier.vcs.clone.i1drx918\.git\objects\pack\pack-fc2e28237057fb097e33d4b8e3de9631a78507e6.idx'

..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:393: PermissionError
---------------------------- Captured stdout call -----------------------------

create  .copier-answers.yml
create  README.txt

_________________________ test_commit_hooks_respected _________________________

tmp_path = WindowsPath('C:/Users/user/AppData/Local/Temp/1/pytest-of-user/pytest-1/test_commit_hooks_respected0/tmp_path')

def test_commit_hooks_respected(tmp_path: Path):
    """Commit hooks are taken into account when producing the update diff."""
    # Prepare source template v1
    src, tmp_path = tmp_path / "src", tmp_path / "tmp_path"
    src.mkdir()
    with local.cwd(src):
        build_file_tree(
            {
                "copier.yml": """
                _tasks:
                  - git init
                  - pre-commit install
                  - pre-commit run -a || true
                what: grog
                """,
                "[[ _copier_conf.answers_file ]].tmpl": """
                [[ _copier_answers|to_nice_yaml ]]
                """,
                ".pre-commit-config.yaml": r"""
                repos:
                - repo: https://github.com/prettier/prettier
                  rev: 2.0.4
                  hooks:
                    - id: prettier
                - repo: local
                  hooks:
                    - id: forbidden-files
                      name: forbidden files
                      entry: found forbidden files; remove them
                      language: fail
                      files: "\\.rej$"
                """,
                "life.yml.tmpl": """
                # Following code should be reformatted by pre-commit after copying
                Line 1:      hello
                Line 2:      [[ what ]]
                Line 3:      bye
                """,
            }
        )
        git("init")
        git("add", ".")
        git("commit", "-m", "commit 1")
        git("tag", "v1")
    # Copy source template
  copy(src_path=str(src), dst_path=tmp_path, force=True)

tests\test_updatediff.py:187:


copier\main.py:150: in copy
shutil.rmtree(conf.src_path)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:500: in rmtree
return _rmtree_unsafe(path, onerror)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:390: in _rmtree_unsafe
_rmtree_unsafe(fullname, onerror)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:390: in _rmtree_unsafe
_rmtree_unsafe(fullname, onerror)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:390: in _rmtree_unsafe
_rmtree_unsafe(fullname, onerror)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:395: in _rmtree_unsafe
onerror(os.unlink, fullname, sys.exc_info())


path = 'C:\Users\user\AppData\Local\Temp\1\copier.vcs.clone.64je6k9p\.git\objects\39'
onerror = <function rmtree..onerror at 0x000001F82B891A60>

def _rmtree_unsafe(path, onerror):
    try:
        if os.path.islink(path):
            # symlinks to directories are forbidden, see bug #1669
            raise OSError("Cannot call rmtree on a symbolic link")
    except OSError:
        onerror(os.path.islink, path, sys.exc_info())
        # can't continue even if onerror hook returns
        return
    names = []
    try:
        names = os.listdir(path)
    except OSError:
        onerror(os.listdir, path, sys.exc_info())
    for name in names:
        fullname = os.path.join(path, name)
        try:
            mode = os.lstat(fullname).st_mode
        except OSError:
            mode = 0
        if stat.S_ISDIR(mode):
            _rmtree_unsafe(fullname, onerror)
        else:
            try:
              os.unlink(fullname)

E PermissionError: [WinError 5] Accès refusé: 'C:\Users\user\AppData\Local\Temp\1\copier.vcs.clone.64je6k9p\.git\objects\39\02e85b0dcaf023b573fc714909ecf81cccf59a'

..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:393: PermissionError
---------------------------- Captured stdout call -----------------------------

create  .pre-commit-config.yaml
create  life.yml
create  .copier-answers.yml

Initialized empty Git repository in C:/Users/user/AppData/Local/Temp/1/pytest-of-user/pytest-1/test_commit_hooks_respected0/tmp_path/.git/
pre-commit installed at .git\hooks\pre-commit
[INFO] Initializing environment for https://github.com/prettier/prettier.
[INFO] Installing environment for https://github.com/prettier/prettier.
[INFO] Once installed this environment will be reused.
[INFO] This may take a few minutes...
prettier.............................................(no files to check)Skipped
forbidden files......................................(no files to check)Skipped

---------------------------- Captured stderr call -----------------------------

Running task 1 of 3: git init
Running task 2 of 3: pre-commit install
Running task 3 of 3: pre-commit run -a || true
________________________________ test_get_repo ________________________________

def test_get_repo():
    get = vcs.get_repo

    assert get("git@git.myproject.org:MyProject") == "git@git.myproject.org:MyProject"
    assert (
        get("git://git.myproject.org/MyProject") == "git://git.myproject.org/MyProject"
    )
    assert (
        get("https://github.com/jpscaletti/copier.git")
        == "https://github.com/jpscaletti/copier.git"
    )

    assert (
        get("gh:/jpscaletti/copier.git") == "https://github.com/jpscaletti/copier.git"
    )
    assert get("gh:jpscaletti/copier.git") == "https://github.com/jpscaletti/copier.git"
    assert get("gl:jpscaletti/copier.git") == "https://gitlab.com/jpscaletti/copier.git"
    assert get("gh:jpscaletti/copier") == "https://github.com/jpscaletti/copier.git"
    assert get("gl:jpscaletti/copier") == "https://gitlab.com/jpscaletti/copier.git"

    assert (
        get("git+https://git.myproject.org/MyProject")
        == "https://git.myproject.org/MyProject"
    )
    assert (
        get("git+ssh://git.myproject.org/MyProject")
        == "ssh://git.myproject.org/MyProject"
    )

    assert get("git://git.myproject.org/MyProject.git@master")
    assert get("git://git.myproject.org/MyProject.git@v1.0")
    assert get("git://git.myproject.org/MyProject.git@da39a3ee5e6b4b0d3255bfef956018")
  assert get("http://google.com") is None

tests\test_vcs.py:40:


copier\vcs.py:48: in get_repo
or is_git_repo_root(url_path)
copier\vcs.py:27: in is_git_repo_root
with local.cwd(path / ".git"):
..\AppData\Local\Programs\Python\Python36\Lib\contextlib.py:81: in enter
return next(self.gen)
...venvs\copier-6wp17jh--py3.6\lib\site-packages\plumbum\path\local.py:375: in call
newdir = self.chdir(newdir)


self = <LocalWorkdir C:\Users\user\copier>
newdir = WindowsPath('http:/google.com/.git')

def chdir(self, newdir):
    """Changes the current working directory to the given one

    :param newdir: The destination director (a string or a ``LocalPath``)
    """
    if isinstance(newdir, RemotePath):
        raise TypeError("newdir cannot be %r" % (newdir, ))
    logger.debug("Chdir to %s", newdir)
  os.chdir(str(newdir))

E OSError: [WinError 123] La syntaxe du nom de fichier, de répertoire ou de volume est incorrecte: 'http:\google.com\.git'

...venvs\copier-6wp17jh--py3.6\lib\site-packages\plumbum\path\local.py:360: OSError

and the rest of it, template and permission errors

_________________________________ test_clone __________________________________
def test_clone():
    tmp = vcs.clone("https://github.com/copier-org/copier.git")
    assert tmp
    assert exists(join(tmp, "README.md"))
  shutil.rmtree(tmp)

tests\test_vcs.py:48:


..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:500: in rmtree
return _rmtree_unsafe(path, onerror)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:390: in _rmtree_unsafe
_rmtree_unsafe(fullname, onerror)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:390: in _rmtree_unsafe
_rmtree_unsafe(fullname, onerror)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:390: in _rmtree_unsafe
_rmtree_unsafe(fullname, onerror)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:395: in _rmtree_unsafe
onerror(os.unlink, fullname, sys.exc_info())


path = 'C:\Users\user\AppData\Local\Temp\1\copier.vcs.clone.171og9u6\.git\objects\pack'
onerror = <function rmtree..onerror at 0x000001F82BB3D268>

def _rmtree_unsafe(path, onerror):
    try:
        if os.path.islink(path):
            # symlinks to directories are forbidden, see bug #1669
            raise OSError("Cannot call rmtree on a symbolic link")
    except OSError:
        onerror(os.path.islink, path, sys.exc_info())
        # can't continue even if onerror hook returns
        return
    names = []
    try:
        names = os.listdir(path)
    except OSError:
        onerror(os.listdir, path, sys.exc_info())
    for name in names:
        fullname = os.path.join(path, name)
        try:
            mode = os.lstat(fullname).st_mode
        except OSError:
            mode = 0
        if stat.S_ISDIR(mode):
            _rmtree_unsafe(fullname, onerror)
        else:
            try:
              os.unlink(fullname)

E PermissionError: [WinError 5] Accès refusé: 'C:\Users\user\AppData\Local\Temp\1\copier.vcs.clone.171og9u6\.git\objects\pack\pack-ad21e377bf323a731ceb92acc46d87aa4550aa15.idx'

..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:393: PermissionError

=========================== short test summary info ===========================
FAILED tests/test_config.py::test_invalid_yaml - assert None
FAILED tests/test_copy.py::test_copy - jinja2.exceptions.TemplateNotFound: [%...
FAILED tests/test_copy.py::test_copy_repo - PermissionError: [WinError 5] Acc...
FAILED tests/test_copy.py::test_default_exclude - jinja2.exceptions.TemplateN...
FAILED tests/test_copy.py::test_include_file - jinja2.exceptions.TemplateNotF...
FAILED tests/test_copy.py::test_include_pattern - jinja2.exceptions.TemplateN...
FAILED tests/test_copy.py::test_exclude_file - jinja2.exceptions.TemplateNotF...
FAILED tests/test_copy.py::test_config_exclude - jinja2.exceptions.TemplateNo...
FAILED tests/test_copy.py::test_config_exclude_overridden - jinja2.exceptions...
FAILED tests/test_copy.py::test_config_include - jinja2.exceptions.TemplateNo...
FAILED tests/test_copy.py::test_skip_option - jinja2.exceptions.TemplateNotFo...
FAILED tests/test_copy.py::test_force_option - jinja2.exceptions.TemplateNotF...
FAILED tests/test_copy.py::test_pretend_option - jinja2.exceptions.TemplateNo...
FAILED tests/test_demo_update_tasks.py::test_update_tasks - PermissionError: ...
FAILED tests/test_migrations.py::test_migrations_and_tasks - PermissionError:...
FAILED tests/test_migrations.py::test_pre_migration_modifies_answers - Permis...
FAILED tests/test_output.py::test_output - jinja2.exceptions.TemplateNotFound...
FAILED tests/test_output.py::test_output_pretend - jinja2.exceptions.Template...
FAILED tests/test_output.py::test_output_force - jinja2.exceptions.TemplateNo...
FAILED tests/test_output.py::test_output_skip - jinja2.exceptions.TemplateNot...
FAILED tests/test_output.py::test_output_quiet - jinja2.exceptions.TemplateNo...
FAILED tests/test_prompt.py::test_copy_default_advertised[name0] - Permission...
FAILED tests/test_prompt.py::test_copy_default_advertised[None] - PermissionE...
FAILED tests/test_prompt.py::test_copy_default_advertised[Luigi] - Permission...
FAILED tests/test_subdirectory.py::test_update_subdirectory - PermissionError...
FAILED tests/test_subdirectory.py::test_new_version_uses_subdirectory - Permi...
FAILED tests/test_subdirectory.py::test_new_version_changes_subdirectory - Pe...
FAILED tests/test_tasks.py::test_render_tasks - jinja2.exceptions.TemplateNot...
FAILED tests/test_updatediff.py::test_updatediff - PermissionError: [WinError...
FAILED tests/test_updatediff.py::test_commit_hooks_respected - PermissionErro...
FAILED tests/test_vcs.py::test_get_repo - OSError: [WinError 123] La syntaxe ...
FAILED tests/test_vcs.py::test_clone - PermissionError: [WinError 5] Accès re...
================== 32 failed, 81 passed in 320.33s (0:05:20) ==================

@pawamoy
Copy link
Contributor

pawamoy commented Jul 23, 2020

There were some formatting issues so here's the text file.

pytest.txt

@yajo
Copy link
Member Author

yajo commented Jul 24, 2020

Thanks! I've seen some errors in code. Stay tuned.

@yajo yajo force-pushed the cross-os-test branch 4 times, most recently from 2c78fbd to 8a318f4 Compare July 24, 2020 12:03
@pawamoy
Copy link
Contributor

pawamoy commented Jul 27, 2020

MacOS

>       assert not (tmp_path / "doc" / "mañana.txt").exists()
E       AssertionError: assert not True

Must be an encoding issue? The exclude option does specify "mañana.txt", but maybe the file's actual name is not matched against it when checking for exclusion?

Windows

A lot of fixed mixed line endings messages. My intuition is that, when cloning/fetching the repo, git is changing line endings. Maybe we could add some CI setup lines to explicitly ask git not to change line endings:

git config --global core.autocrlf false
git config --global core.eol lf

The changed line endings could also be the reason why the black hook fails, since Black likely changes line endings back to LF.


We also see unicode encode/decore errors in both Windows and MacOS. Maybe some extra environments variables would fix this:

PYTHONIOENCODING="UTF-8"
LC_ALL="en_US.utf-8"
LANG="en_US.utf-8"

@yajo
Copy link
Member Author

yajo commented Jul 27, 2020

OK, I tried that, let's see what the bots say.

FTR a possible source of macOS problems: https://www.python.org/dev/peps/pep-0540/#locale-encoding-and-utf-8

@pawamoy
Copy link
Contributor

pawamoy commented Jul 27, 2020

We still have errors on the mañana file (check_executables_have_shebangs and MacOs). Black is fixed 🎉 but prettier still rewrites files, and mixed-line-endings is still confused. Maybe line endings should be configured in each tool (if possible 😕)

EDIT: I see you've added the autocrlf false, maybe git config --global core.eol lf would help as well.
EDIT2: or maybe autocrlf should be true? https://stackoverflow.com/q/3206843/3451029 definitely not. input maybe.

@pawamoy
Copy link
Contributor

pawamoy commented Jul 27, 2020

Prettier has an end-of-line option: https://prettier.io/docs/en/options.html#end-of-line
EDIT: maybe we do have mixed line endings in some files? Just checked and ran dos2unix of every regular file in the repo, doesn't seem so: there's no difference (git diff is empty).

@pawamoy
Copy link
Contributor

pawamoy commented Jul 28, 2020

For the mañana file, I'd say this is a bug in pre-commit-hooks/check_executables_have_shebangs.

The thing is, they get the paths by parsing the output of git ls-files --stage -- path1 path2 ..., which wraps a path in double-quotes when it contains an unusual character, character which is escaped with integer sequences:

$ git ls-files --stage -- tests/demo/doc/mañana.txt tests/demo/doc/manana.txt
100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0       tests/demo/doc/manana.txt
100644 5ab9dcdd36df0f76f202227ecb7ae8a5baaa456b 0       "tests/demo/doc/ma\303\261ana.txt"

The path they get for the second line is "tests/demo/doc/ma\303\261ana.txt", which they then try to open, and of course it fails because of the extra quotes and integer sequences.

I'll open an issue there.

@pawamoy
Copy link
Contributor

pawamoy commented Jul 28, 2020

Prettier and mixed-line-endings are passing on my workstation, file endings are LF. I guess this really has to be fixed with some git config, to make sure the files are fetched with LF in CI.

@pawamoy
Copy link
Contributor

pawamoy commented Jul 29, 2020

A few more fixes for tests:

diff --git a/tests/demo_migrations/copier.yaml b/tests/demo_migrations/copier.yaml
index e363b8b..80dc594 100644
--- a/tests/demo_migrations/copier.yaml
+++ b/tests/demo_migrations/copier.yaml
@@ -4,8 +4,8 @@ _exclude:
   - .git

 _tasks:
-  - "python3 [[ _copier_conf.src_path / 'tasks.py' ]] 1"
-  - [python3, "[[ _copier_conf.src_path / 'tasks.py' ]]", 2]
+  - "python [[ _copier_conf.src_path / 'tasks.py' ]] 1"
+  - [python, "[[ _copier_conf.src_path / 'tasks.py' ]]", 2]

 _migrations:
   # This migration is never executed because it's the 1st version copied, and
diff --git a/tests/demo_migrations/migrations.py b/tests/demo_migrations/migrations.py
index 6c498e0..9d34c33 100755
--- a/tests/demo_migrations/migrations.py
+++ b/tests/demo_migrations/migrations.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python3
+#!/usr/bin/env python
 import json
 import os
 import sys
diff --git a/tests/demo_migrations/tasks.py b/tests/demo_migrations/tasks.py
index 74503ba..9e7b102 100755
--- a/tests/demo_migrations/tasks.py
+++ b/tests/demo_migrations/tasks.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python3
+#!/usr/bin/env python
 import os
 import os.path
 import sys
diff --git a/tests/test_migrations.py b/tests/test_migrations.py
index f53936d..eee41e2 100644
--- a/tests/test_migrations.py
+++ b/tests/test_migrations.py
@@ -100,7 +100,7 @@ def test_pre_migration_modifies_answers(tmp_path_factory):
                     _migrations:
                       - version: v2
                         before:
-                          - - python3
+                          - - python
                             - -c
                             - |
                                 import sys, yaml, pathlib
diff --git a/tests/test_output.py b/tests/test_output.py
index 7c7b00c..302d624 100644
--- a/tests/test_output.py
+++ b/tests/test_output.py
@@ -8,7 +8,7 @@ def test_output(capsys, tmp_path):
     out, _ = capsys.readouterr()
     assert re.search(r"create[^\s]*  config\.py", out)
     assert re.search(r"create[^\s]*  pyproject\.toml", out)
-    assert re.search(r"create[^\s]*  doc/images/nslogo\.gif", out)
+    assert re.search(r"create[^\s]*  doc[/\\]images[/\\]nslogo\.gif", out)


 def test_output_pretend(capsys, tmp_path):
@@ -16,7 +16,7 @@ def test_output_pretend(capsys, tmp_path):
     out, _ = capsys.readouterr()
     assert re.search(r"create[^\s]*  config\.py", out)
     assert re.search(r"create[^\s]*  pyproject\.toml", out)
-    assert re.search(r"create[^\s]*  doc/images/nslogo\.gif", out)
+    assert re.search(r"create[^\s]*  doc[/\\]images[/\\]nslogo\.gif", out)


 def test_output_force(capsys, tmp_path):
@@ -27,7 +27,7 @@ def test_output_force(capsys, tmp_path):
     assert re.search(r"conflict[^\s]*  config\.py", out)
     assert re.search(r"force[^\s]*  config\.py", out)
     assert re.search(r"identical[^\s]*  pyproject\.toml", out)
-    assert re.search(r"identical[^\s]*  doc/images/nslogo\.gif", out)
+    assert re.search(r"identical[^\s]*  doc[/\\]images[/\\]nslogo\.gif", out)


 def test_output_skip(capsys, tmp_path):
@@ -38,7 +38,7 @@ def test_output_skip(capsys, tmp_path):
     assert re.search(r"conflict[^\s]*  config\.py", out)
     assert re.search(r"skip[^\s]*  config\.py", out)
     assert re.search(r"identical[^\s]*  pyproject\.toml", out)
-    assert re.search(r"identical[^\s]*  doc/images/nslogo\.gif", out)
+    assert re.search(r"identical[^\s]*  doc[/\\]images[/\\]nslogo\.gif", out)


 def test_output_quiet(capsys, tmp_path):
  • It replaces python3 with python in tests files. During the tests, the environment should be properly set up for python to be the right one (the one the tests are executed with). On my workstation I don't have python3 so these tests were failing.
  • It fixes the last few paths and regex searches that were using Linux separators /. Now it accepts both: [/\\]

The rest of the failures (on my workstation) are only permission errors now, due to temporary file cleaning with shutil.rmtree.
Example:

____________________ test_new_version_changes_subdirectory ____________________
[gw1] win32 -- Python 3.6.8 c:\users\user\.venvs\copier-6wp17jh--py3.6\scripts\python.exe

tmp_path_factory = TempPathFactory(_given_basetemp=WindowsPath('C:/Users/user/AppData/Local/Temp/1/pytest-of-user/pytest-3/popen-...0002450212B908>, _basetemp=WindowsPath('C:/Users/user/AppData/Local/Temp/1/pytest-of-user/pytest-3/popen-gw1'))

    def test_new_version_changes_subdirectory(tmp_path_factory):
        # 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?
        template_path = tmp_path_factory.mktemp("subdirectory_template")
        project_path = tmp_path_factory.mktemp("subdirectory_project")

        # First, create the template with an initial subdirectory and README inside it
        with local.cwd(template_path):
            os.mkdir("subdir1")

            with open("subdir1/README.md", "w") as fd:
                fd.write("upstream version 1\n")

            with open("subdir1/[[_copier_conf.answers_file]].tmpl", "w") as fd:
                fd.write("[[_copier_answers|to_nice_yaml]]\n")

            # Add the subdirectory option to copier.yml
            with open("copier.yml", "w") as fd:
                fd.write("_subdirectory: subdir1\n")

            git_init("hello template")

        # Generate the project a first time, assert the README exists
        copier.copy(str(template_path), project_path, force=True)
        assert (project_path / "README.md").exists()

        # Start versioning the generated project
        with local.cwd(project_path):
            git_init("hello project")

            # After first commit, change the README, commit again
            with open("README.md", "w") as fd:
                fd.write("downstream version 1\n")
            git("commit", "-am", "updated readme")

        # Now change the template
        with local.cwd(template_path):

            # Update the README
            with open("subdir1/README.md", "w") as fd:
                fd.write("upstream version 2\n")

            # Rename the subdirectory
            os.rename("subdir1", "subdir2")

            # Update copier.yml to reflect this change
            with open("copier.yml", "w") as fd:
                fd.write("_subdirectory: subdir2\n")

            # Commit the changes
            git("add", ".", "-A")
            git("commit", "-m", "changed from subdir1 to subdir2")

        # Finally, update the generated project
        copier.copy(
>           str(template_path), project_path, force=True, skip_if_exists=["README.md"]
        )

tests\test_subdirectory.py:172:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
copier\main.py:140: in copy
    update_diff(conf=conf)
copier\main.py:244: in update_diff
    diff = diff_cmd("--inter-hunk-context=0")
..\AppData\Local\Programs\Python\Python36\Lib\tempfile.py:809: in __exit__
    self.cleanup()
..\AppData\Local\Programs\Python\Python36\Lib\tempfile.py:813: in cleanup
    _shutil.rmtree(self.name)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:500: in rmtree
    return _rmtree_unsafe(path, onerror)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:390: in _rmtree_unsafe
    _rmtree_unsafe(fullname, onerror)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:390: in _rmtree_unsafe
    _rmtree_unsafe(fullname, onerror)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:390: in _rmtree_unsafe
    _rmtree_unsafe(fullname, onerror)
..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:395: in _rmtree_unsafe
    onerror(os.unlink, fullname, sys.exc_info())
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

path = 'C:\\Users\\user\\AppData\\Local\\Temp\\1\\copier.main.update_diff.2o04g94l\\.git\\objects\\1a'
onerror = <function rmtree.<locals>.onerror at 0x00000245032FF9D8>

    def _rmtree_unsafe(path, onerror):
        try:
            if os.path.islink(path):
                # symlinks to directories are forbidden, see bug #1669
                raise OSError("Cannot call rmtree on a symbolic link")
        except OSError:
            onerror(os.path.islink, path, sys.exc_info())
            # can't continue even if onerror hook returns
            return
        names = []
        try:
            names = os.listdir(path)
        except OSError:
            onerror(os.listdir, path, sys.exc_info())
        for name in names:
            fullname = os.path.join(path, name)
            try:
                mode = os.lstat(fullname).st_mode
            except OSError:
                mode = 0
            if stat.S_ISDIR(mode):
                _rmtree_unsafe(fullname, onerror)
            else:
                try:
>                   os.unlink(fullname)
E                   PermissionError: [WinError 5] Accès refusé: 'C:\\Users\\user\\AppData\\Local\\Temp\\1\\copier.main.update_diff.2o04g94l\\.git\\objects\\1a\\8e59d50a1c92ad3de61a4a6ce0815fd3da4c07'

..\AppData\Local\Programs\Python\Python36\Lib\shutil.py:393: PermissionError

@pawamoy
Copy link
Contributor

pawamoy commented Jul 31, 2020

New pre-commit-hooks release: https://github.com/pre-commit/pre-commit-hooks/releases/tag/v3.2.0, this should fix the manana issue on Windows 🙂

@yajo
Copy link
Member Author

yajo commented Aug 3, 2020

EDIT: I see you've added the autocrlf false, maybe git config --global core.eol lf would help as well.

Changed. Also applied diff from #224 (comment).

Prettier has an end-of-line option: https://prettier.io/docs/en/options.html#end-of-line

We're using that since we use prettier AFAIK. Maybe that should be disabled so it's git who handles that? 🤔

endOfLine: lf

Let's see if tests work under windows or mac.

Don't fail fast the tests

If there's one Python version or OS that fails, I still want to see results for the other matrix runs.
TODO Get some happy Mac hacker that fixes this for us.
@yajo
Copy link
Member Author

yajo commented Aug 4, 2020

I took a dirty but pragmatic approach on some problems:

  1. Only support py3.8+ on Windows. This version features a safe cleanup implementation on TemporaryDirectory class.
  2. Skip the utf-8 corner-case test on macOS. I hope some mac hipster comes around and fixes that.
  3. Allow failures on pre-commit on Widows. I have set up a Windows VM, entered WSL (bash), fixed some git configs and finally I managed to get pre-commit happy. But none of them worked here at CI. After all, as long as pre-commit is happy on Linux, we can consider it OK. Also, the fact that it removes some CRLF chars before committing for people on Windows should not be a real world problem too. So, hey, if there's another Windows hipster that wants to fix that for us, he's welcome too! 😊
  4. Remove external dependencies (yaml, plumbum) from tasks or migrations in tests. These were not available to the main python interpreter, the one chosen by windows to run tasks. They were not really needed after all.

Let's see what happens now 😊

@pawamoy
Copy link
Contributor

pawamoy commented Aug 4, 2020

That is great, and you are completely right, as long as non-unit tests are passing on Linux, this is fine. And skipping the few unit tests not passing on Windows/MacOS is fine as well. If a user of these platforms encounter a related bug, they'll be able to fix it or at least help fix it.

Thanks a lot for your work here @yajo 🙂

Now I looked at the two last tests failing on Windows, but cannot see what's happening. There seems to be a failed substitution in the OSError case, and a failure from git to add and commit a modified file in the second case.

- Ignore errors when executing `shutil.rmtree()`, because it seems like it's common to fail when deleting git repos on Windows, and since these are temp files, we don't really care that much there's garbage left. Any good OS should clean the temp folders automatically.
- Always find Jinja templates in `PosixPath` mode.
- Ignore `OSError` when trying to enter a possibly git root directory. This is yielded by Windows when the path is a URL and we don't really care about it.
- Fix some tests with non-windows hardcoded stuff.
- Fix a test that was using a Bash script. Modified to be Python, which should work fine cross-system.
- Remove external python dependencies (yaml, plumbum) from test task/migration files. These are available on Linux because it gets the python env from the venv, but on Windows, it uses the main python interpreter and breaks. After all, that's not very important here.
- Do not modify EOL in CI.
- Use python executable in tests instead of python3.
- Update pre-commit versions to include pre-commit/pre-commit-hooks#509.
- Disable autorebasing.
- Disable pre-commit on CI on Windows.
- Require python 3.8 on Windows, where `tempfile` supports autoremoving directories with read-only files.
@yajo
Copy link
Member Author

yajo commented Aug 7, 2020

I have been testing in a Windows VM and I'd say the main problem is that the CI is using Git Bash instead of WSL Bash on Windows, which is weird because it seems like being the opposite case of what rest of the world is saying (see actions/runner-images#1081 (comment)).

But, you know what? I give up... 😁

If somebody really wants to use console scripts with deep git features on Windows, he'll be probably using WSL already, which is supposed to make whatever works fine on Ubuntu fine on Windows, so if something fails it's most likely WSL problem and not Copier's... So, finally the workaround will be the same as for that nasty macOS test: disable them. FTR this is the latest action run that failed before skipping those tests.

I'm gonna open a couple of issues speaking about these corner cases after the PR is merged, so we give visibility to OS-specific issues, which could happen only on CI or could be happening in the real world, but that will have to be fixed by some voluntary user that really cares about that. 🤷‍♂️

@yajo yajo added automerge and removed automerge labels Aug 7, 2020
@yajo yajo force-pushed the cross-os-test branch 2 times, most recently from a497532 to 30903f1 Compare August 7, 2020 11:12
Pip, poetry and pre-commit defaulted to OS-specific cache and venv dirs. This made the caching system less useful on macOS and Windows.

Now, all is stored under $PWD/.venv and cached from there. This works fine across different OS. Also, the OS is added to the cache key.
Applying workaround for problem described at actions/cache#315 (comment) that makes the windows build take over 20 minutes. Better to use no cache then.
@github-actions github-actions bot merged commit a957f0e into master Aug 8, 2020
@github-actions github-actions bot deleted the cross-os-test branch August 8, 2020 15:27
@yajo yajo linked an issue Aug 10, 2020 that may be closed by this pull request
@yajo yajo added this to the v4.1.0 milestone Aug 13, 2020
rachmadaniHaryono added a commit to rachmadaniHaryono/copier-poetry that referenced this pull request Dec 10, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
maintenance Boring maintenance routine
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Template not found / permission denied on Windows
2 participants