asyncio: New error handling API. Issue #20681.

This commit is contained in:
Yury Selivanov 2014-02-18 18:02:19 -05:00
parent 065efc3072
commit ff827f08ac
15 changed files with 491 additions and 99 deletions

View file

@ -15,6 +15,10 @@ from asyncio import constants
from asyncio import test_utils
MOCK_ANY = unittest.mock.ANY
PY34 = sys.version_info >= (3, 4)
class BaseEventLoopTests(unittest.TestCase):
def setUp(self):
@ -49,20 +53,21 @@ class BaseEventLoopTests(unittest.TestCase):
self.assertRaises(NotImplementedError, next, iter(gen))
def test__add_callback_handle(self):
h = asyncio.Handle(lambda: False, ())
h = asyncio.Handle(lambda: False, (), self.loop)
self.loop._add_callback(h)
self.assertFalse(self.loop._scheduled)
self.assertIn(h, self.loop._ready)
def test__add_callback_timer(self):
h = asyncio.TimerHandle(time.monotonic()+10, lambda: False, ())
h = asyncio.TimerHandle(time.monotonic()+10, lambda: False, (),
self.loop)
self.loop._add_callback(h)
self.assertIn(h, self.loop._scheduled)
def test__add_callback_cancelled_handle(self):
h = asyncio.Handle(lambda: False, ())
h = asyncio.Handle(lambda: False, (), self.loop)
h.cancel()
self.loop._add_callback(h)
@ -137,15 +142,15 @@ class BaseEventLoopTests(unittest.TestCase):
self.assertRaises(
AssertionError, self.loop.run_in_executor,
None, asyncio.Handle(cb, ()), ('',))
None, asyncio.Handle(cb, (), self.loop), ('',))
self.assertRaises(
AssertionError, self.loop.run_in_executor,
None, asyncio.TimerHandle(10, cb, ()))
None, asyncio.TimerHandle(10, cb, (), self.loop))
def test_run_once_in_executor_cancelled(self):
def cb():
pass
h = asyncio.Handle(cb, ())
h = asyncio.Handle(cb, (), self.loop)
h.cancel()
f = self.loop.run_in_executor(None, h)
@ -156,7 +161,7 @@ class BaseEventLoopTests(unittest.TestCase):
def test_run_once_in_executor_plain(self):
def cb():
pass
h = asyncio.Handle(cb, ())
h = asyncio.Handle(cb, (), self.loop)
f = asyncio.Future(loop=self.loop)
executor = unittest.mock.Mock()
executor.submit.return_value = f
@ -175,8 +180,10 @@ class BaseEventLoopTests(unittest.TestCase):
f.cancel() # Don't complain about abandoned Future.
def test__run_once(self):
h1 = asyncio.TimerHandle(time.monotonic() + 5.0, lambda: True, ())
h2 = asyncio.TimerHandle(time.monotonic() + 10.0, lambda: True, ())
h1 = asyncio.TimerHandle(time.monotonic() + 5.0, lambda: True, (),
self.loop)
h2 = asyncio.TimerHandle(time.monotonic() + 10.0, lambda: True, (),
self.loop)
h1.cancel()
@ -205,14 +212,15 @@ class BaseEventLoopTests(unittest.TestCase):
m_time.monotonic = monotonic
self.loop._scheduled.append(
asyncio.TimerHandle(11.0, lambda: True, ()))
asyncio.TimerHandle(11.0, lambda: True, (), self.loop))
self.loop._process_events = unittest.mock.Mock()
self.loop._run_once()
self.assertEqual(logging.INFO, m_logger.log.call_args[0][0])
idx = -1
data = [10.0, 10.0, 10.3, 13.0]
self.loop._scheduled = [asyncio.TimerHandle(11.0, lambda: True, ())]
self.loop._scheduled = [asyncio.TimerHandle(11.0, lambda: True, (),
self.loop)]
self.loop._run_once()
self.assertEqual(logging.DEBUG, m_logger.log.call_args[0][0])
@ -225,7 +233,8 @@ class BaseEventLoopTests(unittest.TestCase):
processed = True
handle = loop.call_soon(lambda: True)
h = asyncio.TimerHandle(time.monotonic() - 1, cb, (self.loop,))
h = asyncio.TimerHandle(time.monotonic() - 1, cb, (self.loop,),
self.loop)
self.loop._process_events = unittest.mock.Mock()
self.loop._scheduled.append(h)
@ -287,6 +296,163 @@ class BaseEventLoopTests(unittest.TestCase):
self.loop.run_until_complete, self.loop.subprocess_shell,
asyncio.SubprocessProtocol, 'exit 0', bufsize=4096)
def test_default_exc_handler_callback(self):
self.loop._process_events = unittest.mock.Mock()
def zero_error(fut):
fut.set_result(True)
1/0
# Test call_soon (events.Handle)
with unittest.mock.patch('asyncio.base_events.logger') as log:
fut = asyncio.Future(loop=self.loop)
self.loop.call_soon(zero_error, fut)
fut.add_done_callback(lambda fut: self.loop.stop())
self.loop.run_forever()
log.error.assert_called_with(
test_utils.MockPattern('Exception in callback.*zero'),
exc_info=(ZeroDivisionError, MOCK_ANY, MOCK_ANY))
# Test call_later (events.TimerHandle)
with unittest.mock.patch('asyncio.base_events.logger') as log:
fut = asyncio.Future(loop=self.loop)
self.loop.call_later(0.01, zero_error, fut)
fut.add_done_callback(lambda fut: self.loop.stop())
self.loop.run_forever()
log.error.assert_called_with(
test_utils.MockPattern('Exception in callback.*zero'),
exc_info=(ZeroDivisionError, MOCK_ANY, MOCK_ANY))
def test_default_exc_handler_coro(self):
self.loop._process_events = unittest.mock.Mock()
@asyncio.coroutine
def zero_error_coro():
yield from asyncio.sleep(0.01, loop=self.loop)
1/0
# Test Future.__del__
with unittest.mock.patch('asyncio.base_events.logger') as log:
fut = asyncio.async(zero_error_coro(), loop=self.loop)
fut.add_done_callback(lambda *args: self.loop.stop())
self.loop.run_forever()
fut = None # Trigger Future.__del__ or futures._TracebackLogger
if PY34:
# Future.__del__ in Python 3.4 logs error with
# an actual exception context
log.error.assert_called_with(
test_utils.MockPattern('.*exception was never retrieved'),
exc_info=(ZeroDivisionError, MOCK_ANY, MOCK_ANY))
else:
# futures._TracebackLogger logs only textual traceback
log.error.assert_called_with(
test_utils.MockPattern(
'.*exception was never retrieved.*ZeroDiv'),
exc_info=False)
def test_set_exc_handler_invalid(self):
with self.assertRaisesRegex(TypeError, 'A callable object or None'):
self.loop.set_exception_handler('spam')
def test_set_exc_handler_custom(self):
def zero_error():
1/0
def run_loop():
self.loop.call_soon(zero_error)
self.loop._run_once()
self.loop._process_events = unittest.mock.Mock()
mock_handler = unittest.mock.Mock()
self.loop.set_exception_handler(mock_handler)
run_loop()
mock_handler.assert_called_with(self.loop, {
'exception': MOCK_ANY,
'message': test_utils.MockPattern(
'Exception in callback.*zero_error'),
'handle': MOCK_ANY,
})
mock_handler.reset_mock()
self.loop.set_exception_handler(None)
with unittest.mock.patch('asyncio.base_events.logger') as log:
run_loop()
log.error.assert_called_with(
test_utils.MockPattern(
'Exception in callback.*zero'),
exc_info=(ZeroDivisionError, MOCK_ANY, MOCK_ANY))
assert not mock_handler.called
def test_set_exc_handler_broken(self):
def run_loop():
def zero_error():
1/0
self.loop.call_soon(zero_error)
self.loop._run_once()
def handler(loop, context):
raise AttributeError('spam')
self.loop._process_events = unittest.mock.Mock()
self.loop.set_exception_handler(handler)
with unittest.mock.patch('asyncio.base_events.logger') as log:
run_loop()
log.error.assert_called_with(
test_utils.MockPattern(
'Unhandled error in exception handler'),
exc_info=(AttributeError, MOCK_ANY, MOCK_ANY))
def test_default_exc_handler_broken(self):
_context = None
class Loop(base_events.BaseEventLoop):
_selector = unittest.mock.Mock()
_process_events = unittest.mock.Mock()
def default_exception_handler(self, context):
nonlocal _context
_context = context
# Simulates custom buggy "default_exception_handler"
raise ValueError('spam')
loop = Loop()
asyncio.set_event_loop(loop)
def run_loop():
def zero_error():
1/0
loop.call_soon(zero_error)
loop._run_once()
with unittest.mock.patch('asyncio.base_events.logger') as log:
run_loop()
log.error.assert_called_with(
'Exception in default exception handler',
exc_info=True)
def custom_handler(loop, context):
raise ValueError('ham')
_context = None
loop.set_exception_handler(custom_handler)
with unittest.mock.patch('asyncio.base_events.logger') as log:
run_loop()
log.error.assert_called_with(
test_utils.MockPattern('Exception in default exception.*'
'while handling.*in custom'),
exc_info=True)
# Check that original context was passed to default
# exception handler.
self.assertIn('context', _context)
self.assertIs(type(_context['context']['exception']),
ZeroDivisionError)
class MyProto(asyncio.Protocol):
done = None
@ -716,7 +882,7 @@ class BaseEventLoopWithSelectorTests(unittest.TestCase):
self.loop._accept_connection(MyProto, sock)
self.assertFalse(sock.close.called)
@unittest.mock.patch('asyncio.selector_events.logger')
@unittest.mock.patch('asyncio.base_events.logger')
def test_accept_connection_exception(self, m_log):
sock = unittest.mock.Mock()
sock.fileno.return_value = 10
@ -725,7 +891,7 @@ class BaseEventLoopWithSelectorTests(unittest.TestCase):
self.loop.call_later = unittest.mock.Mock()
self.loop._accept_connection(MyProto, sock)
self.assertTrue(m_log.exception.called)
self.assertTrue(m_log.error.called)
self.assertFalse(sock.close.called)
self.loop.remove_reader.assert_called_with(10)
self.loop.call_later.assert_called_with(constants.ACCEPT_RETRY_DELAY,