Commit 35ca2ddd authored by Yury Selivanov's avatar Yury Selivanov

asyncio: Increase asyncio.Future test coverage; test both implementations.

Also, add 'isfuture' to 'asyncio.futures.__all__', so that it's
exposed as 'asyncio.isfuture'.
parent af21ba6c
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
__all__ = ['CancelledError', 'TimeoutError', __all__ = ['CancelledError', 'TimeoutError',
'InvalidStateError', 'InvalidStateError',
'Future', 'wrap_future', 'Future', 'wrap_future', 'isfuture'
] ]
import concurrent.futures._base import concurrent.futures._base
...@@ -389,6 +389,10 @@ class Future: ...@@ -389,6 +389,10 @@ class Future:
__await__ = __iter__ # make compatible with 'await' expression __await__ = __iter__ # make compatible with 'await' expression
# Needed for testing purposes.
_PyFuture = Future
def _set_result_unless_cancelled(fut, result): def _set_result_unless_cancelled(fut, result):
"""Helper setting the result only if the future was not cancelled.""" """Helper setting the result only if the future was not cancelled."""
if fut.cancelled(): if fut.cancelled():
...@@ -488,4 +492,5 @@ try: ...@@ -488,4 +492,5 @@ try:
except ImportError: except ImportError:
pass pass
else: else:
Future = _asyncio.Future # _CFuture is needed for tests.
Future = _CFuture = _asyncio.Future
...@@ -9,6 +9,7 @@ from unittest import mock ...@@ -9,6 +9,7 @@ from unittest import mock
import asyncio import asyncio
from asyncio import test_utils from asyncio import test_utils
from asyncio import futures
try: try:
from test import support from test import support
except ImportError: except ImportError:
...@@ -93,14 +94,17 @@ class DuckTests(test_utils.TestCase): ...@@ -93,14 +94,17 @@ class DuckTests(test_utils.TestCase):
assert g is f assert g is f
class FutureTests(test_utils.TestCase): class BaseFutureTests:
def _new_future(self, loop=None):
raise NotImplementedError
def setUp(self): def setUp(self):
self.loop = self.new_test_loop() self.loop = self.new_test_loop()
self.addCleanup(self.loop.close) self.addCleanup(self.loop.close)
def test_initial_state(self): def test_initial_state(self):
f = asyncio.Future(loop=self.loop) f = self._new_future(loop=self.loop)
self.assertFalse(f.cancelled()) self.assertFalse(f.cancelled())
self.assertFalse(f.done()) self.assertFalse(f.done())
f.cancel() f.cancel()
...@@ -108,15 +112,15 @@ class FutureTests(test_utils.TestCase): ...@@ -108,15 +112,15 @@ class FutureTests(test_utils.TestCase):
def test_init_constructor_default_loop(self): def test_init_constructor_default_loop(self):
asyncio.set_event_loop(self.loop) asyncio.set_event_loop(self.loop)
f = asyncio.Future() f = self._new_future()
self.assertIs(f._loop, self.loop) self.assertIs(f._loop, self.loop)
def test_constructor_positional(self): def test_constructor_positional(self):
# Make sure Future doesn't accept a positional argument # Make sure Future doesn't accept a positional argument
self.assertRaises(TypeError, asyncio.Future, 42) self.assertRaises(TypeError, self._new_future, 42)
def test_cancel(self): def test_cancel(self):
f = asyncio.Future(loop=self.loop) f = self._new_future(loop=self.loop)
self.assertTrue(f.cancel()) self.assertTrue(f.cancel())
self.assertTrue(f.cancelled()) self.assertTrue(f.cancelled())
self.assertTrue(f.done()) self.assertTrue(f.done())
...@@ -127,7 +131,7 @@ class FutureTests(test_utils.TestCase): ...@@ -127,7 +131,7 @@ class FutureTests(test_utils.TestCase):
self.assertFalse(f.cancel()) self.assertFalse(f.cancel())
def test_result(self): def test_result(self):
f = asyncio.Future(loop=self.loop) f = self._new_future(loop=self.loop)
self.assertRaises(asyncio.InvalidStateError, f.result) self.assertRaises(asyncio.InvalidStateError, f.result)
f.set_result(42) f.set_result(42)
...@@ -141,7 +145,7 @@ class FutureTests(test_utils.TestCase): ...@@ -141,7 +145,7 @@ class FutureTests(test_utils.TestCase):
def test_exception(self): def test_exception(self):
exc = RuntimeError() exc = RuntimeError()
f = asyncio.Future(loop=self.loop) f = self._new_future(loop=self.loop)
self.assertRaises(asyncio.InvalidStateError, f.exception) self.assertRaises(asyncio.InvalidStateError, f.exception)
# StopIteration cannot be raised into a Future - CPython issue26221 # StopIteration cannot be raised into a Future - CPython issue26221
...@@ -158,12 +162,12 @@ class FutureTests(test_utils.TestCase): ...@@ -158,12 +162,12 @@ class FutureTests(test_utils.TestCase):
self.assertFalse(f.cancel()) self.assertFalse(f.cancel())
def test_exception_class(self): def test_exception_class(self):
f = asyncio.Future(loop=self.loop) f = self._new_future(loop=self.loop)
f.set_exception(RuntimeError) f.set_exception(RuntimeError)
self.assertIsInstance(f.exception(), RuntimeError) self.assertIsInstance(f.exception(), RuntimeError)
def test_yield_from_twice(self): def test_yield_from_twice(self):
f = asyncio.Future(loop=self.loop) f = self._new_future(loop=self.loop)
def fixture(): def fixture():
yield 'A' yield 'A'
...@@ -182,7 +186,7 @@ class FutureTests(test_utils.TestCase): ...@@ -182,7 +186,7 @@ class FutureTests(test_utils.TestCase):
def test_future_repr(self): def test_future_repr(self):
self.loop.set_debug(True) self.loop.set_debug(True)
f_pending_debug = asyncio.Future(loop=self.loop) f_pending_debug = self._new_future(loop=self.loop)
frame = f_pending_debug._source_traceback[-1] frame = f_pending_debug._source_traceback[-1]
self.assertEqual(repr(f_pending_debug), self.assertEqual(repr(f_pending_debug),
'<Future pending created at %s:%s>' '<Future pending created at %s:%s>'
...@@ -190,21 +194,21 @@ class FutureTests(test_utils.TestCase): ...@@ -190,21 +194,21 @@ class FutureTests(test_utils.TestCase):
f_pending_debug.cancel() f_pending_debug.cancel()
self.loop.set_debug(False) self.loop.set_debug(False)
f_pending = asyncio.Future(loop=self.loop) f_pending = self._new_future(loop=self.loop)
self.assertEqual(repr(f_pending), '<Future pending>') self.assertEqual(repr(f_pending), '<Future pending>')
f_pending.cancel() f_pending.cancel()
f_cancelled = asyncio.Future(loop=self.loop) f_cancelled = self._new_future(loop=self.loop)
f_cancelled.cancel() f_cancelled.cancel()
self.assertEqual(repr(f_cancelled), '<Future cancelled>') self.assertEqual(repr(f_cancelled), '<Future cancelled>')
f_result = asyncio.Future(loop=self.loop) f_result = self._new_future(loop=self.loop)
f_result.set_result(4) f_result.set_result(4)
self.assertEqual(repr(f_result), '<Future finished result=4>') self.assertEqual(repr(f_result), '<Future finished result=4>')
self.assertEqual(f_result.result(), 4) self.assertEqual(f_result.result(), 4)
exc = RuntimeError() exc = RuntimeError()
f_exception = asyncio.Future(loop=self.loop) f_exception = self._new_future(loop=self.loop)
f_exception.set_exception(exc) f_exception.set_exception(exc)
self.assertEqual(repr(f_exception), self.assertEqual(repr(f_exception),
'<Future finished exception=RuntimeError()>') '<Future finished exception=RuntimeError()>')
...@@ -215,7 +219,7 @@ class FutureTests(test_utils.TestCase): ...@@ -215,7 +219,7 @@ class FutureTests(test_utils.TestCase):
text = '%s() at %s:%s' % (func.__qualname__, filename, lineno) text = '%s() at %s:%s' % (func.__qualname__, filename, lineno)
return re.escape(text) return re.escape(text)
f_one_callbacks = asyncio.Future(loop=self.loop) f_one_callbacks = self._new_future(loop=self.loop)
f_one_callbacks.add_done_callback(_fakefunc) f_one_callbacks.add_done_callback(_fakefunc)
fake_repr = func_repr(_fakefunc) fake_repr = func_repr(_fakefunc)
self.assertRegex(repr(f_one_callbacks), self.assertRegex(repr(f_one_callbacks),
...@@ -224,7 +228,7 @@ class FutureTests(test_utils.TestCase): ...@@ -224,7 +228,7 @@ class FutureTests(test_utils.TestCase):
self.assertEqual(repr(f_one_callbacks), self.assertEqual(repr(f_one_callbacks),
'<Future cancelled>') '<Future cancelled>')
f_two_callbacks = asyncio.Future(loop=self.loop) f_two_callbacks = self._new_future(loop=self.loop)
f_two_callbacks.add_done_callback(first_cb) f_two_callbacks.add_done_callback(first_cb)
f_two_callbacks.add_done_callback(last_cb) f_two_callbacks.add_done_callback(last_cb)
first_repr = func_repr(first_cb) first_repr = func_repr(first_cb)
...@@ -233,7 +237,7 @@ class FutureTests(test_utils.TestCase): ...@@ -233,7 +237,7 @@ class FutureTests(test_utils.TestCase):
r'<Future pending cb=\[%s, %s\]>' r'<Future pending cb=\[%s, %s\]>'
% (first_repr, last_repr)) % (first_repr, last_repr))
f_many_callbacks = asyncio.Future(loop=self.loop) f_many_callbacks = self._new_future(loop=self.loop)
f_many_callbacks.add_done_callback(first_cb) f_many_callbacks.add_done_callback(first_cb)
for i in range(8): for i in range(8):
f_many_callbacks.add_done_callback(_fakefunc) f_many_callbacks.add_done_callback(_fakefunc)
...@@ -248,31 +252,31 @@ class FutureTests(test_utils.TestCase): ...@@ -248,31 +252,31 @@ class FutureTests(test_utils.TestCase):
def test_copy_state(self): def test_copy_state(self):
from asyncio.futures import _copy_future_state from asyncio.futures import _copy_future_state
f = asyncio.Future(loop=self.loop) f = self._new_future(loop=self.loop)
f.set_result(10) f.set_result(10)
newf = asyncio.Future(loop=self.loop) newf = self._new_future(loop=self.loop)
_copy_future_state(f, newf) _copy_future_state(f, newf)
self.assertTrue(newf.done()) self.assertTrue(newf.done())
self.assertEqual(newf.result(), 10) self.assertEqual(newf.result(), 10)
f_exception = asyncio.Future(loop=self.loop) f_exception = self._new_future(loop=self.loop)
f_exception.set_exception(RuntimeError()) f_exception.set_exception(RuntimeError())
newf_exception = asyncio.Future(loop=self.loop) newf_exception = self._new_future(loop=self.loop)
_copy_future_state(f_exception, newf_exception) _copy_future_state(f_exception, newf_exception)
self.assertTrue(newf_exception.done()) self.assertTrue(newf_exception.done())
self.assertRaises(RuntimeError, newf_exception.result) self.assertRaises(RuntimeError, newf_exception.result)
f_cancelled = asyncio.Future(loop=self.loop) f_cancelled = self._new_future(loop=self.loop)
f_cancelled.cancel() f_cancelled.cancel()
newf_cancelled = asyncio.Future(loop=self.loop) newf_cancelled = self._new_future(loop=self.loop)
_copy_future_state(f_cancelled, newf_cancelled) _copy_future_state(f_cancelled, newf_cancelled)
self.assertTrue(newf_cancelled.cancelled()) self.assertTrue(newf_cancelled.cancelled())
def test_iter(self): def test_iter(self):
fut = asyncio.Future(loop=self.loop) fut = self._new_future(loop=self.loop)
def coro(): def coro():
yield from fut yield from fut
...@@ -285,20 +289,20 @@ class FutureTests(test_utils.TestCase): ...@@ -285,20 +289,20 @@ class FutureTests(test_utils.TestCase):
@mock.patch('asyncio.base_events.logger') @mock.patch('asyncio.base_events.logger')
def test_tb_logger_abandoned(self, m_log): def test_tb_logger_abandoned(self, m_log):
fut = asyncio.Future(loop=self.loop) fut = self._new_future(loop=self.loop)
del fut del fut
self.assertFalse(m_log.error.called) self.assertFalse(m_log.error.called)
@mock.patch('asyncio.base_events.logger') @mock.patch('asyncio.base_events.logger')
def test_tb_logger_result_unretrieved(self, m_log): def test_tb_logger_result_unretrieved(self, m_log):
fut = asyncio.Future(loop=self.loop) fut = self._new_future(loop=self.loop)
fut.set_result(42) fut.set_result(42)
del fut del fut
self.assertFalse(m_log.error.called) self.assertFalse(m_log.error.called)
@mock.patch('asyncio.base_events.logger') @mock.patch('asyncio.base_events.logger')
def test_tb_logger_result_retrieved(self, m_log): def test_tb_logger_result_retrieved(self, m_log):
fut = asyncio.Future(loop=self.loop) fut = self._new_future(loop=self.loop)
fut.set_result(42) fut.set_result(42)
fut.result() fut.result()
del fut del fut
...@@ -306,7 +310,7 @@ class FutureTests(test_utils.TestCase): ...@@ -306,7 +310,7 @@ class FutureTests(test_utils.TestCase):
@mock.patch('asyncio.base_events.logger') @mock.patch('asyncio.base_events.logger')
def test_tb_logger_exception_unretrieved(self, m_log): def test_tb_logger_exception_unretrieved(self, m_log):
fut = asyncio.Future(loop=self.loop) fut = self._new_future(loop=self.loop)
fut.set_exception(RuntimeError('boom')) fut.set_exception(RuntimeError('boom'))
del fut del fut
test_utils.run_briefly(self.loop) test_utils.run_briefly(self.loop)
...@@ -315,7 +319,7 @@ class FutureTests(test_utils.TestCase): ...@@ -315,7 +319,7 @@ class FutureTests(test_utils.TestCase):
@mock.patch('asyncio.base_events.logger') @mock.patch('asyncio.base_events.logger')
def test_tb_logger_exception_retrieved(self, m_log): def test_tb_logger_exception_retrieved(self, m_log):
fut = asyncio.Future(loop=self.loop) fut = self._new_future(loop=self.loop)
fut.set_exception(RuntimeError('boom')) fut.set_exception(RuntimeError('boom'))
fut.exception() fut.exception()
del fut del fut
...@@ -323,7 +327,7 @@ class FutureTests(test_utils.TestCase): ...@@ -323,7 +327,7 @@ class FutureTests(test_utils.TestCase):
@mock.patch('asyncio.base_events.logger') @mock.patch('asyncio.base_events.logger')
def test_tb_logger_exception_result_retrieved(self, m_log): def test_tb_logger_exception_result_retrieved(self, m_log):
fut = asyncio.Future(loop=self.loop) fut = self._new_future(loop=self.loop)
fut.set_exception(RuntimeError('boom')) fut.set_exception(RuntimeError('boom'))
self.assertRaises(RuntimeError, fut.result) self.assertRaises(RuntimeError, fut.result)
del fut del fut
...@@ -337,12 +341,12 @@ class FutureTests(test_utils.TestCase): ...@@ -337,12 +341,12 @@ class FutureTests(test_utils.TestCase):
f1 = ex.submit(run, 'oi') f1 = ex.submit(run, 'oi')
f2 = asyncio.wrap_future(f1, loop=self.loop) f2 = asyncio.wrap_future(f1, loop=self.loop)
res, ident = self.loop.run_until_complete(f2) res, ident = self.loop.run_until_complete(f2)
self.assertIsInstance(f2, asyncio.Future) self.assertTrue(asyncio.isfuture(f2))
self.assertEqual(res, 'oi') self.assertEqual(res, 'oi')
self.assertNotEqual(ident, threading.get_ident()) self.assertNotEqual(ident, threading.get_ident())
def test_wrap_future_future(self): def test_wrap_future_future(self):
f1 = asyncio.Future(loop=self.loop) f1 = self._new_future(loop=self.loop)
f2 = asyncio.wrap_future(f1) f2 = asyncio.wrap_future(f1)
self.assertIs(f1, f2) self.assertIs(f1, f2)
...@@ -377,10 +381,10 @@ class FutureTests(test_utils.TestCase): ...@@ -377,10 +381,10 @@ class FutureTests(test_utils.TestCase):
def test_future_source_traceback(self): def test_future_source_traceback(self):
self.loop.set_debug(True) self.loop.set_debug(True)
future = asyncio.Future(loop=self.loop) future = self._new_future(loop=self.loop)
lineno = sys._getframe().f_lineno - 1 lineno = sys._getframe().f_lineno - 1
self.assertIsInstance(future._source_traceback, list) self.assertIsInstance(future._source_traceback, list)
self.assertEqual(future._source_traceback[-1][:3], self.assertEqual(future._source_traceback[-2][:3],
(__file__, (__file__,
lineno, lineno,
'test_future_source_traceback')) 'test_future_source_traceback'))
...@@ -396,57 +400,18 @@ class FutureTests(test_utils.TestCase): ...@@ -396,57 +400,18 @@ class FutureTests(test_utils.TestCase):
return exc return exc
exc = memory_error() exc = memory_error()
future = asyncio.Future(loop=self.loop) future = self._new_future(loop=self.loop)
if debug:
source_traceback = future._source_traceback
future.set_exception(exc) future.set_exception(exc)
future = None future = None
test_utils.run_briefly(self.loop) test_utils.run_briefly(self.loop)
support.gc_collect() support.gc_collect()
if sys.version_info >= (3, 4): if sys.version_info >= (3, 4):
if debug: regex = r'^Future exception was never retrieved\n'
frame = source_traceback[-1]
regex = (r'^Future exception was never retrieved\n'
r'future: <Future finished exception=MemoryError\(\) '
r'created at {filename}:{lineno}>\n'
r'source_traceback: Object '
r'created at \(most recent call last\):\n'
r' File'
r'.*\n'
r' File "{filename}", line {lineno}, '
r'in check_future_exception_never_retrieved\n'
r' future = asyncio\.Future\(loop=self\.loop\)$'
).format(filename=re.escape(frame[0]),
lineno=frame[1])
else:
regex = (r'^Future exception was never retrieved\n'
r'future: '
r'<Future finished exception=MemoryError\(\)>$'
)
exc_info = (type(exc), exc, exc.__traceback__) exc_info = (type(exc), exc, exc.__traceback__)
m_log.error.assert_called_once_with(mock.ANY, exc_info=exc_info) m_log.error.assert_called_once_with(mock.ANY, exc_info=exc_info)
else: else:
if debug: regex = r'^Future/Task exception was never retrieved\n'
frame = source_traceback[-1]
regex = (r'^Future/Task exception was never retrieved\n'
r'Future/Task created at \(most recent call last\):\n'
r' File'
r'.*\n'
r' File "{filename}", line {lineno}, '
r'in check_future_exception_never_retrieved\n'
r' future = asyncio\.Future\(loop=self\.loop\)\n'
r'Traceback \(most recent call last\):\n'
r'.*\n'
r'MemoryError$'
).format(filename=re.escape(frame[0]),
lineno=frame[1])
else:
regex = (r'^Future/Task exception was never retrieved\n'
r'Traceback \(most recent call last\):\n'
r'.*\n'
r'MemoryError$'
)
m_log.error.assert_called_once_with(mock.ANY, exc_info=False) m_log.error.assert_called_once_with(mock.ANY, exc_info=False)
message = m_log.error.call_args[0][0] message = m_log.error.call_args[0][0]
self.assertRegex(message, re.compile(regex, re.DOTALL)) self.assertRegex(message, re.compile(regex, re.DOTALL))
...@@ -458,14 +423,13 @@ class FutureTests(test_utils.TestCase): ...@@ -458,14 +423,13 @@ class FutureTests(test_utils.TestCase):
self.check_future_exception_never_retrieved(True) self.check_future_exception_never_retrieved(True)
def test_set_result_unless_cancelled(self): def test_set_result_unless_cancelled(self):
from asyncio import futures fut = self._new_future(loop=self.loop)
fut = asyncio.Future(loop=self.loop)
fut.cancel() fut.cancel()
futures._set_result_unless_cancelled(fut, 2) futures._set_result_unless_cancelled(fut, 2)
self.assertTrue(fut.cancelled()) self.assertTrue(fut.cancelled())
def test_future_stop_iteration_args(self): def test_future_stop_iteration_args(self):
fut = asyncio.Future(loop=self.loop) fut = self._new_future(loop=self.loop)
fut.set_result((1, 2)) fut.set_result((1, 2))
fi = fut.__iter__() fi = fut.__iter__()
result = None result = None
...@@ -478,7 +442,21 @@ class FutureTests(test_utils.TestCase): ...@@ -478,7 +442,21 @@ class FutureTests(test_utils.TestCase):
self.assertEqual(result, (1, 2)) self.assertEqual(result, (1, 2))
class FutureDoneCallbackTests(test_utils.TestCase): @unittest.skipUnless(hasattr(futures, '_CFuture'),
'requires the C _asyncio module')
class CFutureTests(BaseFutureTests, test_utils.TestCase):
def _new_future(self, *args, **kwargs):
return futures._CFuture(*args, **kwargs)
class PyFutureTests(BaseFutureTests, test_utils.TestCase):
def _new_future(self, *args, **kwargs):
return futures._PyFuture(*args, **kwargs)
class BaseFutureDoneCallbackTests():
def setUp(self): def setUp(self):
self.loop = self.new_test_loop() self.loop = self.new_test_loop()
...@@ -493,7 +471,7 @@ class FutureDoneCallbackTests(test_utils.TestCase): ...@@ -493,7 +471,7 @@ class FutureDoneCallbackTests(test_utils.TestCase):
return bag_appender return bag_appender
def _new_future(self): def _new_future(self):
return asyncio.Future(loop=self.loop) raise NotImplementedError
def test_callbacks_invoked_on_set_result(self): def test_callbacks_invoked_on_set_result(self):
bag = [] bag = []
...@@ -557,5 +535,21 @@ class FutureDoneCallbackTests(test_utils.TestCase): ...@@ -557,5 +535,21 @@ class FutureDoneCallbackTests(test_utils.TestCase):
self.assertEqual(f.result(), 'foo') self.assertEqual(f.result(), 'foo')
@unittest.skipUnless(hasattr(futures, '_CFuture'),
'requires the C _asyncio module')
class CFutureDoneCallbackTests(BaseFutureDoneCallbackTests,
test_utils.TestCase):
def _new_future(self):
return futures._CFuture(loop=self.loop)
class PyFutureDoneCallbackTests(BaseFutureDoneCallbackTests,
test_utils.TestCase):
def _new_future(self):
return futures._PyFuture(loop=self.loop)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment