##// 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 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 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 org_exc = json.loads(server.request.output_stream.buflist[-1])
113 expected_result = {
113 'exception': 'Exception',
114 'exception': 'Exception',
114 'exception_args': ('Test exception', )
115 'exception_traceback': org_exc['exception_traceback'],
115 })
116 'exception_args': ['Test exception']
116 assert server.request.output_stream.buflist[-1] == expected_result
117 }
118 assert org_exc == expected_result
117
119
118 def test_log_message_writes_to_debug_log(self, caplog):
120 def test_log_message_writes_to_debug_log(self, caplog):
119 ip_port = ('0.0.0.0', 8888)
121 ip_port = ('0.0.0.0', 8888)
120 handler = hooks_daemon.HooksHttpHandler(
122 handler = hooks_daemon.HooksHttpHandler(
121 MockRequest('POST /'), ip_port, mock.Mock())
123 MockRequest('POST /'), ip_port, mock.Mock())
122 fake_date = '1/Nov/2015 00:00:00'
124 fake_date = '1/Nov/2015 00:00:00'
123 date_patcher = mock.patch.object(
125 date_patcher = mock.patch.object(
124 handler, 'log_date_time_string', return_value=fake_date)
126 handler, 'log_date_time_string', return_value=fake_date)
125 with date_patcher, caplog.at_level(logging.DEBUG):
127 with date_patcher, caplog.at_level(logging.DEBUG):
126 handler.log_message('Some message %d, %s', 123, 'string')
128 handler.log_message('Some message %d, %s', 123, 'string')
127
129
128 expected_message = '{} - - [{}] Some message 123, string'.format(
130 expected_message = '{} - - [{}] Some message 123, string'.format(
129 ip_port[0], fake_date)
131 ip_port[0], fake_date)
130 assert_message_in_log(
132 assert_message_in_log(
131 caplog.records, expected_message,
133 caplog.records, expected_message,
132 levelno=logging.DEBUG, module='hooks_daemon')
134 levelno=logging.DEBUG, module='hooks_daemon')
133
135
134 def _generate_post_request(self, data):
136 def _generate_post_request(self, data):
135 payload = json.dumps(data)
137 payload = json.dumps(data)
136 return 'POST / HTTP/1.0\nContent-Length: {}\n\n{}'.format(
138 return 'POST / HTTP/1.0\nContent-Length: {}\n\n{}'.format(
137 len(payload), payload)
139 len(payload), payload)
138
140
139
141
140 class ThreadedHookCallbackDaemon(object):
142 class ThreadedHookCallbackDaemon(object):
141 def test_constructor_calls_prepare(self):
143 def test_constructor_calls_prepare(self):
142 prepare_daemon_patcher = mock.patch.object(
144 prepare_daemon_patcher = mock.patch.object(
143 hooks_daemon.ThreadedHookCallbackDaemon, '_prepare')
145 hooks_daemon.ThreadedHookCallbackDaemon, '_prepare')
144 with prepare_daemon_patcher as prepare_daemon_mock:
146 with prepare_daemon_patcher as prepare_daemon_mock:
145 hooks_daemon.ThreadedHookCallbackDaemon()
147 hooks_daemon.ThreadedHookCallbackDaemon()
146 prepare_daemon_mock.assert_called_once_with()
148 prepare_daemon_mock.assert_called_once_with()
147
149
148 def test_run_is_called_on_context_start(self):
150 def test_run_is_called_on_context_start(self):
149 patchers = mock.patch.multiple(
151 patchers = mock.patch.multiple(
150 hooks_daemon.ThreadedHookCallbackDaemon,
152 hooks_daemon.ThreadedHookCallbackDaemon,
151 _run=mock.DEFAULT, _prepare=mock.DEFAULT, __exit__=mock.DEFAULT)
153 _run=mock.DEFAULT, _prepare=mock.DEFAULT, __exit__=mock.DEFAULT)
152
154
153 with patchers as mocks:
155 with patchers as mocks:
154 daemon = hooks_daemon.ThreadedHookCallbackDaemon()
156 daemon = hooks_daemon.ThreadedHookCallbackDaemon()
155 with daemon as daemon_context:
157 with daemon as daemon_context:
156 pass
158 pass
157 mocks['_run'].assert_called_once_with()
159 mocks['_run'].assert_called_once_with()
158 assert daemon_context == daemon
160 assert daemon_context == daemon
159
161
160 def test_stop_is_called_on_context_exit(self):
162 def test_stop_is_called_on_context_exit(self):
161 patchers = mock.patch.multiple(
163 patchers = mock.patch.multiple(
162 hooks_daemon.ThreadedHookCallbackDaemon,
164 hooks_daemon.ThreadedHookCallbackDaemon,
163 _run=mock.DEFAULT, _prepare=mock.DEFAULT, _stop=mock.DEFAULT)
165 _run=mock.DEFAULT, _prepare=mock.DEFAULT, _stop=mock.DEFAULT)
164
166
165 with patchers as mocks:
167 with patchers as mocks:
166 daemon = hooks_daemon.ThreadedHookCallbackDaemon()
168 daemon = hooks_daemon.ThreadedHookCallbackDaemon()
167 with daemon as daemon_context:
169 with daemon as daemon_context:
168 assert mocks['_stop'].call_count == 0
170 assert mocks['_stop'].call_count == 0
169
171
170 mocks['_stop'].assert_called_once_with()
172 mocks['_stop'].assert_called_once_with()
171 assert daemon_context == daemon
173 assert daemon_context == daemon
172
174
173
175
174 class TestHttpHooksCallbackDaemon(object):
176 class TestHttpHooksCallbackDaemon(object):
175 def test_prepare_inits_daemon_variable(self, tcp_server, caplog):
177 def test_prepare_inits_daemon_variable(self, tcp_server, caplog):
176 with self._tcp_patcher(tcp_server), caplog.at_level(logging.DEBUG):
178 with self._tcp_patcher(tcp_server), caplog.at_level(logging.DEBUG):
177 daemon = hooks_daemon.HttpHooksCallbackDaemon()
179 daemon = hooks_daemon.HttpHooksCallbackDaemon()
178 assert daemon._daemon == tcp_server
180 assert daemon._daemon == tcp_server
179
181
180 assert_message_in_log(
182 assert_message_in_log(
181 caplog.records,
183 caplog.records,
182 'Preparing callback daemon and registering hook object',
184 'Preparing callback daemon and registering hook object',
183 levelno=logging.DEBUG, module='hooks_daemon')
185 levelno=logging.DEBUG, module='hooks_daemon')
184
186
185 def test_prepare_inits_hooks_uri_and_logs_it(
187 def test_prepare_inits_hooks_uri_and_logs_it(
186 self, tcp_server, caplog):
188 self, tcp_server, caplog):
187 with self._tcp_patcher(tcp_server), caplog.at_level(logging.DEBUG):
189 with self._tcp_patcher(tcp_server), caplog.at_level(logging.DEBUG):
188 daemon = hooks_daemon.HttpHooksCallbackDaemon()
190 daemon = hooks_daemon.HttpHooksCallbackDaemon()
189
191
190 _, port = tcp_server.server_address
192 _, port = tcp_server.server_address
191 expected_uri = '{}:{}'.format(daemon.IP_ADDRESS, port)
193 expected_uri = '{}:{}'.format(daemon.IP_ADDRESS, port)
192 assert daemon.hooks_uri == expected_uri
194 assert daemon.hooks_uri == expected_uri
193
195
194 assert_message_in_log(
196 assert_message_in_log(
195 caplog.records, 'Hooks uri is: {}'.format(expected_uri),
197 caplog.records, 'Hooks uri is: {}'.format(expected_uri),
196 levelno=logging.DEBUG, module='hooks_daemon')
198 levelno=logging.DEBUG, module='hooks_daemon')
197
199
198 def test_run_creates_a_thread(self, tcp_server):
200 def test_run_creates_a_thread(self, tcp_server):
199 thread = mock.Mock()
201 thread = mock.Mock()
200
202
201 with self._tcp_patcher(tcp_server):
203 with self._tcp_patcher(tcp_server):
202 daemon = hooks_daemon.HttpHooksCallbackDaemon()
204 daemon = hooks_daemon.HttpHooksCallbackDaemon()
203
205
204 with self._thread_patcher(thread) as thread_mock:
206 with self._thread_patcher(thread) as thread_mock:
205 daemon._run()
207 daemon._run()
206
208
207 thread_mock.assert_called_once_with(
209 thread_mock.assert_called_once_with(
208 target=tcp_server.serve_forever,
210 target=tcp_server.serve_forever,
209 kwargs={'poll_interval': daemon.POLL_INTERVAL})
211 kwargs={'poll_interval': daemon.POLL_INTERVAL})
210 assert thread.daemon is True
212 assert thread.daemon is True
211 thread.start.assert_called_once_with()
213 thread.start.assert_called_once_with()
212
214
213 def test_run_logs(self, tcp_server, caplog):
215 def test_run_logs(self, tcp_server, caplog):
214
216
215 with self._tcp_patcher(tcp_server):
217 with self._tcp_patcher(tcp_server):
216 daemon = hooks_daemon.HttpHooksCallbackDaemon()
218 daemon = hooks_daemon.HttpHooksCallbackDaemon()
217
219
218 with self._thread_patcher(mock.Mock()), caplog.at_level(logging.DEBUG):
220 with self._thread_patcher(mock.Mock()), caplog.at_level(logging.DEBUG):
219 daemon._run()
221 daemon._run()
220
222
221 assert_message_in_log(
223 assert_message_in_log(
222 caplog.records,
224 caplog.records,
223 'Running event loop of callback daemon in background thread',
225 'Running event loop of callback daemon in background thread',
224 levelno=logging.DEBUG, module='hooks_daemon')
226 levelno=logging.DEBUG, module='hooks_daemon')
225
227
226 def test_stop_cleans_up_the_connection(self, tcp_server, caplog):
228 def test_stop_cleans_up_the_connection(self, tcp_server, caplog):
227 thread = mock.Mock()
229 thread = mock.Mock()
228
230
229 with self._tcp_patcher(tcp_server):
231 with self._tcp_patcher(tcp_server):
230 daemon = hooks_daemon.HttpHooksCallbackDaemon()
232 daemon = hooks_daemon.HttpHooksCallbackDaemon()
231
233
232 with self._thread_patcher(thread), caplog.at_level(logging.DEBUG):
234 with self._thread_patcher(thread), caplog.at_level(logging.DEBUG):
233 with daemon:
235 with daemon:
234 assert daemon._daemon == tcp_server
236 assert daemon._daemon == tcp_server
235 assert daemon._callback_thread == thread
237 assert daemon._callback_thread == thread
236
238
237 assert daemon._daemon is None
239 assert daemon._daemon is None
238 assert daemon._callback_thread is None
240 assert daemon._callback_thread is None
239 tcp_server.shutdown.assert_called_with()
241 tcp_server.shutdown.assert_called_with()
240 thread.join.assert_called_once_with()
242 thread.join.assert_called_once_with()
241
243
242 assert_message_in_log(
244 assert_message_in_log(
243 caplog.records, 'Waiting for background thread to finish.',
245 caplog.records, 'Waiting for background thread to finish.',
244 levelno=logging.DEBUG, module='hooks_daemon')
246 levelno=logging.DEBUG, module='hooks_daemon')
245
247
246 def _tcp_patcher(self, tcp_server):
248 def _tcp_patcher(self, tcp_server):
247 return mock.patch.object(
249 return mock.patch.object(
248 hooks_daemon, 'TCPServer', return_value=tcp_server)
250 hooks_daemon, 'TCPServer', return_value=tcp_server)
249
251
250 def _thread_patcher(self, thread):
252 def _thread_patcher(self, thread):
251 return mock.patch.object(
253 return mock.patch.object(
252 hooks_daemon.threading, 'Thread', return_value=thread)
254 hooks_daemon.threading, 'Thread', return_value=thread)
253
255
254
256
255 class TestPrepareHooksDaemon(object):
257 class TestPrepareHooksDaemon(object):
256 @pytest.mark.parametrize('protocol', ('http',))
258 @pytest.mark.parametrize('protocol', ('http',))
257 def test_returns_dummy_hooks_callback_daemon_when_using_direct_calls(
259 def test_returns_dummy_hooks_callback_daemon_when_using_direct_calls(
258 self, protocol):
260 self, protocol):
259 expected_extras = {'extra1': 'value1'}
261 expected_extras = {'extra1': 'value1'}
260 callback, extras = hooks_daemon.prepare_callback_daemon(
262 callback, extras = hooks_daemon.prepare_callback_daemon(
261 expected_extras.copy(), protocol=protocol, use_direct_calls=True)
263 expected_extras.copy(), protocol=protocol, use_direct_calls=True)
262 assert isinstance(callback, hooks_daemon.DummyHooksCallbackDaemon)
264 assert isinstance(callback, hooks_daemon.DummyHooksCallbackDaemon)
263 expected_extras['hooks_module'] = 'rhodecode.lib.hooks_daemon'
265 expected_extras['hooks_module'] = 'rhodecode.lib.hooks_daemon'
264 assert extras == expected_extras
266 assert extras == expected_extras
265
267
266 @pytest.mark.parametrize('protocol, expected_class', (
268 @pytest.mark.parametrize('protocol, expected_class', (
267 ('http', hooks_daemon.HttpHooksCallbackDaemon),
269 ('http', hooks_daemon.HttpHooksCallbackDaemon),
268 ))
270 ))
269 def test_returns_real_hooks_callback_daemon_when_protocol_is_specified(
271 def test_returns_real_hooks_callback_daemon_when_protocol_is_specified(
270 self, protocol, expected_class):
272 self, protocol, expected_class):
271 expected_extras = {
273 expected_extras = {
272 'extra1': 'value1',
274 'extra1': 'value1',
273 'hooks_protocol': protocol.lower()
275 'hooks_protocol': protocol.lower()
274 }
276 }
275 callback, extras = hooks_daemon.prepare_callback_daemon(
277 callback, extras = hooks_daemon.prepare_callback_daemon(
276 expected_extras.copy(), protocol=protocol, use_direct_calls=False)
278 expected_extras.copy(), protocol=protocol, use_direct_calls=False)
277 assert isinstance(callback, expected_class)
279 assert isinstance(callback, expected_class)
278 hooks_uri = extras.pop('hooks_uri')
280 hooks_uri = extras.pop('hooks_uri')
279 assert extras == expected_extras
281 assert extras == expected_extras
280
282
281 @pytest.mark.parametrize('protocol', (
283 @pytest.mark.parametrize('protocol', (
282 'invalid',
284 'invalid',
283 'Http',
285 'Http',
284 'HTTP',
286 'HTTP',
285 ))
287 ))
286 def test_raises_on_invalid_protocol(self, protocol):
288 def test_raises_on_invalid_protocol(self, protocol):
287 expected_extras = {
289 expected_extras = {
288 'extra1': 'value1',
290 'extra1': 'value1',
289 'hooks_protocol': protocol.lower()
291 'hooks_protocol': protocol.lower()
290 }
292 }
291 with pytest.raises(Exception):
293 with pytest.raises(Exception):
292 callback, extras = hooks_daemon.prepare_callback_daemon(
294 callback, extras = hooks_daemon.prepare_callback_daemon(
293 expected_extras.copy(),
295 expected_extras.copy(),
294 protocol=protocol,
296 protocol=protocol,
295 use_direct_calls=False)
297 use_direct_calls=False)
296
298
297
299
298 class MockRequest(object):
300 class MockRequest(object):
299 def __init__(self, request):
301 def __init__(self, request):
300 self.request = request
302 self.request = request
301 self.input_stream = StringIO(b'{}'.format(self.request))
303 self.input_stream = StringIO(b'{}'.format(self.request))
302 self.output_stream = StringIO()
304 self.output_stream = StringIO()
303
305
304 def makefile(self, mode, *args, **kwargs):
306 def makefile(self, mode, *args, **kwargs):
305 return self.output_stream if mode == 'wb' else self.input_stream
307 return self.output_stream if mode == 'wb' else self.input_stream
306
308
307
309
308 class MockServer(object):
310 class MockServer(object):
309 def __init__(self, Handler, request):
311 def __init__(self, Handler, request):
310 ip_port = ('0.0.0.0', 8888)
312 ip_port = ('0.0.0.0', 8888)
311 self.request = MockRequest(request)
313 self.request = MockRequest(request)
312 self.handler = Handler(self.request, ip_port, self)
314 self.handler = Handler(self.request, ip_port, self)
313
315
314
316
315 @pytest.fixture
317 @pytest.fixture
316 def tcp_server():
318 def tcp_server():
317 server = mock.Mock()
319 server = mock.Mock()
318 server.server_address = ('127.0.0.1', 8881)
320 server.server_address = ('127.0.0.1', 8881)
319 return server
321 return server
General Comments 0
You need to be logged in to leave comments. Login now