tests.test_hooks   A
last analyzed

Complexity

Total Complexity 38

Size/Duplication

Total Lines 236
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 38
eloc 152
dl 0
loc 236
rs 9.36
c 0
b 0
f 0

3 Functions

Rating   Name   Duplication   Size   Complexity  
B make_test_repo() 0 39 6
A dir_with_hooks() 0 32 1
A test_ignore_hook_backup_files() 0 6 1

13 Methods

Rating   Name   Duplication   Size   Complexity  
A TestFindHooks.teardown_method() 0 3 1
A TestFindHooks.setup_method() 0 3 1
A TestFindHooks.test_find_hook() 0 10 2
A TestFindHooks.test_no_hooks() 0 4 2
A TestExternalHooks.test_run_hook() 0 10 2
A TestExternalHooks.test_run_script() 0 4 1
A TestExternalHooks.test_run_failing_hook() 0 13 4
A TestExternalHooks.setup_method() 0 3 1
B TestExternalHooks.teardown_method() 0 16 7
A TestExternalHooks.test_run_script_with_context() 0 30 4
A TestExternalHooks.test_run_script_cwd() 0 8 1
A TestFindHooks.test_hook_not_found() 0 4 2
A TestFindHooks.test_unknown_hooks_dir() 0 7 2
1
# -*- coding: utf-8 -*-
2
3
"""Tests for `cookiecutter.hooks` module."""
4
5
import os
6
import pytest
7
import stat
8
import sys
9
import textwrap
10
11
from cookiecutter import hooks, utils, exceptions
12
13
14
def make_test_repo(name):
15
    """Create test repository for test setup methods."""
16
    hook_dir = os.path.join(name, 'hooks')
17
    template = os.path.join(name, 'input{{hooks}}')
18
    os.mkdir(name)
19
    os.mkdir(hook_dir)
20
    os.mkdir(template)
21
22
    with open(os.path.join(template, 'README.rst'), 'w') as f:
23
        f.write("foo\n===\n\nbar\n")
24
25
    with open(os.path.join(hook_dir, 'pre_gen_project.py'), 'w') as f:
26
        f.write("#!/usr/bin/env python\n")
27
        f.write("# -*- coding: utf-8 -*-\n")
28
        f.write("from __future__ import print_function\n")
29
        f.write("\n")
30
        f.write("print('pre generation hook')\n")
31
        f.write("f = open('python_pre.txt', 'w')\n")
32
        f.write("f.close()\n")
33
34
    if sys.platform.startswith('win'):
35
        post = 'post_gen_project.bat'
36
        with open(os.path.join(hook_dir, post), 'w') as f:
37
            f.write("@echo off\n")
38
            f.write("\n")
39
            f.write("echo post generation hook\n")
40
            f.write("echo. >shell_post.txt\n")
41
    else:
42
        post = 'post_gen_project.sh'
43
        filename = os.path.join(hook_dir, post)
44
        with open(filename, 'w') as f:
45
            f.write("#!/bin/bash\n")
46
            f.write("\n")
47
            f.write("echo 'post generation hook';\n")
48
            f.write("touch 'shell_post.txt'\n")
49
        # Set the execute bit
50
        os.chmod(filename, os.stat(filename).st_mode | stat.S_IXUSR)
51
52
    return post
53
54
55
class TestFindHooks(object):
56
    """Class to unite find hooks related tests in one place."""
57
58
    repo_path = 'tests/test-hooks'
59
60
    def setup_method(self, method):
61
        """Find hooks related tests setup fixture."""
62
        self.post_hook = make_test_repo(self.repo_path)
63
64
    def teardown_method(self, method):
65
        """Find hooks related tests teardown fixture."""
66
        utils.rmtree(self.repo_path)
67
68
    def test_find_hook(self):
69
        """Finds the specified hook."""
70
        with utils.work_in(self.repo_path):
71
            expected_pre = os.path.abspath('hooks/pre_gen_project.py')
72
            actual_hook_path = hooks.find_hook('pre_gen_project')
73
            assert expected_pre == actual_hook_path
74
75
            expected_post = os.path.abspath('hooks/{}'.format(self.post_hook))
76
            actual_hook_path = hooks.find_hook('post_gen_project')
77
            assert expected_post == actual_hook_path
78
79
    def test_no_hooks(self):
80
        """`find_hooks` should return None if the hook could not be found."""
81
        with utils.work_in('tests/fake-repo'):
82
            assert None is hooks.find_hook('pre_gen_project')
83
84
    def test_unknown_hooks_dir(self):
85
        """`find_hooks` should return None if hook directory not found."""
86
        with utils.work_in(self.repo_path):
87
            assert hooks.find_hook(
88
                'pre_gen_project',
89
                hooks_dir='hooks_dir'
90
            ) is None
91
92
    def test_hook_not_found(self):
93
        """`find_hooks` should return None if the hook could not be found."""
94
        with utils.work_in(self.repo_path):
95
            assert hooks.find_hook('unknown_hook') is None
96
97
98
class TestExternalHooks(object):
99
    """Class to unite tests for hooks with different project paths."""
100
101
    repo_path = os.path.abspath('tests/test-hooks/')
102
    hooks_path = os.path.abspath('tests/test-hooks/hooks')
103
104
    def setup_method(self, method):
105
        """External hooks related tests setup fixture."""
106
        self.post_hook = make_test_repo(self.repo_path)
107
108
    def teardown_method(self, method):
109
        """External hooks related tests teardown fixture."""
110
        utils.rmtree(self.repo_path)
111
112
        if os.path.exists('python_pre.txt'):
