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

TestRealHooks.setup_method()   A

Complexity

Conditions 1

Size

Total Lines 3

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 3
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
18
from cookiecutter import hooks, utils
19
from testfixtures import LogCapture, ShouldRaise
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
31
    def teardown_method(self, method):
32
        LogCapture.uninstall_all()
33
        subprocess.Popen = self.old_popen
34
        sys.platform = self.old_platform
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 __sys_platform_patcher(self, startswith):
71
        """
72
        Helper factory method to create a mock patcher of sys.platform
73
        :param startswith: value that should be returned by
74
                           sys.platform.startswith
75
        """
76
        patcher = mock.patch('sys.platform', new=mock.MagicMock())
77
        omock = patcher.start()
78
        args = {'startswith.return_value': startswith}
79
        omock.configure_mock(**args)
80
81
        return patcher
82
83
    def __configure_mock(self, mock_object, configuration):
84
        """
85
        Helper method to configure a given mock object
86
        :param mock_object: the mock to configure
87
        :param configuration: the configuration dictionary
88
        """
89
        omock = mock_object.return_value
90
        omock.configure_mock(**configuration)
91
92
        return omock
93
94
    def test_run_script_with_context_get_updated_context(self):
95
        """
96
        Execute a hook script, passing a serialized context object and
97
        getting the context updated
98
        """
99
        context = {
100
            "my_key": "my_val"
101
        }
102
        expected = {
103
            "my_key": "my_val_updated"
104
        }
105
        actual = self.run_script_with_context('update_context', context)
106
107
        assert actual == expected
108
109
    def test_run_script_with_context_returns_context(self):
110
        """
111
        Execute a hook script, passing a serialized context object
112
        """
113
        context = {
114
            "my_key": "my_val"
115
        }
116
        actual = self.run_script_with_context('simple', context)
117
118
        assert actual == context
119
120
    def test_run_hook_returns_context(self):
121
        """
122
        Execute a hook script, passing a serialized context object
123
        """
124
        context = {
125
            "my_key": "my_val"
126
        }
127
128
        with utils.work_in(os.path.join(self.repo_path, 'simple')):
129
            actual = self.run_hook('simple', context)
130
131
            assert actual == context
132
133
        with utils.work_in(os.path.join(self.repo_path, 'update_context')):
134
            expected = {
135
                "my_key": "my_val_updated"
136
            }
137
            actual = self.run_hook('update_context', context)
138
139
            assert actual == expected
140
141
        with utils.work_in(os.path.join(self.repo_path, 'update_context')):
142
            actual = self.run_hook(
143
                'update_context',
144
                context,
145
                'not_handled_hook'
146
            )
147
148
            assert actual == context
149
150
    def test_run_script_with_context_runs_hook_in_place(self):
151
        """
152
        Execute a hook script in place, passing a serialized context object
153
        """
154
        hook = os.path.join(
155
            self.repo_path,
156
            'inplace',
157
            'hooks',
158
            'pre_gen_project.py'
159
        )
160
        context = {
161
            "_run_hook_in_place": True
162
        }
163
        expected = {
164
            "_run_hook_in_place": True,
165
            "inplace": hook
166
        }
167
        actual = hooks.run_script_with_context(hook, 'tests', context)
168
169
        assert actual == expected
170
171
    def test_getting_bad_json_returns_original_context(self):
172
        """
173
        Execute a hook script that returns bad json
174
        """
175
        context = {
176
            "my_key": "my_val",
177
        }
178
179
        actual = self.run_script_with_context('bad_json', context)
180
181
        assert actual == context
182
183
    @mock.patch('subprocess.Popen', autospec=True)
184
    def test_handle_lost_stdin_during_communication_on_windows_os(
185
        self, mock_popen
186
    ):
187
        """
188
        Ensure that an OSError raised from Popen._stdin_write is correctly
189
        caught and logged, while not blocking the process on windows OS
190
        :param mock_popen: subprocess.Poper mock
191
        """
192
        context = {
193
            "my_key": "my_val"
194
        }
195
196
        log = LogCapture()
197
198
        self.__configure_mock(
199
            mock_popen,
200
            {
201
                'communicate.side_effect': OSError(
202
                    errno.EINVAL, 'Invalid Argument'
203
                ),
204
                'communicate.return_value': json.dumps(context).encode(),
205
                'wait.return_value': 0
206
            }
207
        )
208
209
        patcher_platform = self.__sys_platform_patcher(True)
210
211
        try:
212
            actual = self.run_script_with_context('simple', context)
213
214
            log.check(
215
                ('root', 'WARNING', 'Popen.communicate failed certainly ' +
216
                    'because of the issue #19612')
217
            )
218
219
            assert actual == context
220
221
        finally:
222
            patcher_platform.stop()
223
224
    @mock.patch('subprocess.Popen', autospec=True)
225
    def test_handle_oserror_during_communication_on_non_windows_os(
226
        self, mock_popen
227
    ):
228
        """
229
        Ensure that an OSError raised on a non windows os is bubbled up
230
        :param mock_popen: subprocess.Poper mock
231
        """
232
        self.__configure_mock(
233
            mock_popen,
234
            {
235
                'communicate.side_effect': OSError(
236
                    errno.EINVAL, 'Invalid Argument'
237
                )
238
            }
239
        )
240
241
        patcher_platform = self.__sys_platform_patcher(False)
242
243
        try:
244
            with ShouldRaise() as s:
245
                self.run_script_with_context('simple', {})
246
247
                assert s.raised.code == errno.EINVAL
248
249
        finally:
250
            patcher_platform.stop()
251