cookiecutter
213 строк · 7.0 Кб
1"""Tests around cloning repositories and detection of errors at it."""
2
3import os
4import subprocess
5
6import pytest
7
8from cookiecutter import exceptions, vcs
9
10
11def test_clone_should_raise_if_vcs_not_installed(mocker, clone_dir) -> None:
12"""In `clone()`, a `VCSNotInstalled` exception should be raised if no VCS \
13is installed."""
14mocker.patch('cookiecutter.vcs.is_vcs_installed', autospec=True, return_value=False)
15
16repo_url = 'https://github.com/pytest-dev/cookiecutter-pytest-plugin.git'
17
18with pytest.raises(exceptions.VCSNotInstalled):
19vcs.clone(repo_url, clone_to_dir=str(clone_dir))
20
21
22def test_clone_should_rstrip_trailing_slash_in_repo_url(mocker, clone_dir) -> None:
23"""In `clone()`, repo URL's trailing slash should be stripped if one is \
24present."""
25mocker.patch('cookiecutter.vcs.is_vcs_installed', autospec=True, return_value=True)
26
27mock_subprocess = mocker.patch(
28'cookiecutter.vcs.subprocess.check_output',
29autospec=True,
30)
31
32vcs.clone('https://github.com/foo/bar/', clone_to_dir=clone_dir, no_input=True)
33
34mock_subprocess.assert_called_once_with(
35['git', 'clone', 'https://github.com/foo/bar'],
36cwd=clone_dir,
37stderr=subprocess.STDOUT,
38)
39
40
41def test_clone_should_abort_if_user_does_not_want_to_reclone(mocker, clone_dir) -> None:
42"""In `clone()`, if user doesn't want to reclone, Cookiecutter should exit \
43without cloning anything."""
44mocker.patch('cookiecutter.vcs.is_vcs_installed', autospec=True, return_value=True)
45mocker.patch(
46'cookiecutter.vcs.prompt_and_delete', side_effect=SystemExit, autospec=True
47)
48mock_subprocess = mocker.patch(
49'cookiecutter.vcs.subprocess.check_output',
50autospec=True,
51)
52
53# Create repo_dir to trigger prompt_and_delete
54repo_dir = clone_dir.joinpath('cookiecutter-pytest-plugin')
55repo_dir.mkdir()
56
57repo_url = 'https://github.com/pytest-dev/cookiecutter-pytest-plugin.git'
58
59with pytest.raises(SystemExit):
60vcs.clone(repo_url, clone_to_dir=str(clone_dir))
61assert not mock_subprocess.called
62
63
64def test_clone_should_silent_exit_if_ok_to_reuse(mocker, tmpdir) -> None:
65"""In `clone()`, if user doesn't want to reclone, Cookiecutter should exit \
66without cloning anything."""
67mocker.patch('cookiecutter.vcs.is_vcs_installed', autospec=True, return_value=True)
68mocker.patch(
69'cookiecutter.vcs.prompt_and_delete', return_value=False, autospec=True
70)
71mock_subprocess = mocker.patch(
72'cookiecutter.vcs.subprocess.check_output',
73autospec=True,
74)
75
76clone_to_dir = tmpdir.mkdir('clone')
77
78# Create repo_dir to trigger prompt_and_delete
79clone_to_dir.mkdir('cookiecutter-pytest-plugin')
80
81repo_url = 'https://github.com/pytest-dev/cookiecutter-pytest-plugin.git'
82
83vcs.clone(repo_url, clone_to_dir=str(clone_to_dir))
84assert not mock_subprocess.called
85
86
87@pytest.mark.parametrize(
88'repo_type, repo_url, repo_name',
89[
90('git', 'https://github.com/hello/world.git', 'world'),
91('hg', 'https://bitbucket.org/foo/bar', 'bar'),
92('git', 'git@host:gitoliterepo', 'gitoliterepo'),
93('git', 'git@gitlab.com:cookiecutter/cookiecutter.git', 'cookiecutter'),
94('git', 'git@github.com:cookiecutter/cookiecutter.git', 'cookiecutter'),
95],
96)
97def test_clone_should_invoke_vcs_command(
98mocker, clone_dir, repo_type, repo_url, repo_name
99) -> None:
100"""When `clone()` is called with a git/hg repo, the corresponding VCS \
101command should be run via `subprocess.check_output()`.
102
103This should take place:
104* In the correct dir
105* With the correct args.
106"""
107mocker.patch('cookiecutter.vcs.is_vcs_installed', autospec=True, return_value=True)
108
109mock_subprocess = mocker.patch(
110'cookiecutter.vcs.subprocess.check_output',
111autospec=True,
112)
113expected_repo_dir = os.path.normpath(os.path.join(clone_dir, repo_name))
114
115branch = 'foobar'
116
117repo_dir = vcs.clone(
118repo_url, checkout=branch, clone_to_dir=clone_dir, no_input=True
119)
120
121assert repo_dir == expected_repo_dir
122
123mock_subprocess.assert_any_call(
124[repo_type, 'clone', repo_url], cwd=clone_dir, stderr=subprocess.STDOUT
125)
126
127branch_info = [branch]
128# We sanitize branch information for Mercurial
129if repo_type == "hg":
130branch_info.insert(0, "--")
131
132mock_subprocess.assert_any_call(
133[repo_type, 'checkout', *branch_info],
134cwd=expected_repo_dir,
135stderr=subprocess.STDOUT,
136)
137
138
139@pytest.mark.parametrize(
140'error_message',
141[
142(b"fatal: repository 'https://github.com/hackebro/cookiedozer' not found"),
143b'hg: abort: HTTP Error 404: Not Found',
144],
145)
146def test_clone_handles_repo_typo(mocker, clone_dir, error_message) -> None:
147"""In `clone()`, repository not found errors should raise an \
148appropriate exception."""
149# side_effect is set to an iterable here (and below),
150# because of a Python 3.4 unittest.mock regression
151# http://bugs.python.org/issue23661
152mocker.patch(
153'cookiecutter.vcs.subprocess.check_output',
154autospec=True,
155side_effect=[subprocess.CalledProcessError(-1, 'cmd', output=error_message)],
156)
157
158repository_url = 'https://github.com/hackebro/cookiedozer'
159with pytest.raises(exceptions.RepositoryNotFound) as err:
160vcs.clone(repository_url, clone_to_dir=str(clone_dir), no_input=True)
161
162assert str(err.value) == (
163f'The repository {repository_url} could not be found, have you made a typo?'
164)
165
166
167@pytest.mark.parametrize(
168'error_message',
169[
170b"error: pathspec 'unknown_branch' did not match any file(s) known to git",
171b"hg: abort: unknown revision 'unknown_branch'!",
172],
173)
174def test_clone_handles_branch_typo(mocker, clone_dir, error_message) -> None:
175"""In `clone()`, branch not found errors should raise an \
176appropriate exception."""
177mocker.patch(
178'cookiecutter.vcs.subprocess.check_output',
179autospec=True,
180side_effect=[subprocess.CalledProcessError(-1, 'cmd', output=error_message)],
181)
182
183repository_url = 'https://github.com/pytest-dev/cookiecutter-pytest-plugin'
184with pytest.raises(exceptions.RepositoryCloneFailed) as err:
185vcs.clone(
186repository_url,
187clone_to_dir=str(clone_dir),
188checkout='unknown_branch',
189no_input=True,
190)
191
192assert str(err.value) == (
193'The unknown_branch branch of repository '
194f'{repository_url} could not found, have you made a typo?'
195)
196
197
198def test_clone_unknown_subprocess_error(mocker, clone_dir) -> None:
199"""In `clone()`, unknown subprocess errors should be raised."""
200mocker.patch(
201'cookiecutter.vcs.subprocess.check_output',
202autospec=True,
203side_effect=[
204subprocess.CalledProcessError(-1, 'cmd', output=b'Something went wrong')
205],
206)
207
208with pytest.raises(subprocess.CalledProcessError):
209vcs.clone(
210'https://github.com/pytest-dev/cookiecutter-pytest-plugin',
211clone_to_dir=str(clone_dir),
212no_input=True,
213)
214