##// END OF EJS Templates
processes: better handling of PID that are not part of RhodeCode processes....
processes: better handling of PID that are not part of RhodeCode processes. - in some cases for multiple gunicorn instances the PID sent is not from rhodecode which would lead to AccessDenied errors. We prevent from crashing the server on this type of errors.

File last commit:

r2487:fcee5614 default
r2661:042cb4c7 default
Show More
test_hooks_daemon.py
321 lines | 11.7 KiB | text/x-python | PythonLexer
/ rhodecode / tests / lib / test_hooks_daemon.py
project: added all source files and assets
r1 # -*- coding: utf-8 -*-
release: update copyright year to 2018
r2487 # Copyright (C) 2010-2018 RhodeCode GmbH
project: added all source files and assets
r1 #
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
# (only), as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# This program is dual-licensed. If you wish to learn more about the
# RhodeCode Enterprise Edition, including its added features, Support services,
# and proprietary license terms, please see https://rhodecode.com/licenses/
import json
import logging
from StringIO import StringIO
import mock
import pytest
from rhodecode.lib import hooks_daemon
from rhodecode.tests.utils import assert_message_in_log
class TestDummyHooksCallbackDaemon(object):
def test_hooks_module_path_set_properly(self):
daemon = hooks_daemon.DummyHooksCallbackDaemon()
assert daemon.hooks_module == 'rhodecode.lib.hooks_daemon'
def test_logs_entering_the_hook(self):
daemon = hooks_daemon.DummyHooksCallbackDaemon()
with mock.patch.object(hooks_daemon.log, 'debug') as log_mock:
with daemon as return_value:
log_mock.assert_called_once_with(
'Running dummy hooks callback daemon')
assert return_value == daemon
def test_logs_exiting_the_hook(self):
daemon = hooks_daemon.DummyHooksCallbackDaemon()
with mock.patch.object(hooks_daemon.log, 'debug') as log_mock:
with daemon:
pass
log_mock.assert_called_with('Exiting dummy hooks callback daemon')
class TestHooks(object):
def test_hooks_can_be_used_as_a_context_processor(self):
hooks = hooks_daemon.Hooks()
with hooks as return_value:
pass
assert hooks == return_value
class TestHooksHttpHandler(object):
def test_read_request_parses_method_name_and_arguments(self):
data = {
'method': 'test',
'extras': {
'param1': 1,
'param2': 'a'
}
}
request = self._generate_post_request(data)
hooks_patcher = mock.patch.object(
hooks_daemon.Hooks, data['method'], create=True, return_value=1)
with hooks_patcher as hooks_mock:
MockServer(hooks_daemon.HooksHttpHandler, request)
hooks_mock.assert_called_once_with(data['extras'])
def test_hooks_serialized_result_is_returned(self):
request = self._generate_post_request({})
rpc_method = 'test'
hook_result = {
'first': 'one',
'second': 2
}
read_patcher = mock.patch.object(
hooks_daemon.HooksHttpHandler, '_read_request',
return_value=(rpc_method, {}))
hooks_patcher = mock.patch.object(
hooks_daemon.Hooks, rpc_method, create=True,
return_value=hook_result)
with read_patcher, hooks_patcher:
server = MockServer(hooks_daemon.HooksHttpHandler, request)
expected_result = json.dumps(hook_result)
assert server.request.output_stream.buflist[-1] == expected_result
def test_exception_is_returned_in_response(self):
request = self._generate_post_request({})
rpc_method = 'test'
read_patcher = mock.patch.object(
hooks_daemon.HooksHttpHandler, '_read_request',
return_value=(rpc_method, {}))
hooks_patcher = mock.patch.object(
hooks_daemon.Hooks, rpc_method, create=True,
side_effect=Exception('Test exception'))
with read_patcher, hooks_patcher:
server = MockServer(hooks_daemon.HooksHttpHandler, request)
tests: fixed tests for the added extra traceback with exceptions for vcsserver.
r1459 org_exc = json.loads(server.request.output_stream.buflist[-1])
expected_result = {
project: added all source files and assets
r1 'exception': 'Exception',
tests: fixed tests for the added extra traceback with exceptions for vcsserver.
r1459 'exception_traceback': org_exc['exception_traceback'],
'exception_args': ['Test exception']
}
assert org_exc == expected_result
project: added all source files and assets
r1
def test_log_message_writes_to_debug_log(self, caplog):
ip_port = ('0.0.0.0', 8888)
handler = hooks_daemon.HooksHttpHandler(
MockRequest('POST /'), ip_port, mock.Mock())
fake_date = '1/Nov/2015 00:00:00'
date_patcher = mock.patch.object(
handler, 'log_date_time_string', return_value=fake_date)
dependencies: bumped pytest libraries to latest versions.
r1221 with date_patcher, caplog.at_level(logging.DEBUG):
project: added all source files and assets
r1 handler.log_message('Some message %d, %s', 123, 'string')
expected_message = '{} - - [{}] Some message 123, string'.format(
ip_port[0], fake_date)
assert_message_in_log(
dependencies: bumped pytest libraries to latest versions.
r1221 caplog.records, expected_message,
project: added all source files and assets
r1 levelno=logging.DEBUG, module='hooks_daemon')
def _generate_post_request(self, data):
payload = json.dumps(data)
return 'POST / HTTP/1.0\nContent-Length: {}\n\n{}'.format(
len(payload), payload)
class ThreadedHookCallbackDaemon(object):
def test_constructor_calls_prepare(self):
prepare_daemon_patcher = mock.patch.object(
hooks_daemon.ThreadedHookCallbackDaemon, '_prepare')
with prepare_daemon_patcher as prepare_daemon_mock:
hooks_daemon.ThreadedHookCallbackDaemon()
prepare_daemon_mock.assert_called_once_with()
def test_run_is_called_on_context_start(self):
patchers = mock.patch.multiple(
hooks_daemon.ThreadedHookCallbackDaemon,
_run=mock.DEFAULT, _prepare=mock.DEFAULT, __exit__=mock.DEFAULT)
with patchers as mocks:
daemon = hooks_daemon.ThreadedHookCallbackDaemon()
with daemon as daemon_context:
pass
mocks['_run'].assert_called_once_with()
assert daemon_context == daemon
def test_stop_is_called_on_context_exit(self):
patchers = mock.patch.multiple(
hooks_daemon.ThreadedHookCallbackDaemon,
_run=mock.DEFAULT, _prepare=mock.DEFAULT, _stop=mock.DEFAULT)
with patchers as mocks:
daemon = hooks_daemon.ThreadedHookCallbackDaemon()
with daemon as daemon_context:
assert mocks['_stop'].call_count == 0
mocks['_stop'].assert_called_once_with()
assert daemon_context == daemon
class TestHttpHooksCallbackDaemon(object):
def test_prepare_inits_daemon_variable(self, tcp_server, caplog):
dependencies: bumped pytest libraries to latest versions.
r1221 with self._tcp_patcher(tcp_server), caplog.at_level(logging.DEBUG):
project: added all source files and assets
r1 daemon = hooks_daemon.HttpHooksCallbackDaemon()
assert daemon._daemon == tcp_server
assert_message_in_log(
dependencies: bumped pytest libraries to latest versions.
r1221 caplog.records,
hooks: added debug logs.
r2135 'Preparing HTTP callback daemon and registering hook object',
project: added all source files and assets
r1 levelno=logging.DEBUG, module='hooks_daemon')
def test_prepare_inits_hooks_uri_and_logs_it(
self, tcp_server, caplog):
dependencies: bumped pytest libraries to latest versions.
r1221 with self._tcp_patcher(tcp_server), caplog.at_level(logging.DEBUG):
project: added all source files and assets
r1 daemon = hooks_daemon.HttpHooksCallbackDaemon()
_, port = tcp_server.server_address
expected_uri = '{}:{}'.format(daemon.IP_ADDRESS, port)
assert daemon.hooks_uri == expected_uri
assert_message_in_log(
dependencies: bumped pytest libraries to latest versions.
r1221 caplog.records, 'Hooks uri is: {}'.format(expected_uri),
project: added all source files and assets
r1 levelno=logging.DEBUG, module='hooks_daemon')
def test_run_creates_a_thread(self, tcp_server):
thread = mock.Mock()
with self._tcp_patcher(tcp_server):
daemon = hooks_daemon.HttpHooksCallbackDaemon()
with self._thread_patcher(thread) as thread_mock:
daemon._run()
thread_mock.assert_called_once_with(
target=tcp_server.serve_forever,
kwargs={'poll_interval': daemon.POLL_INTERVAL})
assert thread.daemon is True
thread.start.assert_called_once_with()
def test_run_logs(self, tcp_server, caplog):
with self._tcp_patcher(tcp_server):
daemon = hooks_daemon.HttpHooksCallbackDaemon()
dependencies: bumped pytest libraries to latest versions.
r1221 with self._thread_patcher(mock.Mock()), caplog.at_level(logging.DEBUG):
project: added all source files and assets
r1 daemon._run()
assert_message_in_log(
dependencies: bumped pytest libraries to latest versions.
r1221 caplog.records,
project: added all source files and assets
r1 'Running event loop of callback daemon in background thread',
levelno=logging.DEBUG, module='hooks_daemon')
def test_stop_cleans_up_the_connection(self, tcp_server, caplog):
thread = mock.Mock()
with self._tcp_patcher(tcp_server):
daemon = hooks_daemon.HttpHooksCallbackDaemon()
dependencies: bumped pytest libraries to latest versions.
r1221 with self._thread_patcher(thread), caplog.at_level(logging.DEBUG):
project: added all source files and assets
r1 with daemon:
assert daemon._daemon == tcp_server
assert daemon._callback_thread == thread
assert daemon._daemon is None
assert daemon._callback_thread is None
tcp_server.shutdown.assert_called_with()
thread.join.assert_called_once_with()
assert_message_in_log(
dependencies: bumped pytest libraries to latest versions.
r1221 caplog.records, 'Waiting for background thread to finish.',
project: added all source files and assets
r1 levelno=logging.DEBUG, module='hooks_daemon')
def _tcp_patcher(self, tcp_server):
return mock.patch.object(
hooks_daemon, 'TCPServer', return_value=tcp_server)
def _thread_patcher(self, thread):
return mock.patch.object(
hooks_daemon.threading, 'Thread', return_value=thread)
class TestPrepareHooksDaemon(object):
core: removed pyro4 from Enterprise code. Fixes #5198
r1409 @pytest.mark.parametrize('protocol', ('http',))
Martin Bornhold
tests: Fix tests that depend on the default value of 'pyro4' as backend.
r968 def test_returns_dummy_hooks_callback_daemon_when_using_direct_calls(
self, protocol):
project: added all source files and assets
r1 expected_extras = {'extra1': 'value1'}
callback, extras = hooks_daemon.prepare_callback_daemon(
Martin Bornhold
tests: Fix tests that depend on the default value of 'pyro4' as backend.
r968 expected_extras.copy(), protocol=protocol, use_direct_calls=True)
project: added all source files and assets
r1 assert isinstance(callback, hooks_daemon.DummyHooksCallbackDaemon)
expected_extras['hooks_module'] = 'rhodecode.lib.hooks_daemon'
assert extras == expected_extras
@pytest.mark.parametrize('protocol, expected_class', (
core: removed pyro4 from Enterprise code. Fixes #5198
r1409 ('http', hooks_daemon.HttpHooksCallbackDaemon),
project: added all source files and assets
r1 ))
def test_returns_real_hooks_callback_daemon_when_protocol_is_specified(
self, protocol, expected_class):
expected_extras = {
'extra1': 'value1',
'hooks_protocol': protocol.lower()
}
callback, extras = hooks_daemon.prepare_callback_daemon(
Martin Bornhold
tests: Fix tests that depend on the default value of 'pyro4' as backend.
r968 expected_extras.copy(), protocol=protocol, use_direct_calls=False)
project: added all source files and assets
r1 assert isinstance(callback, expected_class)
hooks_uri = extras.pop('hooks_uri')
assert extras == expected_extras
Martin Bornhold
tests: Fix tests that depend on the default value of 'pyro4' as backend.
r968 @pytest.mark.parametrize('protocol', (
'invalid',
'Http',
'HTTP',
))
def test_raises_on_invalid_protocol(self, protocol):
expected_extras = {
'extra1': 'value1',
'hooks_protocol': protocol.lower()
}
with pytest.raises(Exception):
callback, extras = hooks_daemon.prepare_callback_daemon(
expected_extras.copy(),
protocol=protocol,
use_direct_calls=False)
project: added all source files and assets
r1
class MockRequest(object):
def __init__(self, request):
self.request = request
self.input_stream = StringIO(b'{}'.format(self.request))
self.output_stream = StringIO()
def makefile(self, mode, *args, **kwargs):
return self.output_stream if mode == 'wb' else self.input_stream
class MockServer(object):
def __init__(self, Handler, request):
ip_port = ('0.0.0.0', 8888)
self.request = MockRequest(request)
self.handler = Handler(self.request, ip_port, self)
@pytest.fixture
def tcp_server():
server = mock.Mock()
server.server_address = ('127.0.0.1', 8881)
return server