mirror of
https://github.com/python/cpython.git
synced 2025-08-03 16:39:00 +00:00
asyncio: New error handling API. Issue #20681.
This commit is contained in:
parent
065efc3072
commit
ff827f08ac
15 changed files with 491 additions and 99 deletions
|
@ -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,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue