Passed
Push — master ( b1f642...d6037b )
by
unknown
01:06
created

test_clone_should_silent_exit_if_ok_to_reuse()   A

Complexity

Conditions 1

Size

Total Lines 20
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

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