113
            os.remove('python_pre.txt')
114
        if os.path.exists('shell_post.txt'):
115
            os.remove('shell_post.txt')
116
        if os.path.exists('tests/shell_post.txt'):
117
            os.remove('tests/shell_post.txt')
118
        if os.path.exists('tests/test-hooks/input{{hooks}}/python_pre.txt'):
119
            os.remove('tests/test-hooks/input{{hooks}}/python_pre.txt')
120
        if os.path.exists('tests/test-hooks/input{{hooks}}/shell_post.txt'):
121
            os.remove('tests/test-hooks/input{{hooks}}/shell_post.txt')
122
        if os.path.exists('tests/context_post.txt'):
123
            os.remove('tests/context_post.txt')
124
125
    def test_run_script(self):
126
        """Execute a hook script, independently of project generation."""
127
        hooks.run_script(os.path.join(self.hooks_path, self.post_hook))
128
        assert os.path.isfile('shell_post.txt')
129
130
    def test_run_script_cwd(self):
131
        """Change directory before running hook."""
132
        hooks.run_script(
133
            os.path.join(self.hooks_path, self.post_hook),
134
            'tests'
135
        )
136
        assert os.path.isfile('tests/shell_post.txt')
137
        assert 'tests' not in os.getcwd()
138
139
    def test_run_script_with_context(self):
140
        """Execute a hook script, passing a context."""
141
        hook_path = os.path.join(self.hooks_path, 'post_gen_project.sh')
142
143
        if sys.platform.startswith('win'):
144
            post = 'post_gen_project.bat'
145
            with open(os.path.join(self.hooks_path, post), 'w') as f:
146
                f.write("@echo off\n")
147
                f.write("\n")
148
                f.write("echo post generation hook\n")
149
                f.write("echo. >{{cookiecutter.file}}\n")
150
        else:
151
            with open(hook_path, 'w') as fh:
152
                fh.write("#!/bin/bash\n")
153
                fh.write("\n")
154
                fh.write("echo 'post generation hook';\n")
155
                fh.write("touch 'shell_post.txt'\n")
156
                fh.write("touch '{{cookiecutter.file}}'\n")
157
                os.chmod(hook_path, os.stat(hook_path).st_mode | stat.S_IXUSR)
158
159
        hooks.run_script_with_context(
160
            os.path.join(self.hooks_path, self.post_hook),
161
            'tests',
162
            {
163
                'cookiecutter': {
164
                    'file': 'context_post.txt'
165
                }
166
            })
167
        assert os.path.isfile('tests/context_post.txt')
168
        assert 'tests' not in os.getcwd()
169
170
    def test_run_hook(self):
171
        """Execute hook from specified template in specified output \
172
        directory."""
173
        tests_dir = os.path.join(self.repo_path, 'input{{hooks}}')
174
        with utils.work_in(self.repo_path):
175
            hooks.run_hook('pre_gen_project', tests_dir, {})
176
            assert os.path.isfile(os.path.join(tests_dir, 'python_pre.txt'))
177
178
            hooks.run_hook('post_gen_project', tests_dir, {})
179
            assert os.path.isfile(os.path.join(tests_dir, 'shell_post.txt'))
180
181
    def test_run_failing_hook(self):
182
        """Test correct exception raise if hook exit code is not zero."""
183
        hook_path = os.path.join(self.hooks_path, 'pre_gen_project.py')
184
        tests_dir = os.path.join(self.repo_path, 'input{{hooks}}')
185
186
        with open(hook_path, 'w') as f:
187
            f.write("#!/usr/bin/env python\n")
188
            f.write("import sys; sys.exit(1)\n")
189
190
        with utils.work_in(self.repo_path):
191
            with pytest.raises(exceptions.FailedHookException) as excinfo:
192
                hooks.run_hook('pre_gen_project', tests_dir, {})
193
            assert 'Hook script failed' in str(excinfo.value)
194
195
196
@pytest.fixture()
197
def dir_with_hooks(tmpdir):
198
    """Yield a directory that contains hook backup files."""
199
    hooks_dir = tmpdir.mkdir('hooks')
200
201
    pre_hook_content = textwrap.dedent(
202
        u"""
203
        #!/usr/bin/env python
204
        # -*- coding: utf-8 -*-
205
        print('pre_gen_project.py~')
206
        """
207
    )
208
    pre_gen_hook_file = hooks_dir / 'pre_gen_project.py~'
209
    pre_gen_hook_file.write_text(pre_hook_content, encoding='utf8')
210
211
    post_hook_content = textwrap.dedent(
212
        u"""
213
        #!/usr/bin/env python
214
        # -*- coding: utf-8 -*-
215
        print('post_gen_project.py~')
216
        """
217
    )
218
219
    post_gen_hook_file = hooks_dir / 'post_gen_project.py~'
220
    post_gen_hook_file.write_text(post_hook_content, encoding='utf8')
221
222
    # Make sure to yield the parent directory as `find_hooks()`
223
    # looks into `hooks/` in the current working directory
224
    yield str(tmpdir)
225
226
    pre_gen_hook_file.remove()
227
    post_gen_hook_file.remove()
228
229
230
def test_ignore_hook_backup_files(monkeypatch, dir_with_hooks):
231
    """Test `find_hook` correctly use `valid_hook` verification function."""
232
    # Change the current working directory that contains `hooks/`
233
    monkeypatch.chdir(dir_with_hooks)
234
    assert hooks.find_hook('pre_gen_project') is None
235
    assert hooks.find_hook('post_gen_project') is None
236