2018-11-26 22:29:32 +00:00
|
|
|
from unittest import mock
|
2015-12-10 15:22:10 +00:00
|
|
|
import contextlib
|
2022-02-02 23:33:48 +00:00
|
|
|
import datetime
|
2016-06-14 03:23:17 +00:00
|
|
|
import distutils.dist
|
|
|
|
import distutils.errors
|
2022-02-02 23:33:48 +00:00
|
|
|
import json
|
2015-11-18 21:15:37 +00:00
|
|
|
import logging
|
2015-12-10 15:22:10 +00:00
|
|
|
import os
|
2022-02-02 23:33:48 +00:00
|
|
|
import re
|
2016-01-27 21:02:45 +00:00
|
|
|
import time
|
2015-12-10 15:22:10 +00:00
|
|
|
import unittest
|
2019-09-03 01:31:18 +00:00
|
|
|
import uuid
|
2019-09-03 11:29:20 +00:00
|
|
|
import warnings
|
2015-11-18 21:15:37 +00:00
|
|
|
|
2022-02-02 23:33:48 +00:00
|
|
|
from tornado import concurrent, httpserver, httputil, ioloop, log, testing, web
|
2015-11-18 21:15:37 +00:00
|
|
|
|
2019-09-03 11:04:21 +00:00
|
|
|
import sprockets.http.app
|
2015-12-10 15:22:10 +00:00
|
|
|
import sprockets.http.mixins
|
2016-01-27 21:02:45 +00:00
|
|
|
import sprockets.http.runner
|
2019-09-01 14:05:08 +00:00
|
|
|
import sprockets.http.testing
|
2015-11-18 21:15:37 +00:00
|
|
|
import examples
|
|
|
|
|
|
|
|
|
|
|
|
class RecordingHandler(logging.Handler):
|
|
|
|
|
|
|
|
def __init__(self):
|
2018-11-27 00:24:30 +00:00
|
|
|
super().__init__()
|
2015-11-18 21:15:37 +00:00
|
|
|
self.emitted = []
|
|
|
|
|
|
|
|
def emit(self, record):
|
|
|
|
self.emitted.append((record, self.format(record)))
|
|
|
|
|
|
|
|
|
2015-12-10 15:22:10 +00:00
|
|
|
class RaisingHandler(sprockets.http.mixins.ErrorLogger,
|
|
|
|
sprockets.http.mixins.ErrorWriter,
|
2015-11-19 22:29:07 +00:00
|
|
|
web.RequestHandler):
|
2015-11-19 13:02:47 +00:00
|
|
|
|
|
|
|
def get(self, status_code):
|
|
|
|
raise web.HTTPError(int(status_code),
|
|
|
|
reason=self.get_query_argument('reason', None))
|
|
|
|
|
|
|
|
|
2015-12-10 15:22:10 +00:00
|
|
|
class MockHelper(unittest.TestCase):
|
|
|
|
|
|
|
|
def setUp(self):
|
2018-11-27 00:24:30 +00:00
|
|
|
super().setUp()
|
2015-12-10 15:22:10 +00:00
|
|
|
self._mocks = []
|
|
|
|
|
|
|
|
def tearDown(self):
|
2018-11-27 00:24:30 +00:00
|
|
|
super().tearDown()
|
2015-12-10 15:22:10 +00:00
|
|
|
for mocker in self._mocks:
|
|
|
|
mocker.stop()
|
|
|
|
del self._mocks[:]
|
|
|
|
|
2016-06-14 03:23:17 +00:00
|
|
|
def start_mock(self, target, existing_mock=None):
|
|
|
|
target_mock = mock.Mock() if existing_mock is None else existing_mock
|
|
|
|
mocked = mock.patch(target, target_mock)
|
2015-12-10 15:22:10 +00:00
|
|
|
self._mocks.append(mocked)
|
|
|
|
return mocked.start()
|
|
|
|
|
|
|
|
|
|
|
|
@contextlib.contextmanager
|
2019-09-03 01:31:18 +00:00
|
|
|
def override_environment_variable(**env_vars):
|
|
|
|
stash = {}
|
|
|
|
for name, value in env_vars.items():
|
|
|
|
stash[name] = os.environ.pop(name, None)
|
|
|
|
if value is not None:
|
|
|
|
os.environ[name] = value
|
2015-12-10 15:22:10 +00:00
|
|
|
try:
|
|
|
|
yield
|
|
|
|
finally:
|
2019-09-03 01:31:18 +00:00
|
|
|
for name, value in stash.items():
|
|
|
|
os.environ.pop(name, None)
|
|
|
|
if value is not None:
|
|
|
|
os.environ[name] = value
|
2015-12-10 15:22:10 +00:00
|
|
|
|
|
|
|
|
2015-11-19 16:54:23 +00:00
|
|
|
class ErrorLoggerTests(testing.AsyncHTTPTestCase):
|
2015-11-18 21:15:37 +00:00
|
|
|
|
|
|
|
def setUp(self):
|
2018-11-27 00:24:30 +00:00
|
|
|
super().setUp()
|
2015-11-18 21:15:37 +00:00
|
|
|
self.recorder = RecordingHandler()
|
|
|
|
root_logger = logging.getLogger()
|
|
|
|
root_logger.addHandler(self.recorder)
|
|
|
|
|
|
|
|
def tearDown(self):
|
2018-11-27 00:24:30 +00:00
|
|
|
super().tearDown()
|
2015-11-18 21:15:37 +00:00
|
|
|
logging.getLogger().removeHandler(self.recorder)
|
|
|
|
|
|
|
|
def get_app(self):
|
2015-11-19 13:02:47 +00:00
|
|
|
return web.Application([
|
|
|
|
web.url(r'/status/(?P<status_code>\d+)', examples.StatusHandler),
|
|
|
|
web.url(r'/fail/(?P<status_code>\d+)', RaisingHandler),
|
|
|
|
])
|
2015-11-18 21:15:37 +00:00
|
|
|
|
|
|
|
def assert_message_logged(self, level, msg_fmt, *msg_args):
|
|
|
|
suffix = msg_fmt.format(*msg_args)
|
|
|
|
for record, message in self.recorder.emitted:
|
2015-11-19 13:02:47 +00:00
|
|
|
if record.levelno == level and message.endswith(suffix):
|
|
|
|
return
|
|
|
|
self.fail('Expected message ending in "%s" to be logged in %r'
|
|
|
|
% (suffix, self.recorder.emitted))
|
2015-11-18 21:15:37 +00:00
|
|
|
|
|
|
|
def test_that_client_error_logged_as_warning(self):
|
|
|
|
self.fetch('/status/400')
|
|
|
|
self.assert_message_logged(
|
|
|
|
logging.WARNING, 'failed with 400: {}', httputil.responses[400])
|
|
|
|
|
|
|
|
def test_that_server_error_logged_as_error(self):
|
|
|
|
self.fetch('/status/500')
|
|
|
|
self.assert_message_logged(
|
|
|
|
logging.ERROR, 'failed with 500: {}', httputil.responses[500])
|
|
|
|
|
|
|
|
def test_that_custom_status_codes_logged_as_unknown(self):
|
|
|
|
self.fetch('/status/623')
|
|
|
|
self.assert_message_logged(logging.ERROR, 'failed with 623: Unknown')
|
|
|
|
|
|
|
|
def test_that_custom_reasons_are_supported(self):
|
|
|
|
self.fetch('/status/456?reason=oops')
|
|
|
|
self.assert_message_logged(logging.WARNING, 'failed with 456: oops')
|
2015-11-19 13:02:47 +00:00
|
|
|
|
|
|
|
def test_that_status_code_extracted_from_http_errors(self):
|
|
|
|
self.fetch('/fail/400')
|
|
|
|
self.assert_message_logged(
|
|
|
|
logging.WARNING, 'failed with 400: {}', httputil.responses[400])
|
|
|
|
|
|
|
|
def test_that_reason_extracted_from_http_errors(self):
|
|
|
|
self.fetch('/fail/400?reason=oopsie')
|
|
|
|
self.assert_message_logged(logging.WARNING, 'failed with 400: oopsie')
|
2015-11-19 16:51:34 +00:00
|
|
|
|
|
|
|
def test_that_log_message_is_honored(self):
|
|
|
|
self.fetch('/status/400?log_message=injected%20message')
|
|
|
|
self.assert_message_logged(logging.WARNING,
|
|
|
|
'failed with 400: injected message')
|
2015-11-19 22:29:07 +00:00
|
|
|
|
|
|
|
|
|
|
|
class ErrorWriterTests(testing.AsyncHTTPTestCase):
|
|
|
|
|
2015-11-20 12:25:05 +00:00
|
|
|
def setUp(self):
|
|
|
|
self._application = None
|
2018-11-27 00:24:30 +00:00
|
|
|
super().setUp()
|
2015-11-20 12:25:05 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def application(self):
|
|
|
|
if self._application is None:
|
|
|
|
self._application = web.Application([
|
|
|
|
web.url(r'/status/(?P<status_code>\d+)',
|
|
|
|
examples.StatusHandler),
|
|
|
|
web.url(r'/fail/(?P<status_code>\d+)', RaisingHandler),
|
|
|
|
])
|
|
|
|
return self._application
|
|
|
|
|
2015-11-19 22:29:07 +00:00
|
|
|
def get_app(self):
|
2015-11-20 12:25:05 +00:00
|
|
|
return self.application
|
2015-11-19 22:29:07 +00:00
|
|
|
|
|
|
|
def _decode_response(self, response):
|
|
|
|
content_type = response.headers['Content-Type']
|
|
|
|
self.assertTrue(content_type.startswith('application/json'),
|
|
|
|
'Incorrect content type received')
|
|
|
|
return json.loads(response.body.decode('utf-8'))
|
|
|
|
|
|
|
|
def test_that_error_json_contains_error_type(self):
|
|
|
|
response = self.fetch('/fail/400')
|
|
|
|
self.assertEqual(response.code, 400)
|
|
|
|
|
|
|
|
exc = web.HTTPError(400)
|
|
|
|
body = self._decode_response(response)
|
|
|
|
self.assertEqual(body['type'], exc.__class__.__name__)
|
|
|
|
|
|
|
|
def test_that_error_json_contains_error_message(self):
|
|
|
|
response = self.fetch('/fail/400')
|
|
|
|
self.assertEqual(response.code, 400)
|
|
|
|
|
|
|
|
exc = web.HTTPError(400)
|
|
|
|
body = self._decode_response(response)
|
|
|
|
self.assertEqual(body['message'], str(exc))
|
|
|
|
|
|
|
|
def test_that_error_json_ignores_the_log_message(self):
|
|
|
|
response = self.fetch('/status/500?log_message=something%20good')
|
|
|
|
self.assertEqual(response.code, 500)
|
|
|
|
|
|
|
|
body = self._decode_response(response)
|
|
|
|
self.assertEqual(body['message'], httputil.responses[500])
|
|
|
|
|
|
|
|
def test_that_error_json_contains_type_none_for_non_exceptions(self):
|
|
|
|
response = self.fetch('/status/500')
|
|
|
|
self.assertEqual(response.code, 500)
|
|
|
|
|
|
|
|
body = self._decode_response(response)
|
|
|
|
self.assertIsNone(body['type'])
|
|
|
|
|
|
|
|
def test_that_error_json_contains_reason_for_non_exceptions(self):
|
|
|
|
response = self.fetch('/status/500')
|
|
|
|
self.assertEqual(response.code, 500)
|
|
|
|
|
|
|
|
body = self._decode_response(response)
|
|
|
|
self.assertEqual(body['message'], httputil.responses[500])
|
|
|
|
|
|
|
|
def test_that_error_json_reason_contains_unknown_in_some_cases(self):
|
|
|
|
response = self.fetch('/status/567')
|
|
|
|
self.assertEqual(response.code, 567)
|
|
|
|
|
|
|
|
body = self._decode_response(response)
|
|
|
|
self.assertEqual(body['message'], 'Unknown')
|
2015-11-20 12:25:05 +00:00
|
|
|
|
|
|
|
def test_that_error_json_honors_serve_traceback(self):
|
|
|
|
self.application.settings['serve_traceback'] = True
|
|
|
|
|
|
|
|
response = self.fetch('/fail/400')
|
|
|
|
self.assertEqual(response.code, 400)
|
|
|
|
|
|
|
|
body = self._decode_response(response)
|
|
|
|
self.assertGreater(len(body['traceback']), 0)
|
2015-11-20 19:14:39 +00:00
|
|
|
|
|
|
|
def test_that_mediatype_mixin_is_honored(self):
|
|
|
|
send_response = mock.Mock()
|
|
|
|
setattr(examples.StatusHandler, 'send_response', send_response)
|
|
|
|
response = self.fetch('/status/500')
|
|
|
|
self.assertEqual(response.code, 500)
|
|
|
|
send_response.assert_called_once_with({
|
|
|
|
'type': None,
|
|
|
|
'message': 'Internal Server Error',
|
|
|
|
'traceback': None
|
|
|
|
})
|
|
|
|
delattr(examples.StatusHandler, 'send_response')
|
2015-12-10 15:22:10 +00:00
|
|
|
|
|
|
|
|
|
|
|
class RunTests(MockHelper, unittest.TestCase):
|
|
|
|
|
|
|
|
def setUp(self):
|
2018-11-27 00:24:30 +00:00
|
|
|
super().setUp()
|
2015-12-10 15:22:10 +00:00
|
|
|
self.runner_cls = self.start_mock('sprockets.http.runner.Runner')
|
2015-12-10 15:41:59 +00:00
|
|
|
self.get_logging_config = self.start_mock(
|
2015-12-10 15:44:52 +00:00
|
|
|
'sprockets.http._get_logging_config')
|
2015-12-10 15:41:59 +00:00
|
|
|
self.get_logging_config.return_value = {'version': 1}
|
|
|
|
self.logging_dict_config = self.start_mock(
|
|
|
|
'sprockets.http.logging.config').dictConfig
|
2015-12-10 15:22:10 +00:00
|
|
|
|
2022-02-02 22:20:49 +00:00
|
|
|
self.app = mock.Mock()
|
|
|
|
self.app.settings = {}
|
|
|
|
self.create_app = mock.Mock(return_value=self.app)
|
|
|
|
|
2015-12-10 15:22:10 +00:00
|
|
|
@property
|
|
|
|
def runner_instance(self):
|
|
|
|
return self.runner_cls.return_value
|
|
|
|
|
|
|
|
def test_that_runner_run_called_with_created_application(self):
|
2022-02-02 22:20:49 +00:00
|
|
|
sprockets.http.run(self.create_app)
|
|
|
|
self.assertEqual(self.create_app.call_count, 1)
|
|
|
|
self.runner_cls.assert_called_once_with(self.create_app.return_value)
|
2015-12-10 15:22:10 +00:00
|
|
|
|
|
|
|
def test_that_debug_envvar_enables_debug_flag(self):
|
2019-09-03 01:31:18 +00:00
|
|
|
with override_environment_variable(DEBUG='1'):
|
2022-02-02 22:20:49 +00:00
|
|
|
sprockets.http.run(self.create_app)
|
|
|
|
self.create_app.assert_called_once_with(debug=True)
|
2015-12-10 15:41:59 +00:00
|
|
|
self.get_logging_config.assert_called_once_with(True)
|
2015-12-10 15:22:10 +00:00
|
|
|
|
|
|
|
def test_that_false_debug_envvar_disables_debug_flag(self):
|
2019-09-03 01:31:18 +00:00
|
|
|
with override_environment_variable(DEBUG='0'):
|
2022-02-02 22:20:49 +00:00
|
|
|
sprockets.http.run(self.create_app)
|
|
|
|
self.create_app.assert_called_once_with(debug=False)
|
2015-12-10 15:41:59 +00:00
|
|
|
self.get_logging_config.assert_called_once_with(False)
|
2015-12-10 15:22:10 +00:00
|
|
|
|
|
|
|
def test_that_unset_debug_envvar_disables_debug_flag(self):
|
2019-09-03 01:31:18 +00:00
|
|
|
with override_environment_variable(DEBUG=None):
|
2022-02-02 22:20:49 +00:00
|
|
|
sprockets.http.run(self.create_app)
|
|
|
|
self.create_app.assert_called_once_with(debug=False)
|
2015-12-10 15:41:59 +00:00
|
|
|
self.get_logging_config.assert_called_once_with(False)
|
2015-12-10 15:22:10 +00:00
|
|
|
|
|
|
|
def test_that_port_defaults_to_8000(self):
|
2022-02-02 22:20:49 +00:00
|
|
|
sprockets.http.run(self.create_app)
|
2016-11-03 11:43:23 +00:00
|
|
|
self.runner_instance.run.assert_called_once_with(8000, mock.ANY)
|
2015-12-10 15:22:10 +00:00
|
|
|
|
|
|
|
def test_that_port_envvar_sets_port_number(self):
|
2019-09-03 01:31:18 +00:00
|
|
|
with override_environment_variable(PORT='8888'):
|
2022-02-02 22:20:49 +00:00
|
|
|
sprockets.http.run(self.create_app)
|
2016-11-03 11:43:23 +00:00
|
|
|
self.runner_instance.run.assert_called_once_with(8888, mock.ANY)
|
2015-12-10 15:22:10 +00:00
|
|
|
|
|
|
|
def test_that_port_kwarg_sets_port_number(self):
|
2022-02-02 22:20:49 +00:00
|
|
|
sprockets.http.run(self.create_app, settings={'port': 8888})
|
2015-12-10 15:41:59 +00:00
|
|
|
self.runner_instance.run.assert_called_once_with(8888, mock.ANY)
|
2015-12-10 15:22:10 +00:00
|
|
|
|
|
|
|
def test_that_number_of_procs_defaults_to_zero(self):
|
2022-02-02 22:20:49 +00:00
|
|
|
sprockets.http.run(self.create_app)
|
2015-12-10 15:41:59 +00:00
|
|
|
self.runner_instance.run.assert_called_once_with(mock.ANY, 0)
|
2015-12-10 15:22:10 +00:00
|
|
|
|
|
|
|
def test_that_number_of_process_kwarg_sets_number_of_procs(self):
|
2022-02-02 22:20:49 +00:00
|
|
|
sprockets.http.run(self.create_app, settings={'number_of_procs': 1})
|
2015-12-10 15:41:59 +00:00
|
|
|
self.runner_instance.run.assert_called_once_with(mock.ANY, 1)
|
|
|
|
|
|
|
|
def test_that_logging_dict_config_is_called_appropriately(self):
|
2022-02-02 22:20:49 +00:00
|
|
|
sprockets.http.run(self.create_app)
|
2015-12-10 15:41:59 +00:00
|
|
|
self.logging_dict_config.assert_called_once_with(
|
|
|
|
self.get_logging_config.return_value)
|
2015-12-10 15:48:25 +00:00
|
|
|
|
|
|
|
def test_that_logconfig_override_is_used(self):
|
2022-02-02 22:20:49 +00:00
|
|
|
sprockets.http.run(self.create_app, log_config=mock.sentinel.config)
|
2015-12-10 15:48:25 +00:00
|
|
|
self.logging_dict_config.assert_called_once_with(
|
|
|
|
mock.sentinel.config)
|
2016-01-27 21:02:45 +00:00
|
|
|
|
2019-09-03 11:29:20 +00:00
|
|
|
def test_that_not_specifying_logging_config_is_deprecated(self):
|
|
|
|
with warnings.catch_warnings(record=True) as captured:
|
|
|
|
warnings.simplefilter('always')
|
2022-02-02 22:20:49 +00:00
|
|
|
sprockets.http.run(self.create_app)
|
2019-09-03 11:29:20 +00:00
|
|
|
|
|
|
|
self.assertEqual(len(captured), 1)
|
|
|
|
self.assertTrue(issubclass(captured[0].category, DeprecationWarning))
|
|
|
|
|
2022-02-02 16:25:16 +00:00
|
|
|
@mock.patch('sentry_sdk.init')
|
2022-02-02 22:20:49 +00:00
|
|
|
def test_that_sentry_is_initialized_with_implied_overrides(
|
|
|
|
self, mock_sentry_init):
|
|
|
|
self.app.settings = {
|
2022-02-02 16:25:16 +00:00
|
|
|
'environment': 'whatever',
|
|
|
|
'version': 'a.b.c',
|
|
|
|
}
|
2022-02-02 22:20:49 +00:00
|
|
|
sprockets.http.run(self.create_app)
|
2022-02-02 16:25:16 +00:00
|
|
|
mock_sentry_init.assert_called_once_with(
|
|
|
|
integrations=sprockets.http._sentry_integrations,
|
|
|
|
release='a.b.c',
|
|
|
|
environment='whatever',
|
|
|
|
)
|
|
|
|
|
2022-02-02 22:20:49 +00:00
|
|
|
@mock.patch('sentry_sdk.init')
|
|
|
|
def test_that_sentry_is_initialized_with_explicit_overrides(
|
|
|
|
self, mock_sentry_init):
|
|
|
|
self.app.settings = {
|
|
|
|
'sentry_sdk_init': {
|
|
|
|
'before_send': mock.sentinel.before_send,
|
|
|
|
'integrations': mock.sentinel.integrations,
|
|
|
|
'environment': mock.sentinel.environment,
|
|
|
|
'release': mock.sentinel.release,
|
|
|
|
},
|
|
|
|
'environment': 'whatever',
|
|
|
|
'version': 'a.b.c',
|
|
|
|
}
|
|
|
|
sprockets.http.run(self.create_app)
|
|
|
|
mock_sentry_init.assert_called_once_with(
|
|
|
|
integrations=mock.sentinel.integrations,
|
|
|
|
before_send=mock.sentinel.before_send,
|
|
|
|
release=mock.sentinel.release,
|
|
|
|
environment=mock.sentinel.environment,
|
|
|
|
)
|
|
|
|
|
2022-02-02 16:25:16 +00:00
|
|
|
@mock.patch('sentry_sdk.init')
|
|
|
|
def test_that_sentry_is_initialized_with_defaults(self, mock_sentry_init):
|
2022-02-02 22:20:49 +00:00
|
|
|
self.app.settings = {}
|
|
|
|
sprockets.http.run(self.create_app)
|
2022-02-02 16:25:16 +00:00
|
|
|
mock_sentry_init.assert_called_once_with(
|
|
|
|
integrations=sprockets.http._sentry_integrations,
|
|
|
|
release=None,
|
|
|
|
environment=None,
|
|
|
|
)
|
|
|
|
|
2016-01-27 21:02:45 +00:00
|
|
|
|
2016-01-28 12:02:06 +00:00
|
|
|
class CallbackTests(MockHelper, unittest.TestCase):
|
2016-01-27 21:02:45 +00:00
|
|
|
|
|
|
|
def setUp(self):
|
2018-11-27 00:24:30 +00:00
|
|
|
super().setUp()
|
2016-01-27 21:02:45 +00:00
|
|
|
self.shutdown_callback = mock.Mock()
|
2016-01-27 23:24:02 +00:00
|
|
|
self.before_run_callback = mock.Mock()
|
2016-01-28 12:02:06 +00:00
|
|
|
self.application = self.make_application()
|
|
|
|
|
|
|
|
self.io_loop = mock.Mock(_callbacks=[], _timeouts=[])
|
|
|
|
self.io_loop.time.side_effect = time.time
|
|
|
|
ioloop_module = self.start_mock('sprockets.http.runner.ioloop')
|
|
|
|
ioloop_module.IOLoop.instance.return_value = self.io_loop
|
|
|
|
|
|
|
|
self.start_mock('sprockets.http.runner.httpserver')
|
2016-01-27 21:02:45 +00:00
|
|
|
|
|
|
|
def make_application(self, **settings):
|
2016-01-28 12:02:06 +00:00
|
|
|
application = mock.Mock()
|
|
|
|
application.settings = settings.copy()
|
|
|
|
application.runner_callbacks = {
|
2016-01-27 23:24:02 +00:00
|
|
|
'before_run': [self.before_run_callback],
|
2016-01-27 21:02:45 +00:00
|
|
|
'shutdown': [self.shutdown_callback],
|
|
|
|
}
|
2016-01-28 12:02:06 +00:00
|
|
|
return application
|
2016-01-27 21:02:45 +00:00
|
|
|
|
|
|
|
def test_that_shutdown_callback_invoked(self):
|
2016-01-28 12:02:06 +00:00
|
|
|
runner = sprockets.http.runner.Runner(self.application)
|
|
|
|
runner.run(8080)
|
|
|
|
runner._shutdown()
|
2016-01-27 21:02:45 +00:00
|
|
|
self.shutdown_callback.assert_called_once_with(self.application)
|
2016-01-27 23:24:02 +00:00
|
|
|
|
2016-01-28 12:08:50 +00:00
|
|
|
def test_that_exceptions_from_shutdown_callbacks_are_ignored(self):
|
|
|
|
another_callback = mock.Mock()
|
|
|
|
self.application.runner_callbacks['shutdown'].append(another_callback)
|
|
|
|
self.shutdown_callback.side_effect = Exception
|
|
|
|
|
|
|
|
runner = sprockets.http.runner.Runner(self.application)
|
|
|
|
runner.run(8080)
|
|
|
|
runner._shutdown()
|
|
|
|
self.shutdown_callback.assert_called_once_with(self.application)
|
|
|
|
another_callback.assert_called_once_with(self.application)
|
|
|
|
|
2016-01-27 23:24:02 +00:00
|
|
|
def test_that_before_run_callback_invoked(self):
|
2016-01-28 12:02:06 +00:00
|
|
|
runner = sprockets.http.runner.Runner(self.application)
|
|
|
|
runner.run(8080)
|
|
|
|
self.before_run_callback.assert_called_once_with(self.application,
|
|
|
|
self.io_loop)
|
2016-01-28 12:08:50 +00:00
|
|
|
|
|
|
|
def test_that_exceptions_from_before_run_callbacks_are_terminal(self):
|
|
|
|
another_callback = mock.Mock()
|
|
|
|
self.application.runner_callbacks['before_run'].append(
|
|
|
|
another_callback)
|
|
|
|
self.before_run_callback.side_effect = Exception
|
|
|
|
|
2016-01-28 12:09:32 +00:00
|
|
|
sys_exit = mock.Mock()
|
|
|
|
sys_exit.side_effect = SystemExit
|
|
|
|
with mock.patch('sprockets.http.runner.sys') as sys_module:
|
|
|
|
sys_module.exit = sys_exit
|
|
|
|
with self.assertRaises(SystemExit):
|
|
|
|
runner = sprockets.http.runner.Runner(self.application)
|
|
|
|
runner.run(8080)
|
2016-01-28 12:08:50 +00:00
|
|
|
|
|
|
|
self.before_run_callback.assert_called_once_with(self.application,
|
|
|
|
self.io_loop)
|
|
|
|
another_callback.assert_not_called()
|
|
|
|
self.shutdown_callback.assert_called_once_with(self.application)
|
2016-01-28 12:09:32 +00:00
|
|
|
sys_exit.assert_called_once_with(70)
|
2016-01-28 12:49:20 +00:00
|
|
|
|
|
|
|
|
|
|
|
class RunnerTests(MockHelper, unittest.TestCase):
|
|
|
|
|
|
|
|
def setUp(self):
|
2018-11-27 00:24:30 +00:00
|
|
|
super().setUp()
|
2016-01-28 12:49:20 +00:00
|
|
|
self.application = mock.Mock()
|
2018-01-25 01:07:12 +00:00
|
|
|
self.application.settings = {
|
|
|
|
'xheaders': True,
|
|
|
|
'max_body_size': 2048,
|
|
|
|
'max_buffer_size': 1024
|
|
|
|
}
|
2016-01-28 12:49:20 +00:00
|
|
|
self.application.runner_callbacks = {}
|
|
|
|
|
|
|
|
self.io_loop = mock.Mock()
|
|
|
|
self.io_loop._callbacks = []
|
|
|
|
self.io_loop._timeouts = []
|
|
|
|
self.io_loop.time = time.time
|
|
|
|
ioloop_module = self.start_mock('sprockets.http.runner.ioloop')
|
|
|
|
ioloop_module.IOLoop.instance.return_value = self.io_loop
|
|
|
|
|
2018-01-28 19:32:41 +00:00
|
|
|
self.http_server = mock.Mock(spec=httpserver.HTTPServer)
|
2018-01-25 01:07:12 +00:00
|
|
|
self.httpserver_module = \
|
|
|
|
self.start_mock('sprockets.http.runner.httpserver')
|
|
|
|
self.httpserver_module.HTTPServer.return_value = self.http_server
|
2016-01-28 12:49:20 +00:00
|
|
|
|
|
|
|
def test_that_run_starts_ioloop(self):
|
|
|
|
runner = sprockets.http.runner.Runner(self.application)
|
|
|
|
runner.run(8000)
|
|
|
|
self.io_loop.start.assert_called_once_with()
|
|
|
|
|
2018-01-25 01:07:12 +00:00
|
|
|
def test_that_http_server_settings_are_used(self):
|
|
|
|
runner = sprockets.http.runner.Runner(self.application)
|
|
|
|
runner.run(8000)
|
|
|
|
self.httpserver_module.HTTPServer.assert_called_once_with(
|
|
|
|
self.application, **self.application.settings)
|
|
|
|
|
2016-01-28 12:49:20 +00:00
|
|
|
def test_that_production_run_starts_in_multiprocess_mode(self):
|
|
|
|
runner = sprockets.http.runner.Runner(self.application)
|
|
|
|
runner.run(8000)
|
2018-01-28 19:32:41 +00:00
|
|
|
|
|
|
|
self.assertTrue(self.http_server.bind.called)
|
|
|
|
args, kwargs = self.http_server.bind.call_args_list[0]
|
|
|
|
self.assertEqual(args, (8000, ))
|
|
|
|
|
2016-01-28 12:49:20 +00:00
|
|
|
self.http_server.start.assert_called_once_with(0)
|
|
|
|
|
2018-01-28 19:32:41 +00:00
|
|
|
def test_that_production_enables_reuse_port(self):
|
|
|
|
runner = sprockets.http.runner.Runner(self.application)
|
|
|
|
runner.run(8000)
|
|
|
|
|
|
|
|
self.assertTrue(self.http_server.bind.called)
|
|
|
|
args, kwargs = self.http_server.bind.call_args_list[0]
|
|
|
|
self.assertEqual(args, (8000, ))
|
|
|
|
self.assertEqual(kwargs['reuse_port'], True)
|
|
|
|
|
2016-01-28 12:49:20 +00:00
|
|
|
def test_that_debug_run_starts_in_singleprocess_mode(self):
|
|
|
|
self.application.settings['debug'] = True
|
|
|
|
runner = sprockets.http.runner.Runner(self.application)
|
|
|
|
runner.run(8000)
|
|
|
|
self.http_server.listen.assert_called_once_with(8000)
|
|
|
|
self.http_server.start.assert_not_called()
|
|
|
|
|
|
|
|
def test_that_initializer_creates_runner_callbacks_dict(self):
|
|
|
|
application = web.Application()
|
2016-06-14 03:23:17 +00:00
|
|
|
sprockets.http.runner.Runner(application)
|
2016-01-28 12:49:20 +00:00
|
|
|
self.assertEqual(application.runner_callbacks['before_run'], [])
|
2016-03-10 22:10:31 +00:00
|
|
|
self.assertEqual(application.runner_callbacks['on_start'], [])
|
2016-01-28 12:49:20 +00:00
|
|
|
self.assertEqual(application.runner_callbacks['shutdown'], [])
|
|
|
|
|
|
|
|
def test_that_signal_handler_invokes_shutdown(self):
|
|
|
|
with mock.patch('sprockets.http.runner.signal') as signal_module:
|
|
|
|
runner = sprockets.http.runner.Runner(self.application)
|
|
|
|
runner.run(8000)
|
|
|
|
|
|
|
|
signal_module.signal.assert_any_call(signal_module.SIGINT,
|
|
|
|
runner._on_signal)
|
|
|
|
signal_module.signal.assert_any_call(signal_module.SIGTERM,
|
|
|
|
runner._on_signal)
|
|
|
|
runner._on_signal(signal_module.SIGINT, mock.Mock())
|
|
|
|
self.io_loop.add_callback_from_signal.assert_called_once_with(
|
|
|
|
runner._shutdown)
|
|
|
|
|
|
|
|
def test_that_shutdown_stops_after_timelimit(self):
|
|
|
|
def add_timeout(_, callback):
|
2017-06-08 20:56:28 +00:00
|
|
|
time.sleep(0.1)
|
2016-01-28 12:49:20 +00:00
|
|
|
callback()
|
|
|
|
self.io_loop.add_timeout = mock.Mock(side_effect=add_timeout)
|
|
|
|
|
|
|
|
self.io_loop._timeouts = [mock.Mock()]
|
|
|
|
runner = sprockets.http.runner.Runner(self.application)
|
|
|
|
runner.shutdown_limit = 0.25
|
2019-09-01 13:21:20 +00:00
|
|
|
runner.wait_timeout = 0.05
|
2016-01-28 12:49:20 +00:00
|
|
|
runner.run(8000)
|
|
|
|
runner._shutdown()
|
|
|
|
self.io_loop.stop.assert_called_once_with()
|
|
|
|
self.assertNotEqual(self.io_loop._timeouts, [])
|
2016-03-10 22:10:31 +00:00
|
|
|
|
2020-07-18 12:59:59 +00:00
|
|
|
def test_that_calling_with_non_sprockets_application_is_deprecated(self):
|
|
|
|
with warnings.catch_warnings(record=True) as captured:
|
|
|
|
warnings.filterwarnings(action='always', module='sprockets')
|
|
|
|
sprockets.http.runner.Runner(web.Application())
|
|
|
|
for warning in captured:
|
|
|
|
if 'sprockets.app.Application' in str(warning.message):
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
self.fail('expected deprecation warning from runnr.Runner')
|
|
|
|
|
|
|
|
with warnings.catch_warnings(record=True) as captured:
|
|
|
|
warnings.filterwarnings(action='always', module='sprockets')
|
|
|
|
sprockets.http.runner.Runner(sprockets.http.app.Application())
|
|
|
|
self.assertEqual(len(captured), 0)
|
|
|
|
|
2016-03-10 22:10:31 +00:00
|
|
|
|
|
|
|
class AsyncRunTests(unittest.TestCase):
|
|
|
|
|
|
|
|
def test_that_on_start_callbacks_are_invoked(self):
|
|
|
|
future = concurrent.Future()
|
|
|
|
|
|
|
|
def on_started(*args, **kwargs):
|
|
|
|
with mock.patch('sprockets.http.runner.Runner.stop_server'):
|
|
|
|
runner._shutdown()
|
|
|
|
future.set_result(True)
|
|
|
|
|
|
|
|
application = web.Application()
|
|
|
|
with mock.patch('sprockets.http.runner.Runner.start_server'):
|
|
|
|
runner = sprockets.http.runner.Runner(application,
|
|
|
|
on_start=[on_started])
|
|
|
|
runner.run(8000)
|
|
|
|
self.assertTrue(future.result())
|
|
|
|
|
|
|
|
def test_that_shutdown_futures_are_waited_on(self):
|
|
|
|
future = concurrent.Future()
|
|
|
|
|
|
|
|
def on_started(*args, **kwargs):
|
|
|
|
with mock.patch('sprockets.http.runner.Runner.stop_server'):
|
|
|
|
runner._shutdown()
|
|
|
|
|
|
|
|
def on_shutdown(*args, **kwargs):
|
|
|
|
def shutdown_complete():
|
|
|
|
future.set_result(True)
|
|
|
|
|
|
|
|
ioloop.IOLoop.current().add_timeout(1, shutdown_complete)
|
|
|
|
return future
|
|
|
|
|
|
|
|
application = web.Application()
|
|
|
|
with mock.patch('sprockets.http.runner.Runner.start_server'):
|
|
|
|
runner = sprockets.http.runner.Runner(application,
|
|
|
|
on_start=[on_started],
|
|
|
|
shutdown=[on_shutdown])
|
|
|
|
runner.run(8000)
|
|
|
|
|
|
|
|
self.assertTrue(future.result())
|
2016-06-14 03:23:17 +00:00
|
|
|
|
|
|
|
|
|
|
|
class RunCommandTests(MockHelper, unittest.TestCase):
|
|
|
|
|
|
|
|
def setUp(self):
|
2018-11-27 00:24:30 +00:00
|
|
|
super().setUp()
|
2016-06-14 03:23:17 +00:00
|
|
|
self.distribution = mock.Mock(spec=distutils.dist.Distribution,
|
|
|
|
verbose=3)
|
|
|
|
|
|
|
|
def test_that_environment_file_is_processed(self):
|
|
|
|
os_module = self.start_mock('sprockets.http.runner.os')
|
|
|
|
os_module.environ = {'SHOULD_BE': 'REMOVED'}
|
|
|
|
os_module.path.exists.return_value = True
|
|
|
|
|
|
|
|
open_mock = mock.mock_open(read_data='\n'.join([
|
|
|
|
'export SIMPLE=1',
|
|
|
|
'NOT_EXPORTED=2 # with comment too!',
|
|
|
|
'export DQUOTED="value with space"',
|
|
|
|
"export SQUOTED='value with space'",
|
|
|
|
'BAD LINE',
|
|
|
|
'# commented line',
|
|
|
|
'SHOULD_BE=',
|
|
|
|
]))
|
2018-11-26 22:29:32 +00:00
|
|
|
self.start_mock('builtins.open', open_mock)
|
2016-06-14 03:23:17 +00:00
|
|
|
|
|
|
|
command = sprockets.http.runner.RunCommand(self.distribution)
|
|
|
|
command.dry_run = True
|
|
|
|
command._find_callable = mock.Mock()
|
|
|
|
command.env_file = 'name.conf'
|
|
|
|
command.application = 'required.to:exist'
|
|
|
|
|
|
|
|
command.ensure_finalized()
|
|
|
|
command.run()
|
|
|
|
|
|
|
|
os_module.path.exists.assert_called_once_with('name.conf')
|
|
|
|
self.assertEqual(
|
|
|
|
sorted(list(os_module.environ.keys())),
|
|
|
|
sorted(['SIMPLE', 'NOT_EXPORTED', 'DQUOTED', 'SQUOTED']))
|
|
|
|
self.assertEqual(os_module.environ['SIMPLE'], '1')
|
|
|
|
self.assertEqual(os_module.environ['NOT_EXPORTED'], '2')
|
|
|
|
self.assertEqual(os_module.environ['DQUOTED'], 'value with space')
|
|
|
|
self.assertEqual(os_module.environ['SQUOTED'], 'value with space')
|
|
|
|
|
|
|
|
def test_that_port_option_sets_environment_variable(self):
|
|
|
|
os_module = self.start_mock('sprockets.http.runner.os')
|
|
|
|
os_module.environ = {}
|
|
|
|
os_module.path.exists.return_value = True
|
|
|
|
|
|
|
|
open_mock = mock.mock_open(read_data='PORT=2')
|
2018-11-26 22:29:32 +00:00
|
|
|
self.start_mock('builtins.open', open_mock)
|
2016-06-14 03:23:17 +00:00
|
|
|
|
|
|
|
command = sprockets.http.runner.RunCommand(self.distribution)
|
|
|
|
command.dry_run = True
|
|
|
|
command._find_callable = mock.Mock()
|
|
|
|
command.env_file = 'name.conf'
|
|
|
|
command.application = 'required.to:exist'
|
|
|
|
command.port = '3'
|
|
|
|
|
|
|
|
command.ensure_finalized()
|
|
|
|
command.run()
|
|
|
|
|
|
|
|
self.assertEqual(os_module.environ['PORT'], '3')
|
|
|
|
|
|
|
|
def test_that_application_callable_is_created(self):
|
|
|
|
# this is somewhat less hacky than patching __import__ ...
|
|
|
|
# just add a "recorder" around the _find_callable method
|
|
|
|
# in a not so hacky way
|
|
|
|
command = sprockets.http.runner.RunCommand(self.distribution)
|
|
|
|
|
|
|
|
result_closure = {'real_method': command._find_callable}
|
|
|
|
|
|
|
|
def patched():
|
|
|
|
result_closure['result'] = result_closure['real_method']()
|
|
|
|
return result_closure['result']
|
|
|
|
|
|
|
|
command.dry_run = True
|
|
|
|
command.application = 'sprockets.http.runner:Runner'
|
|
|
|
command._find_callable = patched
|
|
|
|
|
|
|
|
command.ensure_finalized()
|
|
|
|
command.run()
|
|
|
|
self.assertEqual(result_closure['result'],
|
|
|
|
sprockets.http.runner.Runner)
|
|
|
|
|
|
|
|
def test_that_finalize_options_requires_application_option(self):
|
|
|
|
command = sprockets.http.runner.RunCommand(self.distribution)
|
|
|
|
command.env_file = 'not used here'
|
|
|
|
with self.assertRaises(distutils.errors.DistutilsArgError):
|
|
|
|
command.ensure_finalized()
|
|
|
|
|
|
|
|
def test_that_finalize_options_with_nonexistant_env_file_fails(self):
|
|
|
|
os_module = self.start_mock('sprockets.http.runner.os')
|
|
|
|
os_module.path.exists.return_value = False
|
|
|
|
|
|
|
|
command = sprockets.http.runner.RunCommand(self.distribution)
|
2016-07-31 13:30:06 +00:00
|
|
|
command.application = examples.Application
|
2016-06-14 03:23:17 +00:00
|
|
|
command.env_file = 'file.conf'
|
|
|
|
with self.assertRaises(distutils.errors.DistutilsArgError):
|
|
|
|
command.ensure_finalized()
|
|
|
|
os_module.path.exists.assert_called_once_with('file.conf')
|
|
|
|
|
|
|
|
def test_that_sprockets_http_run_is_called_appropriately(self):
|
|
|
|
# yes this god awful path is actually correct :/
|
|
|
|
run_function = self.start_mock(
|
|
|
|
'sprockets.http.runner.sprockets.http.run')
|
|
|
|
|
|
|
|
command = sprockets.http.runner.RunCommand(self.distribution)
|
|
|
|
|
|
|
|
result_closure = {'real_method': command._find_callable}
|
|
|
|
|
|
|
|
def patched():
|
|
|
|
result_closure['result'] = result_closure['real_method']()
|
|
|
|
return result_closure['result']
|
|
|
|
|
2016-07-31 13:30:06 +00:00
|
|
|
command.application = 'examples:Application'
|
2016-06-14 03:23:17 +00:00
|
|
|
command.dry_run = False
|
|
|
|
command._find_callable = patched
|
|
|
|
|
|
|
|
command.ensure_finalized()
|
|
|
|
command.run()
|
|
|
|
|
|
|
|
run_function.assert_called_once_with(result_closure['result'])
|
2019-09-01 14:05:08 +00:00
|
|
|
|
|
|
|
|
|
|
|
class TestCaseTests(unittest.TestCase):
|
|
|
|
|
|
|
|
class FakeTest(sprockets.http.testing.SprocketsHttpTestCase):
|
|
|
|
def get_app(self):
|
|
|
|
self.app = mock.Mock()
|
|
|
|
return self.app
|
|
|
|
|
|
|
|
def runTest(self):
|
|
|
|
pass
|
|
|
|
|
|
|
|
def test_that_setup_calls_start(self):
|
|
|
|
test_case = self.FakeTest()
|
|
|
|
test_case.setUp()
|
|
|
|
test_case.app.start.assert_called_once_with(test_case.io_loop)
|
|
|
|
|
|
|
|
def test_that_teardown_calls_stop(self):
|
|
|
|
test_case = self.FakeTest()
|
|
|
|
test_case.setUp()
|
|
|
|
test_case.io_loop = mock.Mock()
|
|
|
|
test_case.tearDown()
|
|
|
|
test_case.app.stop.assert_called_once_with(
|
|
|
|
test_case.io_loop, test_case.shutdown_limit,
|
|
|
|
test_case.wait_timeout)
|
2019-09-03 01:31:18 +00:00
|
|
|
|
|
|
|
|
|
|
|
class CorrelationFilterTests(unittest.TestCase):
|
|
|
|
def setUp(self):
|
|
|
|
super(CorrelationFilterTests, self).setUp()
|
|
|
|
self.logger = logging.getLogger()
|
|
|
|
self.record = self.logger.makeRecord(
|
|
|
|
'name', logging.INFO, 'functionName', 42, 'hello %s',
|
|
|
|
tuple(['world']), (None, None, None))
|
|
|
|
self.filter = sprockets.http._CorrelationFilter()
|
|
|
|
|
|
|
|
def test_that_correlation_filter_adds_correlation_id(self):
|
|
|
|
self.filter.filter(self.record)
|
|
|
|
self.assertTrue(hasattr(self.record, 'correlation-id'))
|
|
|
|
|
|
|
|
def test_that_correlation_filter_does_not_overwrite_correlation_id(self):
|
|
|
|
some_value = str(uuid.uuid4())
|
|
|
|
setattr(self.record, 'correlation-id', some_value)
|
|
|
|
self.filter.filter(self.record)
|
|
|
|
self.assertEqual(getattr(self.record, 'correlation-id'), some_value)
|
|
|
|
|
|
|
|
|
|
|
|
class LoggingConfigurationTests(unittest.TestCase):
|
|
|
|
def test_that_debug_sets_log_level_to_debug(self):
|
|
|
|
config = sprockets.http._get_logging_config(True)
|
|
|
|
self.assertEqual(config['root']['level'], 'DEBUG')
|
|
|
|
|
|
|
|
def test_that_not_debug_sets_log_level_to_info(self):
|
|
|
|
config = sprockets.http._get_logging_config(False)
|
|
|
|
self.assertEqual(config['root']['level'], 'INFO')
|
|
|
|
|
|
|
|
def test_that_format_includes_sd_when_service_and_env_are_set(self):
|
|
|
|
with override_environment_variable(SERVICE='service',
|
|
|
|
ENVIRONMENT='whatever'):
|
|
|
|
config = sprockets.http._get_logging_config(False)
|
|
|
|
fmt_name = list(config['formatters'].keys())[0]
|
|
|
|
self.assertIn('service="service" environment="whatever"',
|
|
|
|
config['formatters'][fmt_name]['format'])
|
2019-09-03 11:04:21 +00:00
|
|
|
|
|
|
|
|
|
|
|
class ShutdownHandlerTests(unittest.TestCase):
|
|
|
|
def setUp(self):
|
|
|
|
super(ShutdownHandlerTests, self).setUp()
|
|
|
|
self.io_loop = ioloop.IOLoop.current()
|
|
|
|
|
|
|
|
def test_that_on_future_complete_logs_exceptions_from_future(self):
|
|
|
|
future = concurrent.Future()
|
|
|
|
future.set_exception(Exception('Injected Failure'))
|
|
|
|
handler = sprockets.http.app._ShutdownHandler(self.io_loop, 0.2, 0.05)
|
|
|
|
with self.assertLogs(handler.logger, 'WARNING') as cm:
|
|
|
|
handler.on_shutdown_future_complete(future)
|
|
|
|
self.assertEqual(len(cm.output), 1)
|
|
|
|
self.assertIn('Injected Failure', cm.output[0])
|
|
|
|
|
|
|
|
def test_that_on_future_complete_logs_active_exceptions(self):
|
|
|
|
future = concurrent.Future()
|
|
|
|
future.set_exception(Exception('Injected Failure'))
|
|
|
|
handler = sprockets.http.app._ShutdownHandler(self.io_loop, 0.2, 0.05)
|
|
|
|
with self.assertLogs(handler.logger, 'WARNING') as cm:
|
|
|
|
try:
|
|
|
|
future.result()
|
|
|
|
except Exception:
|
|
|
|
handler.on_shutdown_future_complete(future)
|
|
|
|
self.assertEqual(len(cm.output), 1)
|
|
|
|
self.assertIn('Injected Failure', cm.output[0])
|
|
|
|
|
|
|
|
def test_that_maybe_stop_retries_until_tasks_are_complete(self):
|
|
|
|
fake_loop = unittest.mock.Mock()
|
|
|
|
fake_loop.time.return_value = 10
|
|
|
|
|
2020-07-18 12:03:10 +00:00
|
|
|
wait_timeout = 1.0
|
|
|
|
handler = sprockets.http.app._ShutdownHandler(
|
|
|
|
fake_loop, 5.0, wait_timeout)
|
2019-09-03 11:04:21 +00:00
|
|
|
|
2020-07-18 12:03:10 +00:00
|
|
|
handler._all_tasks = unittest.mock.Mock()
|
|
|
|
handler._all_tasks.return_value = ['does-not-matter']
|
|
|
|
|
|
|
|
# on_shutdown_ready should schedule the callback since there
|
|
|
|
# are outstanding tasks
|
|
|
|
handler.on_shutdown_ready()
|
|
|
|
fake_loop.add_timeout.assert_called_once_with(
|
|
|
|
fake_loop.time.return_value + wait_timeout,
|
|
|
|
handler._maybe_stop)
|
2019-09-03 11:04:21 +00:00
|
|
|
fake_loop.add_timeout.reset_mock()
|
|
|
|
|
2020-07-18 12:03:10 +00:00
|
|
|
# the callback should re-schedule since there are still
|
|
|
|
# outstanding tasks
|
|
|
|
handler._maybe_stop()
|
|
|
|
fake_loop.add_timeout.assert_called_once_with(
|
|
|
|
fake_loop.time.return_value + wait_timeout,
|
|
|
|
handler._maybe_stop)
|
|
|
|
fake_loop.add_timeout.reset_mock()
|
2019-09-03 11:04:21 +00:00
|
|
|
|
2020-07-18 12:03:10 +00:00
|
|
|
# when all of the tasks are finished, the loop is stopped
|
|
|
|
handler._all_tasks.return_value = []
|
2019-09-03 11:04:21 +00:00
|
|
|
handler._maybe_stop()
|
|
|
|
fake_loop.stop.assert_called_once_with()
|
|
|
|
|
|
|
|
def test_that_maybe_stop_terminates_when_deadline_reached(self):
|
|
|
|
fake_loop = unittest.mock.Mock()
|
|
|
|
|
2020-07-18 12:03:10 +00:00
|
|
|
shutdown_limit = 10
|
|
|
|
ticks = range(0, shutdown_limit)
|
|
|
|
handler = sprockets.http.app._ShutdownHandler(
|
|
|
|
fake_loop, shutdown_limit, 1.0)
|
2019-09-03 11:04:21 +00:00
|
|
|
|
2020-07-18 12:03:10 +00:00
|
|
|
handler._all_tasks = unittest.mock.Mock()
|
|
|
|
handler._all_tasks.return_value = ['does-not-matter']
|
2019-09-03 11:04:21 +00:00
|
|
|
|
2020-07-18 12:03:10 +00:00
|
|
|
fake_loop.time.return_value = 0.0
|
|
|
|
handler.on_shutdown_ready() # sets deadline to 0 + shutdown_limit
|
|
|
|
for time_value in ticks: # tick down
|
|
|
|
fake_loop.time.return_value = float(time_value)
|
2019-09-03 11:04:21 +00:00
|
|
|
handler._maybe_stop()
|
2020-07-18 12:03:10 +00:00
|
|
|
fake_loop.stop.assert_not_called()
|
2019-09-03 11:04:21 +00:00
|
|
|
|
2020-07-18 12:03:10 +00:00
|
|
|
fake_loop.time.return_value = float(shutdown_limit)
|
2019-09-03 11:04:21 +00:00
|
|
|
handler._maybe_stop()
|
|
|
|
fake_loop.stop.assert_called_once_with()
|
2022-02-02 23:33:48 +00:00
|
|
|
|
|
|
|
|
|
|
|
class AccessLogTests(sprockets.http.testing.SprocketsHttpTestCase):
|
|
|
|
|
|
|
|
def get_app(self):
|
|
|
|
self.app = sprockets.http.app.Application([])
|
|
|
|
return self.app
|
|
|
|
|
|
|
|
def test_that_log_request_uses_expected_format(self):
|
|
|
|
request = httputil.HTTPServerRequest('GET', '/search?q=42')
|
|
|
|
request.remote_ip = '1.1.1.1'
|
|
|
|
request._start_time = time.time()
|
|
|
|
request.connection = unittest.mock.Mock()
|
|
|
|
|
|
|
|
handler = web.RequestHandler(self.app, request)
|
|
|
|
|
|
|
|
with self.assertLogs(log.access_log) as context:
|
|
|
|
self.app.log_request(handler)
|
|
|
|
|
|
|
|
when = datetime.datetime.fromtimestamp(request._start_time,
|
|
|
|
datetime.timezone.utc)
|
|
|
|
expected_message = re.compile(
|
2022-03-15 22:41:24 +00:00
|
|
|
r'^%s - - %s "%s %s %s" %d "%s" - "-" "-" \(secs:([^)]*)\)' %
|
2022-02-02 23:33:48 +00:00
|
|
|
(request.remote_ip,
|
|
|
|
re.escape(
|
|
|
|
when.strftime('[%d/%b/%Y:%H:%M:%S %z]')), request.method,
|
2022-03-15 22:41:24 +00:00
|
|
|
re.escape(request.uri), request.version, handler.get_status(),
|
|
|
|
handler._reason))
|
2022-02-02 23:33:48 +00:00
|
|
|
message = context.records[0].getMessage()
|
|
|
|
match = expected_message.match(message)
|
|
|
|
if match is None:
|
|
|
|
self.fail(f'"{message}" did not match "{expected_message}"')
|
|
|
|
try:
|
|
|
|
float(match.group(1))
|
|
|
|
except ValueError:
|
|
|
|
self.fail(f'Expected {match.group(1)} to be a float')
|
|
|
|
|
|
|
|
def test_that_log_request_uses_correct_log_level(self):
|
|
|
|
expectations = {
|
|
|
|
200: logging.INFO,
|
|
|
|
303: logging.INFO,
|
|
|
|
400: logging.WARNING,
|
|
|
|
404: logging.WARNING,
|
|
|
|
500: logging.ERROR,
|
|
|
|
599: logging.ERROR,
|
|
|
|
}
|
|
|
|
|
|
|
|
request = httputil.HTTPServerRequest('GET', '/search?q=42')
|
|
|
|
request.remote_ip = '1.1.1.1'
|
|
|
|
request._start_time = time.time()
|
|
|
|
request.connection = unittest.mock.Mock()
|
|
|
|
|
|
|
|
for status, log_level in expectations.items():
|
|
|
|
handler = web.RequestHandler(self.app, request)
|
|
|
|
handler.set_status(status)
|
|
|
|
with self.assertLogs(log.access_log, log_level) as context:
|
|
|
|
self.app.log_request(handler)
|
|
|
|
self.assertEqual(context.records[0].levelno, log_level)
|
|
|
|
|
|
|
|
def test_that_log_request_uses_correct_log_level_with_only_failures(self):
|
|
|
|
expectations = {
|
|
|
|
200: logging.DEBUG,
|
|
|
|
303: logging.DEBUG,
|
|
|
|
400: logging.WARNING,
|
|
|
|
404: logging.WARNING,
|
|
|
|
500: logging.ERROR,
|
|
|
|
599: logging.ERROR,
|
|
|
|
}
|
|
|
|
|
|
|
|
request = httputil.HTTPServerRequest('GET', '/search?q=42')
|
|
|
|
request.remote_ip = '1.1.1.1'
|
|
|
|
request._start_time = time.time()
|
|
|
|
request.connection = unittest.mock.Mock()
|
|
|
|
|
|
|
|
for status, log_level in expectations.items():
|
|
|
|
handler = web.RequestHandler(self.app, request)
|
|
|
|
handler.access_log_failures_only = True
|
|
|
|
handler.set_status(status)
|
|
|
|
with self.assertLogs(log.access_log, log_level) as context:
|
|
|
|
self.app.log_request(handler)
|
|
|
|
self.assertEqual(context.records[0].levelno, log_level)
|
2022-02-05 00:10:08 +00:00
|
|
|
|
|
|
|
|
|
|
|
class ServerHeaderTests(sprockets.http.testing.SprocketsHttpTestCase):
|
|
|
|
|
|
|
|
def get_app(self):
|
|
|
|
self.app = sprockets.http.app.Application(
|
|
|
|
server_header='a/b/c')
|
|
|
|
return self.app
|
|
|
|
|
|
|
|
def test_reads_from_settings(self):
|
|
|
|
self.app.settings['server_header'] = 'a/b/c'
|
|
|
|
response = self.fetch('/')
|
|
|
|
self.assertEqual('a/b/c', response.headers['Server'])
|
|
|
|
|
|
|
|
self.app.settings['server_header'] = 'some server'
|
|
|
|
response = self.fetch('/')
|
|
|
|
self.assertEqual('some server', response.headers['Server'])
|
|
|
|
|
|
|
|
self.app.settings['server_header'] = None
|
|
|
|
response = self.fetch('/')
|
|
|
|
self.assertNotIn('Server', response.headers)
|
|
|
|
|
|
|
|
self.app.settings.pop('server_header')
|
|
|
|
response = self.fetch('/')
|
|
|
|
self.assertNotIn('Server', response.headers)
|
|
|
|
|
|
|
|
def test_defaults(self):
|
|
|
|
app = sprockets.http.app.Application()
|
|
|
|
self.assertIsNone(app.settings['server_header'])
|
|
|
|
|
|
|
|
app = sprockets.http.app.Application(service='myservice',
|
|
|
|
version='myversion')
|
|
|
|
self.assertEqual('myservice/myversion', app.settings['server_header'])
|
|
|
|
|
|
|
|
app = sprockets.http.app.Application(service='myservice',
|
|
|
|
version=None)
|
|
|
|
self.assertEqual('myservice', app.settings['server_header'])
|
|
|
|
|
|
|
|
app = sprockets.http.app.Application(service=None,
|
|
|
|
version='myversion')
|
|
|
|
self.assertIsNone(app.settings['server_header'])
|