Completed
Pull Request — master (#974)
by
unknown
40s
created

TestExternalHooks.setup_method()   A

Complexity

Conditions 1

Size

Total Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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