Completed
Push — dry-undefined-variable-excepti... ( d183c8...3df04f )
by Michael
01:19
created

test_ignore_hook_backup_files()   A

Complexity

Conditions 3

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
c 1
b 0
f 0
dl 0
loc 9
rs 9.6666
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
15
from cookiecutter import hooks, utils, exceptions
16
17
18
def make_test_repo(name):
19
    """Helper function which is called in the test setup methods."""
20
    hook_dir = os.path.join(name, 'hooks')
21
    template = os.path.join(name, 'input{{hooks}}')
22
    os.mkdir(name)
23
    os.mkdir(hook_dir)
24
    os.mkdir(template)
25
26
    with open(os.path.join(template, 'README.rst'), 'w') as f:
27
        f.write("foo\n===\n\nbar\n")
28
29
    with open(os.path.join(hook_dir, 'pre_gen_project.py'), 'w') as f:
30
        f.write("#!/usr/bin/env python\n")
31
        f.write("# -*- coding: utf-8 -*-\n")
32
        f.write("from __future__ import print_function\n")
33
        f.write("\n")
34
        f.write("print('pre generation hook')\n")
35
        f.write("f = open('python_pre.txt', 'w')\n")
36
        f.write("f.close()\n")
37
38
    if sys.platform.startswith('win'):
39 View Code Duplication
        post = 'post_gen_project.bat'
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
40
        with open(os.path.join(hook_dir, post), 'w') as f:
41
            f.write("@echo off\n")
42
            f.write("\n")
43
            f.write("echo post generation hook\n")
44
            f.write("echo. >shell_post.txt\n")
45
    else:
46
        post = 'post_gen_project.sh'
47
        filename = os.path.join(hook_dir, post)
48
        with open(filename, 'w') as f:
49
            f.write("#!/bin/bash\n")
50
            f.write("\n")
51
            f.write("echo 'post generation hook';\n")
52
            f.write("touch 'shell_post.txt'\n")
53
        # Set the execute bit
54
        os.chmod(filename, os.stat(filename).st_mode | stat.S_IXUSR)
55
56
    return post
57
58
59
class TestFindHooks(object):
60
61
    repo_path = 'tests/test-hooks'
62
63
    def setup_method(self, method):
64
        self.post_hook = make_test_repo(self.repo_path)
65
66
    def teardown_method(self, method):
67
        utils.rmtree(self.repo_path)
68
69
    def test_find_hook(self):
70
        """Finds the specified hook."""
71
72
        with utils.work_in(self.repo_path):
73
            expected = {
74
                'pre_gen_project': os.path.abspath('hooks/pre_gen_project.py'),
75
                'post_gen_project': os.path.abspath(
76
                    os.path.join('hooks', self.post_hook)
77
                ),
78
            }
79
            assert expected == hooks.find_hooks()
80
81
    def test_no_hooks(self):
82
        """find_hooks should return None if the hook could not be found."""
83
84
        with utils.work_in('tests/fake-repo'):
85
            assert {} == hooks.find_hooks()
86
87
88
class TestExternalHooks(object):
89
90
    repo_path = os.path.abspath('tests/test-hooks/')
91
    hooks_path = os.path.abspath('tests/test-hooks/hooks')
92
93
    def setup_method(self, method):
94
        self.post_hook = make_test_repo(self.repo_path)
95
96
    def teardown_method(self, method):
97
        utils.rmtree(self.repo_path)
98
99
        if os.path.exists('python_pre.txt'):
100
            os.remove('python_pre.txt')
101
        if os.path.exists('shell_post.txt'):
102
            os.remove('shell_post.txt')
103
        if os.path.exists('tests/shell_post.txt'):
104
            os.remove('tests/shell_post.txt')
105
        if os.path.exists('tests/test-hooks/input{{hooks}}/python_pre.txt'):
106
            os.remove('tests/test-hooks/input{{hooks}}/python_pre.txt')
107
        if os.path.exists('tests/test-hooks/input{{hooks}}/shell_post.txt'):
108
            os.remove('tests/test-hooks/input{{hooks}}/shell_post.txt')
109
        if os.path.exists('tests/context_post.txt'):
110
            os.remove('tests/context_post.txt')
111
112
    def test_run_script(self):
113
        """Execute a hook script, independently of project generation"""
114
        hooks.run_script(os.path.join(self.hooks_path, self.post_hook))
115
        assert os.path.isfile('shell_post.txt')
116
117
    def test_run_script_cwd(self):
118
        """Change directory before running hook"""
119
        hooks.run_script(
120
            os.path.join(self.hooks_path, self.post_hook),
121
            'tests'
122
        )
123
        assert os.path.isfile('tests/shell_post.txt')
124
        assert 'tests' not in os.getcwd()
125
126
    def test_run_script_with_context(self):
127
        """Execute a hook script, passing a context"""
128
129
        hook_path = os.path.join(self.hooks_path, 'post_gen_project.sh')
130
131
        if sys.platform.startswith('win'):
132 View Code Duplication
            post = 'post_gen_project.bat'
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
133
            with open(os.path.join(self.hooks_path, post), 'w') as f:
134
                f.write("@echo off\n")
135
                f.write("\n")
136
                f.write("echo post generation hook\n")
137
                f.write("echo. >{{cookiecutter.file}}\n")
138
        else:
139
            with open(hook_path, 'w') as fh:
140
                fh.write("#!/bin/bash\n")
141
                fh.write("\n")
142
                fh.write("echo 'post generation hook';\n")
143
                fh.write("touch 'shell_post.txt'\n")
144
                fh.write("touch '{{cookiecutter.file}}'\n")
145
                os.chmod(hook_path, os.stat(hook_path).st_mode | stat.S_IXUSR)
146
147
        hooks.run_script_with_context(
148
            os.path.join(self.hooks_path, self.post_hook),
149
            'tests',
150
            {
151
                'cookiecutter': {
152
                    'file': 'context_post.txt'
153
                }
154
            })
155
        assert os.path.isfile('tests/context_post.txt')
156
        assert 'tests' not in os.getcwd()
157
158
    def test_run_hook(self):
159
        """Execute hook from specified template in specified output
160
        directory.
161
        """
162
        tests_dir = os.path.join(self.repo_path, 'input{{hooks}}')
163
        with utils.work_in(self.repo_path):
164
            hooks.run_hook('pre_gen_project', tests_dir, {})
165
            assert os.path.isfile(os.path.join(tests_dir, 'python_pre.txt'))
166
167
            hooks.run_hook('post_gen_project', tests_dir, {})
168
            assert os.path.isfile(os.path.join(tests_dir, 'shell_post.txt'))
169
170
    def test_run_failing_hook(self):
171
        hook_path = os.path.join(self.hooks_path, 'pre_gen_project.py')
172
        tests_dir = os.path.join(self.repo_path, 'input{{hooks}}')
173
174
        with open(hook_path, 'w') as f:
175
            f.write("#!/usr/bin/env python\n")
176
            f.write("import sys; sys.exit(1)\n")
177
178
        with utils.work_in(self.repo_path):
179
            with pytest.raises(exceptions.FailedHookException) as excinfo:
180
                hooks.run_hook('pre_gen_project', tests_dir, {})
181
            assert 'Hook script failed' in str(excinfo.value)
182
183
184
@pytest.fixture
185
def hook_backup_files():
186
    """Return basenames of all files in the hooks dir."""
187
    return sorted([
188
        os.path.basename(f) for f in
189
        os.listdir('tests/hooks-backup-files/hooks')
190
    ])
191
192
193
def test_ignore_hook_backup_files(monkeypatch, hook_backup_files):
194
    # This makes sure that the files are actually in the directory
195
    assert hook_backup_files == [
196
        'post_gen_project.py~',
197
        'pre_gen_project.py~',
198
    ]
199
200
    monkeypatch.chdir('tests/hooks-backup-files/')
201
    assert hooks.find_hooks() == {}
202