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

TestRealHooks.setup_method()   A

Complexity

Conditions 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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