##// END OF EJS Templates
tests: Fix tests that depend on the default value of 'pyro4' as backend.
Martin Bornhold -
r968:70573703 default
parent child Browse files
Show More
@@ -1,371 +1,388 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 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 112 expected_result = json.dumps({
113 113 'exception': 'Exception',
114 114 'exception_args': ('Test exception', )
115 115 })
116 116 assert server.request.output_stream.buflist[-1] == expected_result
117 117
118 118 def test_log_message_writes_to_debug_log(self, caplog):
119 119 ip_port = ('0.0.0.0', 8888)
120 120 handler = hooks_daemon.HooksHttpHandler(
121 121 MockRequest('POST /'), ip_port, mock.Mock())
122 122 fake_date = '1/Nov/2015 00:00:00'
123 123 date_patcher = mock.patch.object(
124 124 handler, 'log_date_time_string', return_value=fake_date)
125 125 with date_patcher, caplog.atLevel(logging.DEBUG):
126 126 handler.log_message('Some message %d, %s', 123, 'string')
127 127
128 128 expected_message = '{} - - [{}] Some message 123, string'.format(
129 129 ip_port[0], fake_date)
130 130 assert_message_in_log(
131 131 caplog.records(), expected_message,
132 132 levelno=logging.DEBUG, module='hooks_daemon')
133 133
134 134 def _generate_post_request(self, data):
135 135 payload = json.dumps(data)
136 136 return 'POST / HTTP/1.0\nContent-Length: {}\n\n{}'.format(
137 137 len(payload), payload)
138 138
139 139
140 140 class ThreadedHookCallbackDaemon(object):
141 141 def test_constructor_calls_prepare(self):
142 142 prepare_daemon_patcher = mock.patch.object(
143 143 hooks_daemon.ThreadedHookCallbackDaemon, '_prepare')
144 144 with prepare_daemon_patcher as prepare_daemon_mock:
145 145 hooks_daemon.ThreadedHookCallbackDaemon()
146 146 prepare_daemon_mock.assert_called_once_with()
147 147
148 148 def test_run_is_called_on_context_start(self):
149 149 patchers = mock.patch.multiple(
150 150 hooks_daemon.ThreadedHookCallbackDaemon,
151 151 _run=mock.DEFAULT, _prepare=mock.DEFAULT, __exit__=mock.DEFAULT)
152 152
153 153 with patchers as mocks:
154 154 daemon = hooks_daemon.ThreadedHookCallbackDaemon()
155 155 with daemon as daemon_context:
156 156 pass
157 157 mocks['_run'].assert_called_once_with()
158 158 assert daemon_context == daemon
159 159
160 160 def test_stop_is_called_on_context_exit(self):
161 161 patchers = mock.patch.multiple(
162 162 hooks_daemon.ThreadedHookCallbackDaemon,
163 163 _run=mock.DEFAULT, _prepare=mock.DEFAULT, _stop=mock.DEFAULT)
164 164
165 165 with patchers as mocks:
166 166 daemon = hooks_daemon.ThreadedHookCallbackDaemon()
167 167 with daemon as daemon_context:
168 168 assert mocks['_stop'].call_count == 0
169 169
170 170 mocks['_stop'].assert_called_once_with()
171 171 assert daemon_context == daemon
172 172
173 173
174 174 class TestPyro4HooksCallbackDaemon(object):
175 175 def test_prepare_inits_pyro4_and_registers_hooks(self, caplog):
176 176 pyro4_daemon = mock.Mock()
177 177
178 178 with self._pyro4_patcher(pyro4_daemon), caplog.atLevel(logging.DEBUG):
179 179 daemon = hooks_daemon.Pyro4HooksCallbackDaemon()
180 180
181 181 assert daemon._daemon == pyro4_daemon
182 182
183 183 assert pyro4_daemon.register.call_count == 1
184 184 args, kwargs = pyro4_daemon.register.call_args
185 185 assert len(args) == 1
186 186 assert isinstance(args[0], hooks_daemon.Hooks)
187 187
188 188 assert_message_in_log(
189 189 caplog.records(),
190 190 'Preparing callback daemon and registering hook object',
191 191 levelno=logging.DEBUG, module='hooks_daemon')
192 192
193 193 def test_run_creates_a_thread(self):
194 194 thread = mock.Mock()
195 195 pyro4_daemon = mock.Mock()
196 196
197 197 with self._pyro4_patcher(pyro4_daemon):
198 198 daemon = hooks_daemon.Pyro4HooksCallbackDaemon()
199 199
200 200 with self._thread_patcher(thread) as thread_mock:
201 201 daemon._run()
202 202
203 203 assert thread_mock.call_count == 1
204 204 args, kwargs = thread_mock.call_args
205 205 assert args == ()
206 206 assert kwargs['target'] == pyro4_daemon.requestLoop
207 207 assert kwargs['kwargs']['loopCondition']() is True
208 208
209 209 def test_stop_cleans_up_the_connection(self, caplog):
210 210 thread = mock.Mock()
211 211 pyro4_daemon = mock.Mock()
212 212
213 213 with self._pyro4_patcher(pyro4_daemon):
214 214 daemon = hooks_daemon.Pyro4HooksCallbackDaemon()
215 215
216 216 with self._thread_patcher(thread), caplog.atLevel(logging.DEBUG):
217 217 with daemon:
218 218 assert daemon._daemon == pyro4_daemon
219 219 assert daemon._callback_thread == thread
220 220
221 221 assert daemon._daemon is None
222 222 assert daemon._callback_thread is None
223 223 pyro4_daemon.close.assert_called_with()
224 224 thread.join.assert_called_once_with()
225 225
226 226 assert_message_in_log(
227 227 caplog.records(), 'Waiting for background thread to finish.',
228 228 levelno=logging.DEBUG, module='hooks_daemon')
229 229
230 230 def _pyro4_patcher(self, daemon):
231 231 return mock.patch.object(
232 232 hooks_daemon.Pyro4, 'Daemon', return_value=daemon)
233 233
234 234 def _thread_patcher(self, thread):
235 235 return mock.patch.object(
236 236 hooks_daemon.threading, 'Thread', return_value=thread)
237 237
238 238
239 239 class TestHttpHooksCallbackDaemon(object):
240 240 def test_prepare_inits_daemon_variable(self, tcp_server, caplog):
241 241 with self._tcp_patcher(tcp_server), caplog.atLevel(logging.DEBUG):
242 242 daemon = hooks_daemon.HttpHooksCallbackDaemon()
243 243 assert daemon._daemon == tcp_server
244 244
245 245 assert_message_in_log(
246 246 caplog.records(),
247 247 'Preparing callback daemon and registering hook object',
248 248 levelno=logging.DEBUG, module='hooks_daemon')
249 249
250 250 def test_prepare_inits_hooks_uri_and_logs_it(
251 251 self, tcp_server, caplog):
252 252 with self._tcp_patcher(tcp_server), caplog.atLevel(logging.DEBUG):
253 253 daemon = hooks_daemon.HttpHooksCallbackDaemon()
254 254
255 255 _, port = tcp_server.server_address
256 256 expected_uri = '{}:{}'.format(daemon.IP_ADDRESS, port)
257 257 assert daemon.hooks_uri == expected_uri
258 258
259 259 assert_message_in_log(
260 260 caplog.records(), 'Hooks uri is: {}'.format(expected_uri),
261 261 levelno=logging.DEBUG, module='hooks_daemon')
262 262
263 263 def test_run_creates_a_thread(self, tcp_server):
264 264 thread = mock.Mock()
265 265
266 266 with self._tcp_patcher(tcp_server):
267 267 daemon = hooks_daemon.HttpHooksCallbackDaemon()
268 268
269 269 with self._thread_patcher(thread) as thread_mock:
270 270 daemon._run()
271 271
272 272 thread_mock.assert_called_once_with(
273 273 target=tcp_server.serve_forever,
274 274 kwargs={'poll_interval': daemon.POLL_INTERVAL})
275 275 assert thread.daemon is True
276 276 thread.start.assert_called_once_with()
277 277
278 278 def test_run_logs(self, tcp_server, caplog):
279 279
280 280 with self._tcp_patcher(tcp_server):
281 281 daemon = hooks_daemon.HttpHooksCallbackDaemon()
282 282
283 283 with self._thread_patcher(mock.Mock()), caplog.atLevel(logging.DEBUG):
284 284 daemon._run()
285 285
286 286 assert_message_in_log(
287 287 caplog.records(),
288 288 'Running event loop of callback daemon in background thread',
289 289 levelno=logging.DEBUG, module='hooks_daemon')
290 290
291 291 def test_stop_cleans_up_the_connection(self, tcp_server, caplog):
292 292 thread = mock.Mock()
293 293
294 294 with self._tcp_patcher(tcp_server):
295 295 daemon = hooks_daemon.HttpHooksCallbackDaemon()
296 296
297 297 with self._thread_patcher(thread), caplog.atLevel(logging.DEBUG):
298 298 with daemon:
299 299 assert daemon._daemon == tcp_server
300 300 assert daemon._callback_thread == thread
301 301
302 302 assert daemon._daemon is None
303 303 assert daemon._callback_thread is None
304 304 tcp_server.shutdown.assert_called_with()
305 305 thread.join.assert_called_once_with()
306 306
307 307 assert_message_in_log(
308 308 caplog.records(), 'Waiting for background thread to finish.',
309 309 levelno=logging.DEBUG, module='hooks_daemon')
310 310
311 311 def _tcp_patcher(self, tcp_server):
312 312 return mock.patch.object(
313 313 hooks_daemon, 'TCPServer', return_value=tcp_server)
314 314
315 315 def _thread_patcher(self, thread):
316 316 return mock.patch.object(
317 317 hooks_daemon.threading, 'Thread', return_value=thread)
318 318
319 319
320 320 class TestPrepareHooksDaemon(object):
321 def test_returns_dummy_hooks_callback_daemon_when_using_direct_calls(self):
321 @pytest.mark.parametrize('protocol', ('http', 'pyro4'))
322 def test_returns_dummy_hooks_callback_daemon_when_using_direct_calls(
323 self, protocol):
322 324 expected_extras = {'extra1': 'value1'}
323 325 callback, extras = hooks_daemon.prepare_callback_daemon(
324 expected_extras.copy(), use_direct_calls=True)
326 expected_extras.copy(), protocol=protocol, use_direct_calls=True)
325 327 assert isinstance(callback, hooks_daemon.DummyHooksCallbackDaemon)
326 328 expected_extras['hooks_module'] = 'rhodecode.lib.hooks_daemon'
327 329 assert extras == expected_extras
328 330
329 331 @pytest.mark.parametrize('protocol, expected_class', (
330 332 ('pyro4', hooks_daemon.Pyro4HooksCallbackDaemon),
331 ('Pyro4', hooks_daemon.Pyro4HooksCallbackDaemon),
332 ('HTTP', hooks_daemon.HttpHooksCallbackDaemon),
333 333 ('http', hooks_daemon.HttpHooksCallbackDaemon)
334 334 ))
335 335 def test_returns_real_hooks_callback_daemon_when_protocol_is_specified(
336 336 self, protocol, expected_class):
337 337 expected_extras = {
338 338 'extra1': 'value1',
339 339 'hooks_protocol': protocol.lower()
340 340 }
341 341 callback, extras = hooks_daemon.prepare_callback_daemon(
342 expected_extras.copy(), protocol=protocol)
342 expected_extras.copy(), protocol=protocol, use_direct_calls=False)
343 343 assert isinstance(callback, expected_class)
344 344 hooks_uri = extras.pop('hooks_uri')
345 345 assert extras == expected_extras
346 346 if protocol.lower() == 'pyro4':
347 347 assert hooks_uri.startswith('PYRO')
348 348
349 @pytest.mark.parametrize('protocol', (
350 'invalid',
351 'Pyro4',
352 'Http',
353 'HTTP',
354 ))
355 def test_raises_on_invalid_protocol(self, protocol):
356 expected_extras = {
357 'extra1': 'value1',
358 'hooks_protocol': protocol.lower()
359 }
360 with pytest.raises(Exception):
361 callback, extras = hooks_daemon.prepare_callback_daemon(
362 expected_extras.copy(),
363 protocol=protocol,
364 use_direct_calls=False)
365
349 366
350 367 class MockRequest(object):
351 368 def __init__(self, request):
352 369 self.request = request
353 370 self.input_stream = StringIO(b'{}'.format(self.request))
354 371 self.output_stream = StringIO()
355 372
356 373 def makefile(self, mode, *args, **kwargs):
357 374 return self.output_stream if mode == 'wb' else self.input_stream
358 375
359 376
360 377 class MockServer(object):
361 378 def __init__(self, Handler, request):
362 379 ip_port = ('0.0.0.0', 8888)
363 380 self.request = MockRequest(request)
364 381 self.handler = Handler(self.request, ip_port, self)
365 382
366 383
367 384 @pytest.fixture
368 385 def tcp_server():
369 386 server = mock.Mock()
370 387 server.server_address = ('127.0.0.1', 8881)
371 388 return server
@@ -1,69 +1,69 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 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 mock
22 22 import Pyro4
23 23 import pytest
24 24 from Pyro4.errors import TimeoutError, ConnectionClosedError
25 25
26 26 from rhodecode.lib.vcs import client, create_vcsserver_proxy
27 27 from rhodecode.lib.vcs.conf import settings
28 28
29 29
30 30 @pytest.fixture
31 31 def short_timeout(request):
32 32 old_timeout = Pyro4.config.COMMTIMEOUT
33 33 Pyro4.config.COMMTIMEOUT = 0.5
34 34
35 35 @request.addfinalizer
36 36 def cleanup():
37 37 Pyro4.config.COMMTIMEOUT = old_timeout
38 38
39 39 return Pyro4.config.COMMTIMEOUT
40 40
41 41
42 42 @pytest.mark.timeout(20)
43 43 def test_vcs_connections_have_a_timeout_set(pylonsapp, short_timeout):
44 44 server_and_port = pylonsapp.config['vcs.server']
45 45 proxy_objects = []
46 46 with pytest.raises(TimeoutError):
47 47 # TODO: johbo: Find a better way to set this number
48 48 for number in xrange(100):
49 server = create_vcsserver_proxy(server_and_port)
49 server = create_vcsserver_proxy(server_and_port, protocol='pyro4')
50 50 server.ping()
51 51 proxy_objects.append(server)
52 52
53 53
54 54 def test_vcs_remote_calls_are_bound_by_timeout(pylonsapp, short_timeout):
55 55 server_and_port = pylonsapp.config['vcs.server']
56 56 with pytest.raises(TimeoutError):
57 server = create_vcsserver_proxy(server_and_port)
57 server = create_vcsserver_proxy(server_and_port, protocol='pyro4')
58 58 server.sleep(short_timeout + 1.0)
59 59
60 60
61 61 def test_wrap_remote_call_triggers_reconnect():
62 62 # First call fails, the second is successful
63 63 func = mock.Mock(side_effect=[ConnectionClosedError, None])
64 64 proxy_mock = mock.Mock()
65 65
66 66 wrapped_func = client._wrap_remote_call(proxy_mock, func)
67 67 wrapped_func()
68 68 proxy_mock._pyroReconnect.assert_called_with(
69 69 tries=settings.PYRO_RECONNECT_TRIES)
General Comments 0
You need to be logged in to leave comments. Login now