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