Passed
Pull Request — master (#1445)
by
unknown
01:16
created

test_not_ok_to_delete_not_ok_to_reuse_results_in_system_exit()   A

Complexity

Conditions 2

Size

Total Lines 20
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 13
dl 0
loc 20
rs 9.75
c 0
b 0
f 0
cc 2
nop 2
1
"""Tests around cloning repositories and detection of errors at it."""
2
import os
3
import subprocess
4
5
import pytest
6
from pathlib import Path
7
8
from cookiecutter import exceptions, vcs
9
10
11
@pytest.fixture
12
def clone_dir(tmpdir):
13
    """Simulate creation of a directory called `clone_dir` inside of `tmpdir`. \
14
    Returns a str to said directory."""
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
    mocker.patch('cookiecutter.vcs.is_vcs_installed', autospec=True, return_value=False)
22
23
    repo_url = 'https://github.com/pytest-dev/cookiecutter-pytest-plugin.git'
24
25
    with pytest.raises(exceptions.VCSNotInstalled):
26
        vcs.clone(repo_url, clone_to_dir=clone_dir)
27
28
29
def test_clone_should_rstrip_trailing_slash_in_repo_url(mocker, clone_dir):
30
    """In `clone()`, repo URL's trailing slash should be stripped if one is \
31
    present."""
32
    mocker.patch('cookiecutter.vcs.is_vcs_installed', autospec=True, return_value=True)
33
34
    mock_subprocess = mocker.patch(
35
        'cookiecutter.vcs.subprocess.check_output', autospec=True,
36
    )
37
38
    vcs.clone('https://github.com/foo/bar/', clone_to_dir=clone_dir, no_input=True)
39
40
    mock_subprocess.assert_called_once_with(
41
        ['git', 'clone', 'https://github.com/foo/bar'],
42
        cwd=clone_dir,
43
        stderr=subprocess.STDOUT,
44
    )
45
46
47
def test_clone_should_abort_if_user_does_not_want_to_reclone(mocker, tmpdir):
48
    """In `clone()`, if user doesn't want to reclone, Cookiecutter should exit \
49
    without cloning anything."""
50
    mocker.patch('cookiecutter.vcs.is_vcs_installed', autospec=True, return_value=True)
51
    mocker.patch(
52
        'cookiecutter.vcs.prompt_ok_to_delete', side_effect=SystemExit, autospec=True
53
    )
54
    mock_subprocess = mocker.patch(
55
        'cookiecutter.vcs.subprocess.check_output', autospec=True,
56
    )
57
58
    clone_to_dir = tmpdir.mkdir('clone')
59
60
    # Create repo_dir to trigger prompt_ok_to_delete
61
    clone_to_dir.mkdir('cookiecutter-pytest-plugin')
62
63
    repo_url = 'https://github.com/pytest-dev/cookiecutter-pytest-plugin.git'
64
65
    with pytest.raises(SystemExit):
66
        vcs.clone(repo_url, clone_to_dir=str(clone_to_dir))
67
    assert not mock_subprocess.called
68
69
70
@pytest.mark.parametrize(
71
    'repo_type, repo_url, repo_name',
72
    [
73
        ('git', 'https://github.com/hello/world.git', 'world'),
74
        ('hg', 'https://bitbucket.org/foo/bar', 'bar'),
75
        ('git', 'git@host:gitoliterepo', 'gitoliterepo'),
76
        ('git', '[email protected]:cookiecutter/cookiecutter.git', 'cookiecutter'),
77
        ('git', '[email protected]:cookiecutter/cookiecutter.git', 'cookiecutter'),
78
    ],
79
)
80
def test_clone_should_invoke_vcs_command(
81
    mocker, clone_dir, repo_type, repo_url, repo_name
82
):
83
    """When `clone()` is called with a git/hg repo, the corresponding VCS \
84
    command should be run via `subprocess.check_output()`.
85
86
    This should take place:
87
    * In the correct dir
88
    * With the correct args.
89
    """
90
    mocker.patch('cookiecutter.vcs.is_vcs_installed', autospec=True, return_value=True)
91
92
    mock_subprocess = mocker.patch(
93
        'cookiecutter.vcs.subprocess.check_output', autospec=True,
94
    )
95
    expected_repo_dir = os.path.normpath(os.path.join(clone_dir, repo_name))
96
97
    branch = 'foobar'
98
99
    repo_dir = vcs.clone(
100
        repo_url, checkout=branch, clone_to_dir=clone_dir, no_input=True
101
    )
102
103
    assert repo_dir == expected_repo_dir
104
105
    mock_subprocess.assert_any_call(
106
        [repo_type, 'clone', repo_url], cwd=clone_dir, stderr=subprocess.STDOUT
107
    )
108
    mock_subprocess.assert_any_call(
109
        [repo_type, 'checkout', branch], cwd=expected_repo_dir, stderr=subprocess.STDOUT
110
    )
111
112
113
@pytest.mark.parametrize(
114
    'error_message',
115
    [
116
        (
117
            "fatal: repository 'https://github.com/hackebro/cookiedozer' not found"
118
        ).encode('utf-8'),
119
        'hg: abort: HTTP Error 404: Not Found'.encode('utf-8'),
120
    ],
121
)
122
def test_clone_handles_repo_typo(mocker, clone_dir, error_message):
123
    """In `clone()`, repository not found errors should raise an \
124
    appropriate exception."""
125
    # side_effect is set to an iterable here (and below),
126
    # because of a Python 3.4 unittest.mock regression
127
    # http://bugs.python.org/issue23661
128
    mocker.patch(
129
        'cookiecutter.vcs.subprocess.check_output',
130
        autospec=True,
131
        side_effect=[subprocess.CalledProcessError(-1, 'cmd', output=error_message)],
132
    )
133
134
    repository_url = 'https://github.com/hackebro/cookiedozer'
135
    with pytest.raises(exceptions.RepositoryNotFound) as err:
136
        vcs.clone(repository_url, clone_to_dir=clone_dir, no_input=True)
137
138
    assert str(err.value) == (
139
        'The repository {} could not be found, have you made a typo?'
140
    ).format(repository_url)
141
142
143
@pytest.mark.parametrize(
144
    'error_message',
145
    [
146
        (
147
            "error: pathspec 'unknown_branch' did not match any file(s) known to git"
148
        ).encode('utf-8'),
149
        "hg: abort: unknown revision 'unknown_branch'!".encode('utf-8'),
150
    ],
151
)
152
def test_clone_handles_branch_typo(mocker, clone_dir, error_message):
153
    """In `clone()`, branch not found errors should raise an \
154
    appropriate exception."""
155
    mocker.patch(
156
        'cookiecutter.vcs.subprocess.check_output',
157
        autospec=True,
158
        side_effect=[subprocess.CalledProcessError(-1, 'cmd', output=error_message)],
159
    )
160
161
    repository_url = 'https://github.com/pytest-dev/cookiecutter-pytest-plugin'
162
    with pytest.raises(exceptions.RepositoryCloneFailed) as err:
163
        vcs.clone(
164
            repository_url,
165
            clone_to_dir=clone_dir,
166
            checkout='unknown_branch',
167
            no_input=True,
168
        )
169
170
    assert str(err.value) == (
171
        'The unknown_branch branch of repository '
172
        '{} could not found, have you made a typo?'
173
    ).format(repository_url)
174
175
176
def test_clone_unknown_subprocess_error(mocker, clone_dir):
177
    """In `clone()`, unknown subprocess errors should be raised."""
178
    mocker.patch(
179
        'cookiecutter.vcs.subprocess.check_output',
180
        autospec=True,
181
        side_effect=[
182
            subprocess.CalledProcessError(
183
                -1, 'cmd', output='Something went wrong'.encode('utf-8')
184
            )
185
        ],
186
    )
187
188
    with pytest.raises(subprocess.CalledProcessError):
189
        vcs.clone(
190
            'https://github.com/pytest-dev/cookiecutter-pytest-plugin',
191
            clone_to_dir=clone_dir,
192
            no_input=True,
193
        )
194
195
196
@pytest.mark.skip(reason='Unable to properly set up mocks')
197
def test_not_ok_to_delete_not_ok_to_reuse_results_in_system_exit(mocker, clone_dir):
198
    """In `clone()`, if the user does not download new version,
199
    and does not reuse old one, then sys.exit() must be called."""
200
    mock_ok_to_delete = mocker.patch(
201
        'cookiecutter.utils.prompt_ok_to_delete', return_value=False,
202
    )
203
204
    mock_ok_to_reuse = mocker.patch(
205
        'cookiecutter.utils.prompt_ok_to_reuse', return_value=False,
206
    )
207
208
    with pytest.raises(SystemExit):
209
        vcs.clone(
210
            'https://github.com/pytest-dev/cookiecutter-pytest-plugin',
211
            clone_to_dir=clone_dir,
212
            no_input=True,
213
        )
214
    assert mock_ok_to_delete.called_once
215
    assert mock_ok_to_reuse.called_once
216
217
218
@pytest.mark.skip(reason='Unable to properly set up mocks and reasonable assertions',)
219
def test_no_ok_to_delete_ok_to_reuse_asked(mocker, clone_dir):
220
    """In `clone()` normal reuse workflow."""
221
    mock_ok_to_delete = mocker.patch(
222
        'cookiecutter.utils.prompt_ok_to_delete', return_value=False,
223
    )
224
225
    mock_ok_to_reuse = mocker.patch(
226
        'cookiecutter.utils.prompt_ok_to_reuse', return_value=True,
227
    )
228
229
    vcs.clone(
230
        'https://github.com/pytest-dev/cookiecutter-pytest-plugin',
231
        clone_to_dir=clone_dir,
232
        no_input=True,
233
    )
234
    assert mock_ok_to_delete.called_once
235
    assert mock_ok_to_reuse.called_once
236
237
238 View Code Duplication
def test_backup_and_delete_repo(mocker, tmpdir):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
239
    """Test that backup works correctly."""
240
    mock_move = mocker.patch('shutil.move', return_value=True)
241
242
    repo_dir = tmpdir.mkdir('repo')
243
    backup_dir = tmpdir.mkdir('backup')
244
245
    subdir = repo_dir.mkdir('subdir')
246
    inner_dir = subdir.mkdir('inner_dir')
247
248
    Path(repo_dir / 'cookiecutter.json').touch()
249
    Path(subdir / 'README.md').touch()
250
    Path(inner_dir / 'some_file.txt').touch()
251
252
    original_files = [
253
        x.relative_to(repo_dir)
254
        for x in sorted(Path(repo_dir).glob('**/*'))
255
        if x.is_file()
256
    ]
257
258
    vcs._backup_and_delete_repo(str(repo_dir), str(backup_dir))
259
    backed_up_files = [
260
        x.relative_to(backup_dir / 'repo')
261
        for x in sorted(Path(backup_dir).glob('**/*'))
262
        if x.is_file()
263
    ]
264
    assert mock_move.called_once
265
    assert original_files == backed_up_files
266
    assert not Path(repo_dir).exists()
267
268
269 View Code Duplication
def test_restore_old_repo_to_existing_directory(mocker, tmpdir):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
270
    """Test that restore works correctly if repo directory exists."""
271
    mock_rmtree = mocker.patch('cookiecutter.utils.rmtree', return_value=True)
272
273
    repo_dir = tmpdir.mkdir('repo')
274
    backup_dir = tmpdir.mkdir('backup').mkdir('repo')
275
276
    subdir = backup_dir.mkdir('subdir')
277
    inner_dir = subdir.mkdir('inner_dir')
278
279
    Path(backup_dir / 'cookiecutter.json').touch()
280
    Path(subdir / 'README.md').touch()
281
    Path(inner_dir / 'some_file.txt').touch()
282
283
    backed_up_files = [
284
        x.relative_to(backup_dir)
285
        for x in sorted(Path(backup_dir).glob('**/*'))
286
        if x.is_file()
287
    ]
288
289
    vcs._restore_old_repo(str(repo_dir), str(backup_dir))
290
    restored_files = [
291
        x.relative_to(repo_dir)
292
        for x in sorted(Path(repo_dir).glob('**/*'))
293
        if x.is_file()
294
    ]
295
    assert backed_up_files == restored_files
296
    assert not Path(backup_dir).exists()
297
    assert mock_rmtree.called_once
298
299
300
def test_restore_old_repo_to_non_existing_directory(mocker, tmpdir):
301
    """Test that restore works correctly to non-existing directory."""
302
    mock_rmtree = mocker.patch('cookiecutter.utils.rmtree', return_value=True)
303
304
    backup_dir = tmpdir.mkdir('backup').mkdir('repo')
305
306
    subdir = backup_dir.mkdir('subdir')
307
    inner_dir = subdir.mkdir('inner_dir')
308
309
    Path(backup_dir / 'cookiecutter.json').touch()
310
    Path(subdir / 'README.md').touch()
311
    Path(inner_dir / 'some_file.txt').touch()
312
313
    backed_up_files = [
314
        x.relative_to(backup_dir)
315
        for x in sorted(Path(backup_dir).glob('**/*'))
316
        if x.is_file()
317
    ]
318
319
    vcs._restore_old_repo(str(tmpdir / 'repo'), str(backup_dir))
320
    restored_files = [
321
        x.relative_to(tmpdir / 'repo')
322
        for x in sorted(Path(tmpdir / 'repo').glob('**/*'))
323
        if x.is_file()
324
    ]
325
    assert backed_up_files == restored_files
326
    assert not Path(backup_dir).exists()
327
    assert mock_rmtree.called_once
328