Completed
Pull Request — master (#694)
by Eric
01:15
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 test_run_script_with_context_get_updated_context(self):
37
        """
38
        Execute a hook script, passing a serialized context object and
39
        getting the context updated
40
        """
41
        context = {
42
            "my_key": "my_val"
43
        }
44
        expected = {
45
            "my_key": "my_val_updated"
46
        }
47
        actual = hooks.run_script_with_context(
48
            os.path.join(
49
                self.repo_path,
50
                'update_context',
51
                'hooks',
52
                'pre_gen_project.py'
53
            ),
54
            'tests',
55
            context
56
        )
57
58
        assert actual == expected
59
60
    def test_run_script_with_context_returns_context(self):
61
        """
62
        Execute a hook script, passing a serialized context object
63
        """
64
        context = {
65
            "my_key": "my_val"
66
        }
67
        actual = hooks.run_script_with_context(
68
            os.path.join(
69
                self.repo_path, 'simple', 'hooks', 'pre_gen_project.py'),
70
            'tests',
71
            context
72
        )
73
74
        assert actual == context
75
76
    def test_run_hook_returns_context(self):
77
        """
78
        Execute a hook script, passing a serialized context object
79
        """
80
        context = {
81
            "my_key": "my_val"
82
        }
83
84
        with utils.work_in(os.path.join(self.repo_path, 'simple')):
85
            actual = hooks.run_hook(
86
                'pre_gen_project',
87
                os.path.join(
88
                    self.repo_path,
89
                    'simple',
90
                    'input{{simple_hooks}}'
91
                ),
92
                context
93
            )
94
            assert actual == context
95
96
        with utils.work_in(os.path.join(self.repo_path, 'update_context')):
97
            expected = {
98
                "my_key": "my_val_updated"
99
            }
100
            actual = hooks.run_hook(
101
                'pre_gen_project',
102
                os.path.join(
103
                    self.repo_path,
104
                    'update_context',
105
                    'input{{update_context_hooks}}'
106
                ),
107
                context
108
            )
109
            assert actual == expected
110
111
        with utils.work_in(os.path.join(self.repo_path, 'simple')):
112
            actual = hooks.run_hook(
113
                'not_handled_hook',
114
                os.path.join(
115
                    self.repo_path,
116
                    'simple',
117
                    'input{{simple_hooks}}'
118
                ),
119
                context
120
            )
121
            assert actual == context
122
123
    def test_run_script_with_context_runs_hook_in_place(self):
124
        """
125
        Execute a hook script in place, passing a serialized context object
126
        """
127
        hook = os.path.join(
128
            self.repo_path,
129
            'inplace',
130
            'hooks',
131
            'pre_gen_project.py'
132
        )
133
        context = {
134
            "_run_hook_in_place": True
135
        }
136
        expected = {
137
            "_run_hook_in_place": True,
138
            "inplace": hook
139
        }
140
        actual = hooks.run_script_with_context(hook, 'tests', context)
141
142
        assert actual == expected
143
144
    def test_getting_bad_json_returns_original_context(self):
145
        """
146
        Execute a hook script that returns bad json
147
        """
148
        context = {
149
            "my_key": "my_val",
150
        }
151
152
        actual = hooks.run_script_with_context(
153
            os.path.join(
154
                self.repo_path,
155
                'bad_json',
156
                'hooks',
157
                'pre_gen_project.py'
158
            ),
159
            'tests',
160
            context
161
        )
162
163
        assert actual == context
164
165
    @mock.patch('subprocess.Popen', autospec=True)
166
    def test_handle_lost_stdin_during_communication_on_windows_os(
167
        self, mock_popen
168
    ):
169
        """
170
        Ensure that an OSError raised from Popen._stdin_write is correctly
171
        caught and logged, while not blocking the process on windows OS
172
        """
173
        context = {
174
            "my_key": "my_val"
175
        }
176
177
        log = LogCapture()
178
179
        proc = mock_popen.return_value
180
        proc.communicate.side_effect = OSError(
181
            errno.EINVAL, 'Invalid Argument'
182
        )
183
        proc.communicate.return_value = json.dumps(context).encode()
184
        proc.wait.return_value = 0
185
186
        patcher_platform = mock.patch('sys.platform', new=mock.MagicMock())
187
        platform = patcher_platform.start()
188
        args = {'startswith.return_value': True}
189
        platform.configure_mock(**args)
190
191
        try:
192
            actual = hooks.run_script_with_context(
193
                os.path.join(
194
                    self.repo_path, 'simple', 'hooks', 'pre_gen_project.py'),
195
                'tests',
196
                context
197
            )
198
199
            log.check(
200
                ('root', 'WARNING', 'Popen.communicate failed certainly ' +
201
                    'because of the issue #19612')
202
            )
203
            assert actual == context
204
205
        finally:
206
            patcher_platform.stop()
207
208
    @mock.patch('subprocess.Popen', autospec=True)
209
    def test_handle_oserror_during_communication_on_non_windows_os(
210
        self, mock_popen
211
    ):
212
        """
213
        Ensure that an OSError raised on a non windows os is bubbled up
214
        """
215
        proc = mock_popen.return_value
216
        proc.communicate.side_effect = OSError(
217
            errno.EINVAL, 'Invalid Argument'
218
        )
219
220
        patcher_platform = mock.patch('sys.platform', new=mock.MagicMock())
221
        platform = patcher_platform.start()
222
        args = {'startswith.return_value': False}
223
        platform.configure_mock(**args)
224
225
        try:
226
            with ShouldRaise() as s:
227
                hooks.run_script_with_context(
228
                    os.path.join(
229
                        self.repo_path,
230
                        'simple',
231
                        'hooks',
232
                        'pre_gen_project.py'
233
                    ),
234
                    'tests',
235
                    {}
236
                )
237
                assert s.raised.code == errno.EINVAL
238
239
        finally:
240
            patcher_platform.stop()
241