Issue #28544: Implement asyncio.Task in C.
This implementation provides additional 10-20% speed boost for
asyncio programs.
The patch also fixes _asynciomodule.c to use Arguments Clinic, and
makes '_schedule_callbacks' an overridable method (as it was in 3.5).
diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py
index 5880061..cc9994d 100644
--- a/Lib/asyncio/base_events.py
+++ b/Lib/asyncio/base_events.py
@@ -57,7 +57,7 @@
def _format_handle(handle):
cb = handle._callback
- if inspect.ismethod(cb) and isinstance(cb.__self__, tasks.Task):
+ if isinstance(getattr(cb, '__self__', None), tasks.Task):
# format the task
return repr(cb.__self__)
else:
diff --git a/Lib/asyncio/base_futures.py b/Lib/asyncio/base_futures.py
new file mode 100644
index 0000000..64f7845
--- /dev/null
+++ b/Lib/asyncio/base_futures.py
@@ -0,0 +1,70 @@
+__all__ = []
+
+import concurrent.futures._base
+import reprlib
+
+from . import events
+
+Error = concurrent.futures._base.Error
+CancelledError = concurrent.futures.CancelledError
+TimeoutError = concurrent.futures.TimeoutError
+
+
+class InvalidStateError(Error):
+ """The operation is not allowed in this state."""
+
+
+# States for Future.
+_PENDING = 'PENDING'
+_CANCELLED = 'CANCELLED'
+_FINISHED = 'FINISHED'
+
+
+def isfuture(obj):
+ """Check for a Future.
+
+ This returns True when obj is a Future instance or is advertising
+ itself as duck-type compatible by setting _asyncio_future_blocking.
+ See comment in Future for more details.
+ """
+ return getattr(obj, '_asyncio_future_blocking', None) is not None
+
+
+def _format_callbacks(cb):
+ """helper function for Future.__repr__"""
+ size = len(cb)
+ if not size:
+ cb = ''
+
+ def format_cb(callback):
+ return events._format_callback_source(callback, ())
+
+ if size == 1:
+ cb = format_cb(cb[0])
+ elif size == 2:
+ cb = '{}, {}'.format(format_cb(cb[0]), format_cb(cb[1]))
+ elif size > 2:
+ cb = '{}, <{} more>, {}'.format(format_cb(cb[0]),
+ size - 2,
+ format_cb(cb[-1]))
+ return 'cb=[%s]' % cb
+
+
+def _future_repr_info(future):
+ # (Future) -> str
+ """helper function for Future.__repr__"""
+ info = [future._state.lower()]
+ if future._state == _FINISHED:
+ if future._exception is not None:
+ info.append('exception={!r}'.format(future._exception))
+ else:
+ # use reprlib to limit the length of the output, especially
+ # for very long strings
+ result = reprlib.repr(future._result)
+ info.append('result={}'.format(result))
+ if future._callbacks:
+ info.append(_format_callbacks(future._callbacks))
+ if future._source_traceback:
+ frame = future._source_traceback[-1]
+ info.append('created at %s:%s' % (frame[0], frame[1]))
+ return info
diff --git a/Lib/asyncio/base_tasks.py b/Lib/asyncio/base_tasks.py
new file mode 100644
index 0000000..5f34434
--- /dev/null
+++ b/Lib/asyncio/base_tasks.py
@@ -0,0 +1,76 @@
+import linecache
+import traceback
+
+from . import base_futures
+from . import coroutines
+
+
+def _task_repr_info(task):
+ info = base_futures._future_repr_info(task)
+
+ if task._must_cancel:
+ # replace status
+ info[0] = 'cancelling'
+
+ coro = coroutines._format_coroutine(task._coro)
+ info.insert(1, 'coro=<%s>' % coro)
+
+ if task._fut_waiter is not None:
+ info.insert(2, 'wait_for=%r' % task._fut_waiter)
+ return info
+
+
+def _task_get_stack(task, limit):
+ frames = []
+ try:
+ # 'async def' coroutines
+ f = task._coro.cr_frame
+ except AttributeError:
+ f = task._coro.gi_frame
+ if f is not None:
+ while f is not None:
+ if limit is not None:
+ if limit <= 0:
+ break
+ limit -= 1
+ frames.append(f)
+ f = f.f_back
+ frames.reverse()
+ elif task._exception is not None:
+ tb = task._exception.__traceback__
+ while tb is not None:
+ if limit is not None:
+ if limit <= 0:
+ break
+ limit -= 1
+ frames.append(tb.tb_frame)
+ tb = tb.tb_next
+ return frames
+
+
+def _task_print_stack(task, limit, file):
+ extracted_list = []
+ checked = set()
+ for f in task.get_stack(limit=limit):
+ lineno = f.f_lineno
+ co = f.f_code
+ filename = co.co_filename
+ name = co.co_name
+ if filename not in checked:
+ checked.add(filename)
+ linecache.checkcache(filename)
+ line = linecache.getline(filename, lineno, f.f_globals)
+ extracted_list.append((filename, lineno, name, line))
+ exc = task._exception
+ if not extracted_list:
+ print('No stack for %r' % task, file=file)
+ elif exc is not None:
+ print('Traceback for %r (most recent call last):' % task,
+ file=file)
+ else:
+ print('Stack for %r (most recent call last):' % task,
+ file=file)
+ traceback.print_list(extracted_list, file=file)
+ if exc is not None:
+ for line in traceback.format_exception_only(exc.__class__, exc):
+ print(line, file=file, end='')
diff --git a/Lib/asyncio/coroutines.py b/Lib/asyncio/coroutines.py
index 1db7030..167c1e4 100644
--- a/Lib/asyncio/coroutines.py
+++ b/Lib/asyncio/coroutines.py
@@ -11,7 +11,7 @@
from . import compat
from . import events
-from . import futures
+from . import base_futures
from .log import logger
@@ -204,7 +204,7 @@
@functools.wraps(func)
def coro(*args, **kw):
res = func(*args, **kw)
- if (futures.isfuture(res) or inspect.isgenerator(res) or
+ if (base_futures.isfuture(res) or inspect.isgenerator(res) or
isinstance(res, CoroWrapper)):
res = yield from res
elif _AwaitableABC is not None:
diff --git a/Lib/asyncio/futures.py b/Lib/asyncio/futures.py
index b571130..d11d289 100644
--- a/Lib/asyncio/futures.py
+++ b/Lib/asyncio/futures.py
@@ -1,35 +1,32 @@
"""A Future class similar to the one in PEP 3148."""
-__all__ = ['CancelledError', 'TimeoutError',
- 'InvalidStateError',
- 'Future', 'wrap_future', 'isfuture'
- ]
+__all__ = ['CancelledError', 'TimeoutError', 'InvalidStateError',
+ 'Future', 'wrap_future', 'isfuture']
-import concurrent.futures._base
+import concurrent.futures
import logging
-import reprlib
import sys
import traceback
+from . import base_futures
from . import compat
from . import events
-# States for Future.
-_PENDING = 'PENDING'
-_CANCELLED = 'CANCELLED'
-_FINISHED = 'FINISHED'
-Error = concurrent.futures._base.Error
-CancelledError = concurrent.futures.CancelledError
-TimeoutError = concurrent.futures.TimeoutError
+CancelledError = base_futures.CancelledError
+InvalidStateError = base_futures.InvalidStateError
+TimeoutError = base_futures.TimeoutError
+isfuture = base_futures.isfuture
+
+
+_PENDING = base_futures._PENDING
+_CANCELLED = base_futures._CANCELLED
+_FINISHED = base_futures._FINISHED
+
STACK_DEBUG = logging.DEBUG - 1 # heavy-duty debugging
-class InvalidStateError(Error):
- """The operation is not allowed in this state."""
-
-
class _TracebackLogger:
"""Helper to log a traceback upon destruction if not cleared.
@@ -110,56 +107,6 @@
self.loop.call_exception_handler({'message': msg})
-def isfuture(obj):
- """Check for a Future.
-
- This returns True when obj is a Future instance or is advertising
- itself as duck-type compatible by setting _asyncio_future_blocking.
- See comment in Future for more details.
- """
- return getattr(obj, '_asyncio_future_blocking', None) is not None
-
-
-def _format_callbacks(cb):
- """helper function for Future.__repr__"""
- size = len(cb)
- if not size:
- cb = ''
-
- def format_cb(callback):
- return events._format_callback_source(callback, ())
-
- if size == 1:
- cb = format_cb(cb[0])
- elif size == 2:
- cb = '{}, {}'.format(format_cb(cb[0]), format_cb(cb[1]))
- elif size > 2:
- cb = '{}, <{} more>, {}'.format(format_cb(cb[0]),
- size-2,
- format_cb(cb[-1]))
- return 'cb=[%s]' % cb
-
-
-def _future_repr_info(future):
- # (Future) -> str
- """helper function for Future.__repr__"""
- info = [future._state.lower()]
- if future._state == _FINISHED:
- if future._exception is not None:
- info.append('exception={!r}'.format(future._exception))
- else:
- # use reprlib to limit the length of the output, especially
- # for very long strings
- result = reprlib.repr(future._result)
- info.append('result={}'.format(result))
- if future._callbacks:
- info.append(_format_callbacks(future._callbacks))
- if future._source_traceback:
- frame = future._source_traceback[-1]
- info.append('created at %s:%s' % (frame[0], frame[1]))
- return info
-
-
class Future:
"""This class is *almost* compatible with concurrent.futures.Future.
@@ -212,7 +159,7 @@
if self._loop.get_debug():
self._source_traceback = traceback.extract_stack(sys._getframe(1))
- _repr_info = _future_repr_info
+ _repr_info = base_futures._future_repr_info
def __repr__(self):
return '<%s %s>' % (self.__class__.__name__, ' '.join(self._repr_info()))
@@ -247,10 +194,10 @@
if self._state != _PENDING:
return False
self._state = _CANCELLED
- self.__schedule_callbacks()
+ self._schedule_callbacks()
return True
- def __schedule_callbacks(self):
+ def _schedule_callbacks(self):
"""Internal: Ask the event loop to call all callbacks.
The callbacks are scheduled to be called as soon as possible. Also
@@ -352,7 +299,7 @@
raise InvalidStateError('{}: {!r}'.format(self._state, self))
self._result = result
self._state = _FINISHED
- self.__schedule_callbacks()
+ self._schedule_callbacks()
def set_exception(self, exception):
"""Mark the future done and set an exception.
@@ -369,7 +316,7 @@
"and cannot be raised into a Future")
self._exception = exception
self._state = _FINISHED
- self.__schedule_callbacks()
+ self._schedule_callbacks()
if compat.PY34:
self._log_traceback = True
else:
diff --git a/Lib/asyncio/tasks.py b/Lib/asyncio/tasks.py
index 8852aa5..5a43ef2 100644
--- a/Lib/asyncio/tasks.py
+++ b/Lib/asyncio/tasks.py
@@ -9,11 +9,10 @@
import concurrent.futures
import functools
import inspect
-import linecache
-import traceback
import warnings
import weakref
+from . import base_tasks
from . import compat
from . import coroutines
from . import events
@@ -93,18 +92,7 @@
futures.Future.__del__(self)
def _repr_info(self):
- info = super()._repr_info()
-
- if self._must_cancel:
- # replace status
- info[0] = 'cancelling'
-
- coro = coroutines._format_coroutine(self._coro)
- info.insert(1, 'coro=<%s>' % coro)
-
- if self._fut_waiter is not None:
- info.insert(2, 'wait_for=%r' % self._fut_waiter)
- return info
+ return base_tasks._task_repr_info(self)
def get_stack(self, *, limit=None):
"""Return the list of stack frames for this task's coroutine.
@@ -127,31 +115,7 @@
For reasons beyond our control, only one stack frame is
returned for a suspended coroutine.
"""
- frames = []
- try:
- # 'async def' coroutines
- f = self._coro.cr_frame
- except AttributeError:
- f = self._coro.gi_frame
- if f is not None:
- while f is not None:
- if limit is not None:
- if limit <= 0:
- break
- limit -= 1
- frames.append(f)
- f = f.f_back
- frames.reverse()
- elif self._exception is not None:
- tb = self._exception.__traceback__
- while tb is not None:
- if limit is not None:
- if limit <= 0:
- break
- limit -= 1
- frames.append(tb.tb_frame)
- tb = tb.tb_next
- return frames
+ return base_tasks._task_get_stack(self, limit)
def print_stack(self, *, limit=None, file=None):
"""Print the stack or traceback for this task's coroutine.
@@ -162,31 +126,7 @@
to which the output is written; by default output is written
to sys.stderr.
"""
- extracted_list = []
- checked = set()
- for f in self.get_stack(limit=limit):
- lineno = f.f_lineno
- co = f.f_code
- filename = co.co_filename
- name = co.co_name
- if filename not in checked:
- checked.add(filename)
- linecache.checkcache(filename)
- line = linecache.getline(filename, lineno, f.f_globals)
- extracted_list.append((filename, lineno, name, line))
- exc = self._exception
- if not extracted_list:
- print('No stack for %r' % self, file=file)
- elif exc is not None:
- print('Traceback for %r (most recent call last):' % self,
- file=file)
- else:
- print('Stack for %r (most recent call last):' % self,
- file=file)
- traceback.print_list(extracted_list, file=file)
- if exc is not None:
- for line in traceback.format_exception_only(exc.__class__, exc):
- print(line, file=file, end='')
+ return base_tasks._task_print_stack(self, limit, file)
def cancel(self):
"""Request that this task cancel itself.
@@ -316,6 +256,18 @@
self = None # Needed to break cycles when an exception occurs.
+_PyTask = Task
+
+
+try:
+ import _asyncio
+except ImportError:
+ pass
+else:
+ # _CTask is needed for tests.
+ Task = _CTask = _asyncio.Task
+
+
# wait() and as_completed() similar to those in PEP 3148.
FIRST_COMPLETED = concurrent.futures.FIRST_COMPLETED
diff --git a/Lib/test/test_asyncio/test_tasks.py b/Lib/test/test_asyncio/test_tasks.py
index 1ceb9b2..d8862fc 100644
--- a/Lib/test/test_asyncio/test_tasks.py
+++ b/Lib/test/test_asyncio/test_tasks.py
@@ -1,5 +1,6 @@
"""Tests for tasks.py."""
+import collections
import contextlib
import functools
import io
@@ -14,6 +15,8 @@
import asyncio
from asyncio import coroutines
+from asyncio import futures
+from asyncio import tasks
from asyncio import test_utils
try:
from test import support
@@ -72,14 +75,25 @@
pass
-class TaskTests(test_utils.TestCase):
+class BaseTaskTests:
+
+ Task = None
+ Future = None
+
+ def new_task(self, loop, coro):
+ return self.__class__.Task(coro, loop=loop)
+
+ def new_future(self, loop):
+ return self.__class__.Future(loop=loop)
def setUp(self):
self.loop = self.new_test_loop()
+ self.loop.set_task_factory(self.new_task)
+ self.loop.create_future = lambda: self.new_future(self.loop)
def test_other_loop_future(self):
other_loop = asyncio.new_event_loop()
- fut = asyncio.Future(loop=other_loop)
+ fut = self.new_future(other_loop)
@asyncio.coroutine
def run(fut):
@@ -107,7 +121,7 @@
@asyncio.coroutine
def notmuch():
return 'ok'
- t = asyncio.Task(notmuch(), loop=self.loop)
+ t = self.new_task(self.loop, notmuch())
self.loop.run_until_complete(t)
self.assertTrue(t.done())
self.assertEqual(t.result(), 'ok')
@@ -115,7 +129,7 @@
loop = asyncio.new_event_loop()
self.set_event_loop(loop)
- t = asyncio.Task(notmuch(), loop=loop)
+ t = self.new_task(loop, notmuch())
self.assertIs(t._loop, loop)
loop.run_until_complete(t)
loop.close()
@@ -138,7 +152,7 @@
loop.close()
def test_ensure_future_future(self):
- f_orig = asyncio.Future(loop=self.loop)
+ f_orig = self.new_future(self.loop)
f_orig.set_result('ko')
f = asyncio.ensure_future(f_orig)
@@ -162,7 +176,7 @@
@asyncio.coroutine
def notmuch():
return 'ok'
- t_orig = asyncio.Task(notmuch(), loop=self.loop)
+ t_orig = self.new_task(self.loop, notmuch())
t = asyncio.ensure_future(t_orig)
self.loop.run_until_complete(t)
self.assertTrue(t.done())
@@ -203,7 +217,7 @@
asyncio.ensure_future('ok')
def test_async_warning(self):
- f = asyncio.Future(loop=self.loop)
+ f = self.new_future(self.loop)
with self.assertWarnsRegex(DeprecationWarning,
'function is deprecated, use ensure_'):
self.assertIs(f, asyncio.async(f))
@@ -250,8 +264,8 @@
# test coroutine function
self.assertEqual(notmuch.__name__, 'notmuch')
if PY35:
- self.assertEqual(notmuch.__qualname__,
- 'TaskTests.test_task_repr.<locals>.notmuch')
+ self.assertRegex(notmuch.__qualname__,
+ r'\w+.test_task_repr.<locals>.notmuch')
self.assertEqual(notmuch.__module__, __name__)
filename, lineno = test_utils.get_function_source(notmuch)
@@ -260,7 +274,7 @@
# test coroutine object
gen = notmuch()
if coroutines._DEBUG or PY35:
- coro_qualname = 'TaskTests.test_task_repr.<locals>.notmuch'
+ coro_qualname = 'BaseTaskTests.test_task_repr.<locals>.notmuch'
else:
coro_qualname = 'notmuch'
self.assertEqual(gen.__name__, 'notmuch')
@@ -269,7 +283,7 @@
coro_qualname)
# test pending Task
- t = asyncio.Task(gen, loop=self.loop)
+ t = self.new_task(self.loop, gen)
t.add_done_callback(Dummy())
coro = format_coroutine(coro_qualname, 'running', src,
@@ -291,7 +305,7 @@
'<Task cancelled %s>' % coro)
# test finished Task
- t = asyncio.Task(notmuch(), loop=self.loop)
+ t = self.new_task(self.loop, notmuch())
self.loop.run_until_complete(t)
coro = format_coroutine(coro_qualname, 'done', src,
t._source_traceback)
@@ -310,9 +324,9 @@
# test coroutine function
self.assertEqual(notmuch.__name__, 'notmuch')
if PY35:
- self.assertEqual(notmuch.__qualname__,
- 'TaskTests.test_task_repr_coro_decorator'
- '.<locals>.notmuch')
+ self.assertRegex(notmuch.__qualname__,
+ r'\w+.test_task_repr_coro_decorator'
+ r'\.<locals>\.notmuch')
self.assertEqual(notmuch.__module__, __name__)
# test coroutine object
@@ -322,7 +336,7 @@
# function, as expected, and have a qualified name (__qualname__
# attribute).
coro_name = 'notmuch'
- coro_qualname = ('TaskTests.test_task_repr_coro_decorator'
+ coro_qualname = ('BaseTaskTests.test_task_repr_coro_decorator'
'.<locals>.notmuch')
else:
# On Python < 3.5, generators inherit the name of the code, not of
@@ -350,7 +364,7 @@
self.assertEqual(repr(gen), '<CoroWrapper %s>' % coro)
# test pending Task
- t = asyncio.Task(gen, loop=self.loop)
+ t = self.new_task(self.loop, gen)
t.add_done_callback(Dummy())
# format the coroutine object
@@ -373,8 +387,8 @@
def wait_for(fut):
return (yield from fut)
- fut = asyncio.Future(loop=self.loop)
- task = asyncio.Task(wait_for(fut), loop=self.loop)
+ fut = self.new_future(self.loop)
+ task = self.new_task(self.loop, wait_for(fut))
test_utils.run_briefly(self.loop)
self.assertRegex(repr(task),
'<Task .* wait_for=%s>' % re.escape(repr(fut)))
@@ -400,10 +414,11 @@
self.addCleanup(task._coro.close)
coro_repr = repr(task._coro)
- expected = ('<CoroWrapper TaskTests.test_task_repr_partial_corowrapper'
- '.<locals>.func(1)() running, ')
- self.assertTrue(coro_repr.startswith(expected),
- coro_repr)
+ expected = (
+ r'<CoroWrapper \w+.test_task_repr_partial_corowrapper'
+ r'\.<locals>\.func\(1\)\(\) running, '
+ )
+ self.assertRegex(coro_repr, expected)
def test_task_basics(self):
@asyncio.coroutine
@@ -437,7 +452,7 @@
yield from asyncio.sleep(10.0, loop=loop)
return 12
- t = asyncio.Task(task(), loop=loop)
+ t = self.new_task(loop, task())
loop.call_soon(t.cancel)
with self.assertRaises(asyncio.CancelledError):
loop.run_until_complete(t)
@@ -452,7 +467,7 @@
yield
return 12
- t = asyncio.Task(task(), loop=self.loop)
+ t = self.new_task(self.loop, task())
test_utils.run_briefly(self.loop) # start coro
t.cancel()
self.assertRaises(
@@ -462,14 +477,14 @@
self.assertFalse(t.cancel())
def test_cancel_inner_future(self):
- f = asyncio.Future(loop=self.loop)
+ f = self.new_future(self.loop)
@asyncio.coroutine
def task():
yield from f
return 12
- t = asyncio.Task(task(), loop=self.loop)
+ t = self.new_task(self.loop, task())
test_utils.run_briefly(self.loop) # start task
f.cancel()
with self.assertRaises(asyncio.CancelledError):
@@ -478,14 +493,14 @@
self.assertTrue(t.cancelled())
def test_cancel_both_task_and_inner_future(self):
- f = asyncio.Future(loop=self.loop)
+ f = self.new_future(self.loop)
@asyncio.coroutine
def task():
yield from f
return 12
- t = asyncio.Task(task(), loop=self.loop)
+ t = self.new_task(self.loop, task())
test_utils.run_briefly(self.loop)
f.cancel()
@@ -499,8 +514,8 @@
self.assertTrue(t.cancelled())
def test_cancel_task_catching(self):
- fut1 = asyncio.Future(loop=self.loop)
- fut2 = asyncio.Future(loop=self.loop)
+ fut1 = self.new_future(self.loop)
+ fut2 = self.new_future(self.loop)
@asyncio.coroutine
def task():
@@ -510,7 +525,7 @@
except asyncio.CancelledError:
return 42
- t = asyncio.Task(task(), loop=self.loop)
+ t = self.new_task(self.loop, task())
test_utils.run_briefly(self.loop)
self.assertIs(t._fut_waiter, fut1) # White-box test.
fut1.set_result(None)
@@ -523,9 +538,9 @@
self.assertFalse(t.cancelled())
def test_cancel_task_ignoring(self):
- fut1 = asyncio.Future(loop=self.loop)
- fut2 = asyncio.Future(loop=self.loop)
- fut3 = asyncio.Future(loop=self.loop)
+ fut1 = self.new_future(self.loop)
+ fut2 = self.new_future(self.loop)
+ fut3 = self.new_future(self.loop)
@asyncio.coroutine
def task():
@@ -537,7 +552,7 @@
res = yield from fut3
return res
- t = asyncio.Task(task(), loop=self.loop)
+ t = self.new_task(self.loop, task())
test_utils.run_briefly(self.loop)
self.assertIs(t._fut_waiter, fut1) # White-box test.
fut1.set_result(None)
@@ -565,7 +580,7 @@
yield from asyncio.sleep(100, loop=loop)
return 12
- t = asyncio.Task(task(), loop=loop)
+ t = self.new_task(loop, task())
self.assertRaises(
asyncio.CancelledError, loop.run_until_complete, t)
self.assertTrue(t.done())
@@ -598,7 +613,7 @@
if x == 2:
loop.stop()
- t = asyncio.Task(task(), loop=loop)
+ t = self.new_task(loop, task())
with self.assertRaises(RuntimeError) as cm:
loop.run_until_complete(t)
self.assertEqual(str(cm.exception),
@@ -636,7 +651,7 @@
foo_running = False
return 'done'
- fut = asyncio.Task(foo(), loop=loop)
+ fut = self.new_task(loop, foo())
with self.assertRaises(asyncio.TimeoutError):
loop.run_until_complete(asyncio.wait_for(fut, 0.1, loop=loop))
@@ -676,7 +691,7 @@
asyncio.set_event_loop(loop)
try:
- fut = asyncio.Task(foo(), loop=loop)
+ fut = self.new_task(loop, foo())
with self.assertRaises(asyncio.TimeoutError):
loop.run_until_complete(asyncio.wait_for(fut, 0.01))
finally:
@@ -695,7 +710,7 @@
loop = self.new_test_loop(gen)
- fut = asyncio.Future(loop=loop)
+ fut = self.new_future(loop)
task = asyncio.wait_for(fut, timeout=0.2, loop=loop)
loop.call_later(0.1, fut.set_result, "ok")
res = loop.run_until_complete(task)
@@ -712,8 +727,8 @@
loop = self.new_test_loop(gen)
- a = asyncio.Task(asyncio.sleep(0.1, loop=loop), loop=loop)
- b = asyncio.Task(asyncio.sleep(0.15, loop=loop), loop=loop)
+ a = self.new_task(loop, asyncio.sleep(0.1, loop=loop))
+ b = self.new_task(loop, asyncio.sleep(0.15, loop=loop))
@asyncio.coroutine
def foo():
@@ -722,12 +737,12 @@
self.assertEqual(pending, set())
return 42
- res = loop.run_until_complete(asyncio.Task(foo(), loop=loop))
+ res = loop.run_until_complete(self.new_task(loop, foo()))
self.assertEqual(res, 42)
self.assertAlmostEqual(0.15, loop.time())
# Doing it again should take no time and exercise a different path.
- res = loop.run_until_complete(asyncio.Task(foo(), loop=loop))
+ res = loop.run_until_complete(self.new_task(loop, foo()))
self.assertAlmostEqual(0.15, loop.time())
self.assertEqual(res, 42)
@@ -742,8 +757,8 @@
loop = self.new_test_loop(gen)
- a = asyncio.Task(asyncio.sleep(0.01, loop=loop), loop=loop)
- b = asyncio.Task(asyncio.sleep(0.015, loop=loop), loop=loop)
+ a = self.new_task(loop, asyncio.sleep(0.01, loop=loop))
+ b = self.new_task(loop, asyncio.sleep(0.015, loop=loop))
@asyncio.coroutine
def foo():
@@ -754,7 +769,7 @@
asyncio.set_event_loop(loop)
res = loop.run_until_complete(
- asyncio.Task(foo(), loop=loop))
+ self.new_task(loop, foo()))
self.assertEqual(res, 42)
@@ -764,9 +779,9 @@
return s
c = coro('test')
- task = asyncio.Task(
- asyncio.wait([c, c, coro('spam')], loop=self.loop),
- loop=self.loop)
+ task =self.new_task(
+ self.loop,
+ asyncio.wait([c, c, coro('spam')], loop=self.loop))
done, pending = self.loop.run_until_complete(task)
@@ -797,12 +812,12 @@
loop = self.new_test_loop(gen)
- a = asyncio.Task(asyncio.sleep(10.0, loop=loop), loop=loop)
- b = asyncio.Task(asyncio.sleep(0.1, loop=loop), loop=loop)
- task = asyncio.Task(
+ a = self.new_task(loop, asyncio.sleep(10.0, loop=loop))
+ b = self.new_task(loop, asyncio.sleep(0.1, loop=loop))
+ task = self.new_task(
+ loop,
asyncio.wait([b, a], return_when=asyncio.FIRST_COMPLETED,
- loop=loop),
- loop=loop)
+ loop=loop))
done, pending = loop.run_until_complete(task)
self.assertEqual({b}, done)
@@ -829,12 +844,12 @@
yield
yield
- a = asyncio.Task(coro1(), loop=self.loop)
- b = asyncio.Task(coro2(), loop=self.loop)
- task = asyncio.Task(
+ a = self.new_task(self.loop, coro1())
+ b = self.new_task(self.loop, coro2())
+ task = self.new_task(
+ self.loop,
asyncio.wait([b, a], return_when=asyncio.FIRST_COMPLETED,
- loop=self.loop),
- loop=self.loop)
+ loop=self.loop))
done, pending = self.loop.run_until_complete(task)
self.assertEqual({a, b}, done)
@@ -853,17 +868,17 @@
loop = self.new_test_loop(gen)
# first_exception, task already has exception
- a = asyncio.Task(asyncio.sleep(10.0, loop=loop), loop=loop)
+ a = self.new_task(loop, asyncio.sleep(10.0, loop=loop))
@asyncio.coroutine
def exc():
raise ZeroDivisionError('err')
- b = asyncio.Task(exc(), loop=loop)
- task = asyncio.Task(
+ b = self.new_task(loop, exc())
+ task = self.new_task(
+ loop,
asyncio.wait([b, a], return_when=asyncio.FIRST_EXCEPTION,
- loop=loop),
- loop=loop)
+ loop=loop))
done, pending = loop.run_until_complete(task)
self.assertEqual({b}, done)
@@ -886,14 +901,14 @@
loop = self.new_test_loop(gen)
# first_exception, exception during waiting
- a = asyncio.Task(asyncio.sleep(10.0, loop=loop), loop=loop)
+ a = self.new_task(loop, asyncio.sleep(10.0, loop=loop))
@asyncio.coroutine
def exc():
yield from asyncio.sleep(0.01, loop=loop)
raise ZeroDivisionError('err')
- b = asyncio.Task(exc(), loop=loop)
+ b = self.new_task(loop, exc())
task = asyncio.wait([b, a], return_when=asyncio.FIRST_EXCEPTION,
loop=loop)
@@ -917,14 +932,14 @@
loop = self.new_test_loop(gen)
- a = asyncio.Task(asyncio.sleep(0.1, loop=loop), loop=loop)
+ a = self.new_task(loop, asyncio.sleep(0.1, loop=loop))
@asyncio.coroutine
def sleeper():
yield from asyncio.sleep(0.15, loop=loop)
raise ZeroDivisionError('really')
- b = asyncio.Task(sleeper(), loop=loop)
+ b = self.new_task(loop, sleeper())
@asyncio.coroutine
def foo():
@@ -934,10 +949,10 @@
errors = set(f for f in done if f.exception() is not None)
self.assertEqual(len(errors), 1)
- loop.run_until_complete(asyncio.Task(foo(), loop=loop))
+ loop.run_until_complete(self.new_task(loop, foo()))
self.assertAlmostEqual(0.15, loop.time())
- loop.run_until_complete(asyncio.Task(foo(), loop=loop))
+ loop.run_until_complete(self.new_task(loop, foo()))
self.assertAlmostEqual(0.15, loop.time())
def test_wait_with_timeout(self):
@@ -953,8 +968,8 @@
loop = self.new_test_loop(gen)
- a = asyncio.Task(asyncio.sleep(0.1, loop=loop), loop=loop)
- b = asyncio.Task(asyncio.sleep(0.15, loop=loop), loop=loop)
+ a = self.new_task(loop, asyncio.sleep(0.1, loop=loop))
+ b = self.new_task(loop, asyncio.sleep(0.15, loop=loop))
@asyncio.coroutine
def foo():
@@ -963,7 +978,7 @@
self.assertEqual(done, set([a]))
self.assertEqual(pending, set([b]))
- loop.run_until_complete(asyncio.Task(foo(), loop=loop))
+ loop.run_until_complete(self.new_task(loop, foo()))
self.assertAlmostEqual(0.11, loop.time())
# move forward to close generator
@@ -983,8 +998,8 @@
loop = self.new_test_loop(gen)
- a = asyncio.Task(asyncio.sleep(0.1, loop=loop), loop=loop)
- b = asyncio.Task(asyncio.sleep(0.15, loop=loop), loop=loop)
+ a = self.new_task(loop, asyncio.sleep(0.1, loop=loop))
+ b = self.new_task(loop, asyncio.sleep(0.15, loop=loop))
done, pending = loop.run_until_complete(
asyncio.wait([b, a], timeout=0.1, loop=loop))
@@ -1032,14 +1047,14 @@
values.append((yield from f))
return values
- res = loop.run_until_complete(asyncio.Task(foo(), loop=loop))
+ res = loop.run_until_complete(self.new_task(loop, foo()))
self.assertAlmostEqual(0.15, loop.time())
self.assertTrue('a' in res[:2])
self.assertTrue('b' in res[:2])
self.assertEqual(res[2], 'c')
# Doing it again should take no time and exercise a different path.
- res = loop.run_until_complete(asyncio.Task(foo(), loop=loop))
+ res = loop.run_until_complete(self.new_task(loop, foo()))
self.assertAlmostEqual(0.15, loop.time())
def test_as_completed_with_timeout(self):
@@ -1068,7 +1083,7 @@
values.append((2, exc))
return values
- res = loop.run_until_complete(asyncio.Task(foo(), loop=loop))
+ res = loop.run_until_complete(self.new_task(loop, foo()))
self.assertEqual(len(res), 2, res)
self.assertEqual(res[0], (1, 'a'))
self.assertEqual(res[1][0], 2)
@@ -1096,7 +1111,7 @@
v = yield from f
self.assertEqual(v, 'a')
- loop.run_until_complete(asyncio.Task(foo(), loop=loop))
+ loop.run_until_complete(self.new_task(loop, foo()))
def test_as_completed_reverse_wait(self):
@@ -1156,7 +1171,7 @@
result.append((yield from f))
return result
- fut = asyncio.Task(runner(), loop=self.loop)
+ fut = self.new_task(self.loop, runner())
self.loop.run_until_complete(fut)
result = fut.result()
self.assertEqual(set(result), {'ham', 'spam'})
@@ -1179,7 +1194,7 @@
res = yield from asyncio.sleep(dt/2, arg, loop=loop)
return res
- t = asyncio.Task(sleeper(0.1, 'yeah'), loop=loop)
+ t = self.new_task(loop, sleeper(0.1, 'yeah'))
loop.run_until_complete(t)
self.assertTrue(t.done())
self.assertEqual(t.result(), 'yeah')
@@ -1194,8 +1209,7 @@
loop = self.new_test_loop(gen)
- t = asyncio.Task(asyncio.sleep(10.0, 'yeah', loop=loop),
- loop=loop)
+ t = self.new_task(loop, asyncio.sleep(10.0, 'yeah', loop=loop))
handle = None
orig_call_later = loop.call_later
@@ -1231,7 +1245,7 @@
@asyncio.coroutine
def doit():
- sleeper = asyncio.Task(sleep(5000), loop=loop)
+ sleeper = self.new_task(loop, sleep(5000))
loop.call_later(0.1, sleeper.cancel)
try:
yield from sleeper
@@ -1245,13 +1259,13 @@
self.assertAlmostEqual(0.1, loop.time())
def test_task_cancel_waiter_future(self):
- fut = asyncio.Future(loop=self.loop)
+ fut = self.new_future(self.loop)
@asyncio.coroutine
def coro():
yield from fut
- task = asyncio.Task(coro(), loop=self.loop)
+ task = self.new_task(self.loop, coro())
test_utils.run_briefly(self.loop)
self.assertIs(task._fut_waiter, fut)
@@ -1268,7 +1282,7 @@
return 'ko'
gen = notmuch()
- task = asyncio.Task(gen, loop=self.loop)
+ task = self.new_task(self.loop, gen)
task.set_result('ok')
self.assertRaises(AssertionError, task._step)
@@ -1304,7 +1318,7 @@
nonlocal result
result = yield from fut
- t = asyncio.Task(wait_for_future(), loop=self.loop)
+ t = self.new_task(self.loop, wait_for_future())
test_utils.run_briefly(self.loop)
self.assertTrue(fut.cb_added)
@@ -1320,7 +1334,7 @@
def notmutch():
raise BaseException()
- task = asyncio.Task(notmutch(), loop=self.loop)
+ task = self.new_task(self.loop, notmutch())
self.assertRaises(BaseException, task._step)
self.assertTrue(task.done())
@@ -1348,7 +1362,7 @@
except asyncio.CancelledError:
raise base_exc
- task = asyncio.Task(notmutch(), loop=loop)
+ task = self.new_task(loop, notmutch())
test_utils.run_briefly(loop)
task.cancel()
@@ -1376,7 +1390,7 @@
self.assertTrue(asyncio.iscoroutinefunction(fn2))
def test_yield_vs_yield_from(self):
- fut = asyncio.Future(loop=self.loop)
+ fut = self.new_future(self.loop)
@asyncio.coroutine
def wait_for_future():
@@ -1420,7 +1434,7 @@
self.assertEqual(res, 'test')
def test_coroutine_non_gen_function_return_future(self):
- fut = asyncio.Future(loop=self.loop)
+ fut = self.new_future(self.loop)
@asyncio.coroutine
def func():
@@ -1430,49 +1444,53 @@
def coro():
fut.set_result('test')
- t1 = asyncio.Task(func(), loop=self.loop)
- t2 = asyncio.Task(coro(), loop=self.loop)
+ t1 = self.new_task(self.loop, func())
+ t2 = self.new_task(self.loop, coro())
res = self.loop.run_until_complete(t1)
self.assertEqual(res, 'test')
self.assertIsNone(t2.result())
def test_current_task(self):
- self.assertIsNone(asyncio.Task.current_task(loop=self.loop))
+ Task = self.__class__.Task
+
+ self.assertIsNone(Task.current_task(loop=self.loop))
@asyncio.coroutine
def coro(loop):
- self.assertTrue(asyncio.Task.current_task(loop=loop) is task)
+ self.assertTrue(Task.current_task(loop=loop) is task)
- task = asyncio.Task(coro(self.loop), loop=self.loop)
+ task = self.new_task(self.loop, coro(self.loop))
self.loop.run_until_complete(task)
- self.assertIsNone(asyncio.Task.current_task(loop=self.loop))
+ self.assertIsNone(Task.current_task(loop=self.loop))
def test_current_task_with_interleaving_tasks(self):
- self.assertIsNone(asyncio.Task.current_task(loop=self.loop))
+ Task = self.__class__.Task
- fut1 = asyncio.Future(loop=self.loop)
- fut2 = asyncio.Future(loop=self.loop)
+ self.assertIsNone(Task.current_task(loop=self.loop))
+
+ fut1 = self.new_future(self.loop)
+ fut2 = self.new_future(self.loop)
@asyncio.coroutine
def coro1(loop):
- self.assertTrue(asyncio.Task.current_task(loop=loop) is task1)
+ self.assertTrue(Task.current_task(loop=loop) is task1)
yield from fut1
- self.assertTrue(asyncio.Task.current_task(loop=loop) is task1)
+ self.assertTrue(Task.current_task(loop=loop) is task1)
fut2.set_result(True)
@asyncio.coroutine
def coro2(loop):
- self.assertTrue(asyncio.Task.current_task(loop=loop) is task2)
+ self.assertTrue(Task.current_task(loop=loop) is task2)
fut1.set_result(True)
yield from fut2
- self.assertTrue(asyncio.Task.current_task(loop=loop) is task2)
+ self.assertTrue(Task.current_task(loop=loop) is task2)
- task1 = asyncio.Task(coro1(self.loop), loop=self.loop)
- task2 = asyncio.Task(coro2(self.loop), loop=self.loop)
+ task1 = self.new_task(self.loop, coro1(self.loop))
+ task2 = self.new_task(self.loop, coro2(self.loop))
self.loop.run_until_complete(asyncio.wait((task1, task2),
loop=self.loop))
- self.assertIsNone(asyncio.Task.current_task(loop=self.loop))
+ self.assertIsNone(Task.current_task(loop=self.loop))
# Some thorough tests for cancellation propagation through
# coroutines, tasks and wait().
@@ -1480,7 +1498,7 @@
def test_yield_future_passes_cancel(self):
# Cancelling outer() cancels inner() cancels waiter.
proof = 0
- waiter = asyncio.Future(loop=self.loop)
+ waiter = self.new_future(self.loop)
@asyncio.coroutine
def inner():
@@ -1514,7 +1532,7 @@
# Cancelling outer() makes wait() return early, leaves inner()
# running.
proof = 0
- waiter = asyncio.Future(loop=self.loop)
+ waiter = self.new_future(self.loop)
@asyncio.coroutine
def inner():
@@ -1538,14 +1556,14 @@
self.assertEqual(proof, 1)
def test_shield_result(self):
- inner = asyncio.Future(loop=self.loop)
+ inner = self.new_future(self.loop)
outer = asyncio.shield(inner)
inner.set_result(42)
res = self.loop.run_until_complete(outer)
self.assertEqual(res, 42)
def test_shield_exception(self):
- inner = asyncio.Future(loop=self.loop)
+ inner = self.new_future(self.loop)
outer = asyncio.shield(inner)
test_utils.run_briefly(self.loop)
exc = RuntimeError('expected')
@@ -1554,7 +1572,7 @@
self.assertIs(outer.exception(), exc)
def test_shield_cancel(self):
- inner = asyncio.Future(loop=self.loop)
+ inner = self.new_future(self.loop)
outer = asyncio.shield(inner)
test_utils.run_briefly(self.loop)
inner.cancel()
@@ -1562,7 +1580,7 @@
self.assertTrue(outer.cancelled())
def test_shield_shortcut(self):
- fut = asyncio.Future(loop=self.loop)
+ fut = self.new_future(self.loop)
fut.set_result(42)
res = self.loop.run_until_complete(asyncio.shield(fut))
self.assertEqual(res, 42)
@@ -1570,7 +1588,7 @@
def test_shield_effect(self):
# Cancelling outer() does not affect inner().
proof = 0
- waiter = asyncio.Future(loop=self.loop)
+ waiter = self.new_future(self.loop)
@asyncio.coroutine
def inner():
@@ -1594,8 +1612,8 @@
self.assertEqual(proof, 1)
def test_shield_gather(self):
- child1 = asyncio.Future(loop=self.loop)
- child2 = asyncio.Future(loop=self.loop)
+ child1 = self.new_future(self.loop)
+ child2 = self.new_future(self.loop)
parent = asyncio.gather(child1, child2, loop=self.loop)
outer = asyncio.shield(parent, loop=self.loop)
test_utils.run_briefly(self.loop)
@@ -1608,8 +1626,8 @@
self.assertEqual(parent.result(), [1, 2])
def test_gather_shield(self):
- child1 = asyncio.Future(loop=self.loop)
- child2 = asyncio.Future(loop=self.loop)
+ child1 = self.new_future(self.loop)
+ child2 = self.new_future(self.loop)
inner1 = asyncio.shield(child1, loop=self.loop)
inner2 = asyncio.shield(child2, loop=self.loop)
parent = asyncio.gather(inner1, inner2, loop=self.loop)
@@ -1625,7 +1643,7 @@
test_utils.run_briefly(self.loop)
def test_as_completed_invalid_args(self):
- fut = asyncio.Future(loop=self.loop)
+ fut = self.new_future(self.loop)
# as_completed() expects a list of futures, not a future instance
self.assertRaises(TypeError, self.loop.run_until_complete,
@@ -1636,7 +1654,7 @@
coro.close()
def test_wait_invalid_args(self):
- fut = asyncio.Future(loop=self.loop)
+ fut = self.new_future(self.loop)
# wait() expects a list of futures, not a future instance
self.assertRaises(TypeError, self.loop.run_until_complete,
@@ -1663,7 +1681,7 @@
yield from fut
# A completed Future used to run the coroutine.
- fut = asyncio.Future(loop=self.loop)
+ fut = self.new_future(self.loop)
fut.set_result(None)
# Call the coroutine.
@@ -1697,15 +1715,15 @@
@asyncio.coroutine
def t2():
- f = asyncio.Future(loop=self.loop)
- asyncio.Task(t3(f), loop=self.loop)
+ f = self.new_future(self.loop)
+ self.new_task(self.loop, t3(f))
return (yield from f)
@asyncio.coroutine
def t3(f):
f.set_result((1, 2, 3))
- task = asyncio.Task(t1(), loop=self.loop)
+ task = self.new_task(self.loop, t1())
val = self.loop.run_until_complete(task)
self.assertEqual(val, (1, 2, 3))
@@ -1768,9 +1786,11 @@
@unittest.skipUnless(PY34,
'need python 3.4 or later')
def test_log_destroyed_pending_task(self):
+ Task = self.__class__.Task
+
@asyncio.coroutine
def kill_me(loop):
- future = asyncio.Future(loop=loop)
+ future = self.new_future(loop)
yield from future
# at this point, the only reference to kill_me() task is
# the Task._wakeup() method in future._callbacks
@@ -1783,7 +1803,7 @@
# schedule the task
coro = kill_me(self.loop)
task = asyncio.ensure_future(coro, loop=self.loop)
- self.assertEqual(asyncio.Task.all_tasks(loop=self.loop), {task})
+ self.assertEqual(Task.all_tasks(loop=self.loop), {task})
# execute the task so it waits for future
self.loop._run_once()
@@ -1798,7 +1818,7 @@
# no more reference to kill_me() task: the task is destroyed by the GC
support.gc_collect()
- self.assertEqual(asyncio.Task.all_tasks(loop=self.loop), set())
+ self.assertEqual(Task.all_tasks(loop=self.loop), set())
mock_handler.assert_called_with(self.loop, {
'message': 'Task was destroyed but it is pending!',
@@ -1863,10 +1883,10 @@
def test_task_source_traceback(self):
self.loop.set_debug(True)
- task = asyncio.Task(coroutine_function(), loop=self.loop)
+ task = self.new_task(self.loop, coroutine_function())
lineno = sys._getframe().f_lineno - 1
self.assertIsInstance(task._source_traceback, list)
- self.assertEqual(task._source_traceback[-1][:3],
+ self.assertEqual(task._source_traceback[-2][:3],
(__file__,
lineno,
'test_task_source_traceback'))
@@ -1878,7 +1898,7 @@
@asyncio.coroutine
def blocking_coroutine():
- fut = asyncio.Future(loop=loop)
+ fut = self.new_future(loop)
# Block: fut result is never set
yield from fut
@@ -1905,7 +1925,7 @@
loop = asyncio.new_event_loop()
self.addCleanup(loop.close)
- fut = asyncio.Future(loop=loop)
+ fut = self.new_future(loop)
# The indirection fut->child_coro is needed since otherwise the
# gathering task is done at the same time as the child future
def child_coro():
@@ -1929,6 +1949,157 @@
self.assertFalse(gather_task.cancelled())
self.assertEqual(gather_task.result(), [42])
+ @mock.patch('asyncio.base_events.logger')
+ def test_error_in_call_soon(self, m_log):
+ def call_soon(callback, *args):
+ raise ValueError
+ self.loop.call_soon = call_soon
+
+ @asyncio.coroutine
+ def coro():
+ pass
+
+ self.assertFalse(m_log.error.called)
+
+ with self.assertRaises(ValueError):
+ self.new_task(self.loop, coro())
+
+ self.assertTrue(m_log.error.called)
+ message = m_log.error.call_args[0][0]
+ self.assertIn('Task was destroyed but it is pending', message)
+
+ self.assertEqual(self.Task.all_tasks(self.loop), set())
+
+
+def add_subclass_tests(cls):
+ BaseTask = cls.Task
+ BaseFuture = cls.Future
+
+ if BaseTask is None or BaseFuture is None:
+ return cls
+
+ class CommonFuture:
+ def __init__(self, *args, **kwargs):
+ self.calls = collections.defaultdict(lambda: 0)
+ super().__init__(*args, **kwargs)
+
+ def _schedule_callbacks(self):
+ self.calls['_schedule_callbacks'] += 1
+ return super()._schedule_callbacks()
+
+ def add_done_callback(self, *args):
+ self.calls['add_done_callback'] += 1
+ return super().add_done_callback(*args)
+
+ class Task(CommonFuture, BaseTask):
+ def _step(self, *args):
+ self.calls['_step'] += 1
+ return super()._step(*args)
+
+ def _wakeup(self, *args):
+ self.calls['_wakeup'] += 1
+ return super()._wakeup(*args)
+
+ class Future(CommonFuture, BaseFuture):
+ pass
+
+ def test_subclasses_ctask_cfuture(self):
+ fut = self.Future(loop=self.loop)
+
+ async def func():
+ self.loop.call_soon(lambda: fut.set_result('spam'))
+ return await fut
+
+ task = self.Task(func(), loop=self.loop)
+
+ result = self.loop.run_until_complete(task)
+
+ self.assertEqual(result, 'spam')
+
+ self.assertEqual(
+ dict(task.calls),
+ {'_step': 2, '_wakeup': 1, 'add_done_callback': 1,
+ '_schedule_callbacks': 1})
+
+ self.assertEqual(
+ dict(fut.calls),
+ {'add_done_callback': 1, '_schedule_callbacks': 1})
+
+ # Add patched Task & Future back to the test case
+ cls.Task = Task
+ cls.Future = Future
+
+ # Add an extra unit-test
+ cls.test_subclasses_ctask_cfuture = test_subclasses_ctask_cfuture
+
+ # Disable the "test_task_source_traceback" test
+ # (the test is hardcoded for a particular call stack, which
+ # is slightly different for Task subclasses)
+ cls.test_task_source_traceback = None
+
+ return cls
+
+
+@unittest.skipUnless(hasattr(futures, '_CFuture'),
+ 'requires the C _asyncio module')
+class CTask_CFuture_Tests(BaseTaskTests, test_utils.TestCase):
+ Task = getattr(tasks, '_CTask', None)
+ Future = getattr(futures, '_CFuture', None)
+
+
+@unittest.skipUnless(hasattr(futures, '_CFuture'),
+ 'requires the C _asyncio module')
+@add_subclass_tests
+class CTask_CFuture_SubclassTests(BaseTaskTests, test_utils.TestCase):
+ Task = getattr(tasks, '_CTask', None)
+ Future = getattr(futures, '_CFuture', None)
+
+
+@unittest.skipUnless(hasattr(futures, '_CFuture'),
+ 'requires the C _asyncio module')
+class CTask_PyFuture_Tests(BaseTaskTests, test_utils.TestCase):
+ Task = getattr(tasks, '_CTask', None)
+ Future = futures._PyFuture
+
+
+@unittest.skipUnless(hasattr(futures, '_CFuture'),
+ 'requires the C _asyncio module')
+class PyTask_CFuture_Tests(BaseTaskTests, test_utils.TestCase):
+ Task = tasks._PyTask
+ Future = getattr(futures, '_CFuture', None)
+
+
+class PyTask_PyFuture_Tests(BaseTaskTests, test_utils.TestCase):
+ Task = tasks._PyTask
+ Future = futures._PyFuture
+
+
+@add_subclass_tests
+class PyTask_PyFuture_SubclassTests(BaseTaskTests, test_utils.TestCase):
+ Task = tasks._PyTask
+ Future = futures._PyFuture
+
+
+class GenericTaskTests(test_utils.TestCase):
+
+ def test_future_subclass(self):
+ self.assertTrue(issubclass(asyncio.Task, asyncio.Future))
+
+ def test_asyncio_module_compiled(self):
+ # Because of circular imports it's easy to make _asyncio
+ # module non-importable. This is a simple test that will
+ # fail on systems where C modules were successfully compiled
+ # (hence the test for _functools), but _asyncio somehow didn't.
+ try:
+ import _functools
+ except ImportError:
+ pass
+ else:
+ try:
+ import _asyncio
+ except ImportError:
+ self.fail('_asyncio module is missing')
+
class GatherTestsBase: