Completed
Pull Request — master (#694)
by Eric
01:17
created

TestRealHooks.teardown_method()   A

Complexity

Conditions 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 1
c 1
b 1
f 0
dl 0
loc 4
rs 10
1
#!/usr/bin/env python
2
# -*- coding: utf-8 -*-
3
4
"""
5
test_real_hooks_with_context_serialized
6
---------------------------------------
7
8
Additional tests for `cookiecutter.hooks` module.
9
"""
10
11
import os
12
import errno
13
import json
14
import sys
15
import subprocess
16
import pytest
17
18
from cookiecutter import hooks, utils
19
20
21
class TestRealHooks(object):
22
    repo_path = os.path.abspath(
23
        'tests/test-real-hooks-with-serialized-context')
24
    hooks_path = repo_path + '/hooks'
25
26
    def setup_method(self, method):
27
        self.old_popen = subprocess.Popen
28
        self.old_platform = sys.platform
29
        self.old_logging = hooks.logging
30
31
    def teardown_method(self, method):
32
        subprocess.Popen = self.old_popen
33
        sys.platform = self.old_platform
34
        hooks.logging = self.old_logging
35
36
    def run_script_with_context(self, repo_id, context):
37
        """
38
        Helper method to execute hooks.run_script_with_context on a given repo
39
        :param repo_id: repository identifier
40
        :param context: context dictionary
41
        """
42
        return hooks.run_script_with_context(
43
            os.path.join(
44
                self.repo_path,
45
                repo_id,
46
                'hooks',
47
                'pre_gen_project.py'
48
            ),
49
            'tests',
50
            context
51
        )
52
53
    def run_hook(self, repo_id, context, hook_name='pre_gen_project'):
54
        """
55
        Helper method to execute hooks.run_hook on a given repo
56
        :param repo_id: repository identifier
57
        :param context: context dictionary
58
        :param hook_name: hook name to run
59
        """
60
        return hooks.run_hook(
61
            hook_name,
62
            os.path.join(
63
                self.repo_path,
64
                repo_id,
65
                'input{{' + repo_id + '_hooks}}'
66
            ),
67
            context
68
        )
69
70
    def __configure_mock(self, mock_object, configuration):
71
        """
72
        Helper method to configure a given mock object
73
        :param mock_object: the mock to configure
74
        :param configuration: the configuration dictionary
75
        """
76
        omock = mock_object.return_value
77
        omock.configure_mock(**configuration)
78
79
        return omock
80
81
    def test_run_script_with_context_get_updated_context(self):
82
        """
83
        Execute a hook script, passing a serialized context object and
84
        getting the context updated
85
        """
86
        context = {
87
            "my_key": "my_val"
88
        }
89
        expected = {
90
            "my_key": "my_val_updated"
91
        }
92
        actual = self.run_script_with_context('update_context', context)
93
94
        assert actual == expected
95
96
    def test_run_script_with_context_returns_context(self):
97
        """
98
        Execute a hook script, passing a serialized context object
99
        """
100
        context = {
101
            "my_key": "my_val"
102
        }
103
        actual = self.run_script_with_context('simple', context)
104
105
        assert actual == context
106
107
    def test_run_hook_returns_context(self):
108
        """
109
        Execute a hook script, passing a serialized context object
110
        """
111
        context = {
112
            "my_key": "my_val"
113
        }
114
115
        with utils.work_in(os.path.join(self.repo_path, 'simple')):
116
            actual = self.run_hook('simple', context)
117
118
            assert actual == context
119
120
        with utils.work_in(os.path.join(self.repo_path, 'update_context')):
121
            expected = {
122
                "my_key": "my_val_updated"
123
            }
124
            actual = self.run_hook('update_context', context)
125
126
            assert actual == expected
127
128
        with utils.work_in(os.path.join(self.repo_path, 'update_context')):
129
            actual = self.run_hook(
130
                'update_context',
131
                context,
132
                'not_handled_hook'
133
            )
134
135
            assert actual == context
136
137
    def test_run_script_with_context_runs_hook_in_place(self):
138
        """
139
        Execute a hook script in place, passing a serialized context object
140
        """
141
        hook = os.path.join(
142
            self.repo_path,
143
            'inplace',
144
            'hooks',
145
            'pre_gen_project.py'
146
        )
147
        context = {
148
            "_run_hook_in_place": True
149
        }
150
        expected = {
151
            "_run_hook_in_place": True,
152
            "inplace": hook
153
        }
154
        actual = hooks.run_script_with_context(hook, 'tests', context)
155
156
        assert actual == expected
157
158
    def test_getting_bad_json_returns_original_context(self):
159
        """
160
        Execute a hook script that returns bad json
161
        """
162
        context = {
163
            "my_key": "my_val",
164
        }
165
166
        actual = self.run_script_with_context('bad_json', context)
167
168
        assert actual == context
169
170
    def test_handle_lost_stdin_during_communication_on_windows_os(
171
        self, mocker
172
    ):
173
        """
174
        Ensure that an OSError raised from Popen._stdin_write is correctly
175
        caught and logged, while not blocking the process on windows OS
176
        :param mocker: mock library handler
177
        """
178
        mock_popen = mocker.patch('subprocess.Popen', autospec=True)
179
        mock_logging = mocker.patch('cookiecutter.hooks.logging')
180
181
        context = {
182
            "my_key": "my_val"
183
        }
184
185
        self.__configure_mock(
186
            mock_popen,
187
            {
188
                'communicate.side_effect': OSError(
189
                    errno.EINVAL, 'Invalid Argument'
190
                ),
191
                'communicate.return_value': json.dumps(context).encode(),
192
                'wait.return_value': 0
193
            }
194
        )
195
196
        sys.platform = 'win32'
197
198
        actual = self.run_script_with_context('simple', context)
199
200
        mock_logging.warn.assert_called_with(
201
            'Popen.communicate failed certainly ' +
202
            'because of the issue #19612'
203
        )
204
        assert actual == context
205
206
    def test_handle_oserror_during_communication_on_non_windows_os(
207
        self, mocker
208
    ):
209
        """
210
        Ensure that an OSError raised on a non windows os is bubbled up
211
        :param mocker: mock library handler
212
        """
213
        mock_popen = mocker.patch('subprocess.Popen', autospec=True)
214
        self.__configure_mock(
215
            mock_popen,
216
            {
217
                'communicate.side_effect': OSError(
218
                    errno.EINVAL, 'Invalid Argument'
219
                )
220
            }
221
        )
222
223
        sys.platform = 'linux2'
224
225
        with pytest.raises(OSError) as excinfo:
226
            self.run_script_with_context('simple', {})
227
228
            assert excinfo.value.errno == errno.EINVAL
229