##// END OF EJS Templates
tests: fixed tests for the added extra traceback with exceptions for vcsserver.
marcink -
r1459:8e46a311 default
parent child Browse files
Show More
@@ -1,319 +1,321 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import json
22 22 import logging
23 23 from StringIO import StringIO
24 24
25 25 import mock
26 26 import pytest
27 27
28 28 from rhodecode.lib import hooks_daemon
29 29 from rhodecode.tests.utils import assert_message_in_log
30 30
31 31
32 32 class TestDummyHooksCallbackDaemon(object):
33 33 def test_hooks_module_path_set_properly(self):
34 34 daemon = hooks_daemon.DummyHooksCallbackDaemon()
35 35 assert daemon.hooks_module == 'rhodecode.lib.hooks_daemon'
36 36
37 37 def test_logs_entering_the_hook(self):
38 38 daemon = hooks_daemon.DummyHooksCallbackDaemon()
39 39 with mock.patch.object(hooks_daemon.log, 'debug') as log_mock:
40 40 with daemon as return_value:
41 41 log_mock.assert_called_once_with(
42 42 'Running dummy hooks callback daemon')
43 43 assert return_value == daemon
44 44
45 45 def test_logs_exiting_the_hook(self):
46 46 daemon = hooks_daemon.DummyHooksCallbackDaemon()
47 47 with mock.patch.object(hooks_daemon.log, 'debug') as log_mock:
48 48 with daemon:
49 49 pass
50 50 log_mock.assert_called_with('Exiting dummy hooks callback daemon')
51 51
52 52
53 53 class TestHooks(object):
54 54 def test_hooks_can_be_used_as_a_context_processor(self):
55 55 hooks = hooks_daemon.Hooks()
56 56 with hooks as return_value:
57 57 pass
58 58 assert hooks == return_value
59 59
60 60
61 61 class TestHooksHttpHandler(object):
62 62 def test_read_request_parses_method_name_and_arguments(self):
63 63 data = {
64 64 'method': 'test',
65 65 'extras': {
66 66 'param1': 1,
67 67 'param2': 'a'
68 68 }
69 69 }
70 70 request = self._generate_post_request(data)
71 71 hooks_patcher = mock.patch.object(
72 72 hooks_daemon.Hooks, data['method'], create=True, return_value=1)
73 73
74 74 with hooks_patcher as hooks_mock:
75 75 MockServer(hooks_daemon.HooksHttpHandler, request)
76 76
77 77 hooks_mock.assert_called_once_with(data['extras'])
78 78
79 79 def test_hooks_serialized_result_is_returned(self):
80 80 request = self._generate_post_request({})
81 81 rpc_method = 'test'
82 82 hook_result = {
83 83 'first': 'one',
84 84 'second': 2
85 85 }
86 86 read_patcher = mock.patch.object(
87 87 hooks_daemon.HooksHttpHandler, '_read_request',
88 88 return_value=(rpc_method, {}))
89 89 hooks_patcher = mock.patch.object(
90 90 hooks_daemon.Hooks, rpc_method, create=True,
91 91 return_value=hook_result)
92 92
93 93 with read_patcher, hooks_patcher:
94 94 server = MockServer(hooks_daemon.HooksHttpHandler, request)
95 95
96 96 expected_result = json.dumps(hook_result)
97 97 assert server.request.output_stream.buflist[-1] == expected_result
98 98
99 99 def test_exception_is_returned_in_response(self):
100 100 request = self._generate_post_request({})
101 101 rpc_method = 'test'
102 102 read_patcher = mock.patch.object(
103 103 hooks_daemon.HooksHttpHandler, '_read_request',
104 104 return_value=(rpc_method, {}))
105 105 hooks_patcher = mock.patch.object(
106 106 hooks_daemon.Hooks, rpc_method, create=True,
107 107 side_effect=Exception('Test exception'))
108 108
109 109 with read_patcher, hooks_patcher:
110 110 server = MockServer(hooks_daemon.HooksHttpHandler, request)
111 111
112 expected_result = json.dumps({
112 org_exc = json.loads(server.request.output_stream.buflist[-1])
113 expected_result = {
113 114 'exception': 'Exception',
114 'exception_args': ('Test exception', )
115 })
116 assert server.request.output_stream.buflist[-1] == expected_result
115 'exception_traceback': org_exc['exception_traceback'],
116 'exception_args': ['Test exception']
117 }
118 assert org_exc == expected_result
117 119
118 120 def test_log_message_writes_to_debug_log(self, caplog):
119 121 ip_port = ('0.0.0.0', 8888)
120 122 handler = hooks_daemon.HooksHttpHandler(
121 123 MockRequest('POST /'), ip_port, mock.Mock())
122 124 fake_date = '1/Nov/2015 00:00:00'
123 125 date_patcher = mock.patch.object(
124 126 handler, 'log_date_time_string', return_value=fake_date)
125 127 with date_patcher, caplog.at_level(logging.DEBUG):
126 128 handler.log_message('Some message %d, %s', 123, 'string')
127 129
128 130 expected_message = '{} - - [{}] Some message 123, string'.format(
129 131 ip_port[0], fake_date)
130 132 assert_message_in_log(
131 133 caplog.records, expected_message,
132 134 levelno=logging.DEBUG, module='hooks_daemon')
133 135
134 136 def _generate_post_request(self, data):
135 137 payload = json.dumps(data)
136 138 return 'POST / HTTP/1.0\nContent-Length: {}\n\n{}'.format(
137 139 len(payload), payload)
138 140
139 141
140 142 class ThreadedHookCallbackDaemon(object):
141 143 def test_constructor_calls_prepare(self):
142 144 prepare_daemon_patcher = mock.patch.object(
143 145 hooks_daemon.ThreadedHookCallbackDaemon, '_prepare')
144 146 with prepare_daemon_patcher as prepare_daemon_mock:
145 147 hooks_daemon.ThreadedHookCallbackDaemon()
146 148 prepare_daemon_mock.assert_called_once_with()
147 149
148 150 def test_run_is_called_on_context_start(self):
149 151 patchers = mock.patch.multiple(
150 152 hooks_daemon.ThreadedHookCallbackDaemon,
151 153 _run=mock.DEFAULT, _prepare=mock.DEFAULT, __exit__=mock.DEFAULT)
152 154
153 155 with patchers as mocks:
154 156 daemon = hooks_daemon.ThreadedHookCallbackDaemon()
155 157 with daemon as daemon_context:
156 158 pass
157 159 mocks['_run'].assert_called_once_with()
158 160 assert daemon_context == daemon
159 161
160 162 def test_stop_is_called_on_context_exit(self):
161 163 patchers = mock.patch.multiple(
162 164 hooks_daemon.ThreadedHookCallbackDaemon,
163 165 _run=mock.DEFAULT, _prepare=mock.DEFAULT, _stop=mock.DEFAULT)
164 166
165 167 with patchers as mocks:
166 168 daemon = hooks_daemon.ThreadedHookCallbackDaemon()
167 169 with daemon as daemon_context:
168 170 assert mocks['_stop'].call_count == 0
169 171
170 172 mocks['_stop'].assert_called_once_with()
171 173 assert daemon_context == daemon
172 174
173 175
174 176 class TestHttpHooksCallbackDaemon(object):
175 177 def test_prepare_inits_daemon_variable(self, tcp_server, caplog):
176 178 with self._tcp_patcher(tcp_server), caplog.at_level(logging.DEBUG):
177 179 daemon = hooks_daemon.HttpHooksCallbackDaemon()
178 180 assert daemon._daemon == tcp_server
179 181
180 182 assert_message_in_log(
181 183 caplog.records,
182 184 'Preparing callback daemon and registering hook object',
183 185 levelno=logging.DEBUG, module='hooks_daemon')
184 186
185 187 def test_prepare_inits_hooks_uri_and_logs_it(
186 188 self, tcp_server, caplog):
187 189 with self._tcp_patcher(tcp_server), caplog.at_level(logging.DEBUG):
188 190 daemon = hooks_daemon.HttpHooksCallbackDaemon()
189 191
190 192 _, port = tcp_server.server_address
191 193 expected_uri = '{}:{}'.format(daemon.IP_ADDRESS, port)
192 194 assert daemon.hooks_uri == expected_uri
193 195
194 196 assert_message_in_log(
195 197 caplog.records, 'Hooks uri is: {}'.format(expected_uri),
196 198 levelno=logging.DEBUG, module='hooks_daemon')
197 199
198 200 def test_run_creates_a_thread(self, tcp_server):
199 201 thread = mock.Mock()
200 202
201 203 with self._tcp_patcher(tcp_server):
202 204 daemon = hooks_daemon.HttpHooksCallbackDaemon()
203 205
204 206 with self._thread_patcher(thread) as thread_mock:
205 207 daemon._run()
206 208
207 209 thread_mock.assert_called_once_with(
208 210 target=tcp_server.serve_forever,
209 211 kwargs={'poll_interval': daemon.POLL_INTERVAL})
210 212 assert thread.daemon is True
211 213 thread.start.assert_called_once_with()
212 214
213 215 def test_run_logs(self, tcp_server, caplog):
214 216
215 217 with self._tcp_patcher(tcp_server):
216 218 daemon = hooks_daemon.HttpHooksCallbackDaemon()
217 219
218 220 with self._thread_patcher(mock.Mock()), caplog.at_level(logging.DEBUG):
219 221 daemon._run()
220 222
221 223 assert_message_in_log(
222 224 caplog.records,
223 225 'Running event loop of callback daemon in background thread',
224 226 levelno=logging.DEBUG, module='hooks_daemon')
225 227
226 228 def test_stop_cleans_up_the_connection(self, tcp_server, caplog):
227 229 thread = mock.Mock()
228 230
229 231 with self._tcp_patcher(tcp_server):
230 232 daemon = hooks_daemon.HttpHooksCallbackDaemon()
231 233
232 234 with self._thread_patcher(thread), caplog.at_level(logging.DEBUG):
233 235 with daemon:
234 236 assert daemon._daemon == tcp_server
235 237 assert daemon._callback_thread == thread
236 238
237 239 assert daemon._daemon is None
238 240 assert daemon._callback_thread is None
239 241 tcp_server.shutdown.assert_called_with()
240 242 thread.join.assert_called_once_with()
241 243
242 244 assert_message_in_log(
243 245 caplog.records, 'Waiting for background thread to finish.',
244 246 levelno=logging.DEBUG, module='hooks_daemon')
245 247
246 248 def _tcp_patcher(self, tcp_server):
247 249 return mock.patch.object(
248 250 hooks_daemon, 'TCPServer', return_value=tcp_server)
249 251
250 252 def _thread_patcher(self, thread):
251 253 return mock.patch.object(
252 254 hooks_daemon.threading, 'Thread', return_value=thread)
253 255
254 256
255 257 class TestPrepareHooksDaemon(object):
256 258 @pytest.mark.parametrize('protocol', ('http',))
257 259 def test_returns_dummy_hooks_callback_daemon_when_using_direct_calls(
258 260 self, protocol):
259 261 expected_extras = {'extra1': 'value1'}
260 262 callback, extras = hooks_daemon.prepare_callback_daemon(
261 263 expected_extras.copy(), protocol=protocol, use_direct_calls=True)
262 264 assert isinstance(callback, hooks_daemon.DummyHooksCallbackDaemon)
263 265 expected_extras['hooks_module'] = 'rhodecode.lib.hooks_daemon'
264 266 assert extras == expected_extras
265 267
266 268 @pytest.mark.parametrize('protocol, expected_class', (
267 269 ('http', hooks_daemon.HttpHooksCallbackDaemon),
268 270 ))
269 271 def test_returns_real_hooks_callback_daemon_when_protocol_is_specified(
270 272 self, protocol, expected_class):
271 273 expected_extras = {
272 274 'extra1': 'value1',
273 275 'hooks_protocol': protocol.lower()
274 276 }
275 277 callback, extras = hooks_daemon.prepare_callback_daemon(
276 278 expected_extras.copy(), protocol=protocol, use_direct_calls=False)
277 279 assert isinstance(callback, expected_class)
278 280 hooks_uri = extras.pop('hooks_uri')
279 281 assert extras == expected_extras
280 282
281 283 @pytest.mark.parametrize('protocol', (
282 284 'invalid',
283 285 'Http',
284 286 'HTTP',
285 287 ))
286 288 def test_raises_on_invalid_protocol(self, protocol):
287 289 expected_extras = {
288 290 'extra1': 'value1',
289 291 'hooks_protocol': protocol.lower()
290 292 }
291 293 with pytest.raises(Exception):
292 294 callback, extras = hooks_daemon.prepare_callback_daemon(
293 295 expected_extras.copy(),
294 296 protocol=protocol,
295 297 use_direct_calls=False)
296 298
297 299
298 300 class MockRequest(object):
299 301 def __init__(self, request):
300 302 self.request = request
301 303 self.input_stream = StringIO(b'{}'.format(self.request))
302 304 self.output_stream = StringIO()
303 305
304 306 def makefile(self, mode, *args, **kwargs):
305 307 return self.output_stream if mode == 'wb' else self.input_stream
306 308
307 309
308 310 class MockServer(object):
309 311 def __init__(self, Handler, request):
310 312 ip_port = ('0.0.0.0', 8888)
311 313 self.request = MockRequest(request)
312 314 self.handler = Handler(self.request, ip_port, self)
313 315
314 316
315 317 @pytest.fixture
316 318 def tcp_server():
317 319 server = mock.Mock()
318 320 server.server_address = ('127.0.0.1', 8881)
319 321 return server
General Comments 0
You need to be logged in to leave comments. Login now