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