summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Lib/asyncio/events.py30
-rw-r--r--Lib/asyncio/tasks.py10
-rw-r--r--Lib/asyncio/test_utils.py7
-rw-r--r--Lib/test/test_asyncio/test_events.py78
-rw-r--r--Lib/test/test_asyncio/test_tasks.py29
5 files changed, 123 insertions, 31 deletions
diff --git a/Lib/asyncio/events.py b/Lib/asyncio/events.py
index 4a9a9a3885..de161df65f 100644
--- a/Lib/asyncio/events.py
+++ b/Lib/asyncio/events.py
@@ -8,9 +8,29 @@ __all__ = ['AbstractEventLoopPolicy',
'get_child_watcher', 'set_child_watcher',
]
+import functools
+import inspect
import subprocess
import threading
import socket
+import sys
+
+
+_PY34 = sys.version_info >= (3, 4)
+
+def _get_function_source(func):
+ if _PY34:
+ func = inspect.unwrap(func)
+ elif hasattr(func, '__wrapped__'):
+ func = func.__wrapped__
+ if inspect.isfunction(func):
+ code = func.__code__
+ return (code.co_filename, code.co_firstlineno)
+ if isinstance(func, functools.partial):
+ return _get_function_source(func.func)
+ if _PY34 and isinstance(func, functools.partialmethod):
+ return _get_function_source(func.func)
+ return None
class Handle:
@@ -26,7 +46,15 @@ class Handle:
self._cancelled = False
def __repr__(self):
- res = 'Handle({}, {})'.format(self._callback, self._args)
+ cb_repr = getattr(self._callback, '__qualname__', None)
+ if not cb_repr:
+ cb_repr = str(self._callback)
+
+ source = _get_function_source(self._callback)
+ if source:
+ cb_repr += ' at %s:%s' % source
+
+ res = 'Handle({}, {})'.format(cb_repr, self._args)
if self._cancelled:
res += '<cancelled>'
return res
diff --git a/Lib/asyncio/tasks.py b/Lib/asyncio/tasks.py
index 8b8fb82ed2..e6fd3d380b 100644
--- a/Lib/asyncio/tasks.py
+++ b/Lib/asyncio/tasks.py
@@ -188,7 +188,15 @@ class Task(futures.Future):
i = res.find('<')
if i < 0:
i = len(res)
- res = res[:i] + '(<{}>)'.format(self._coro.__name__) + res[i:]
+ text = self._coro.__name__
+ coro = self._coro
+ if inspect.isgenerator(coro):
+ filename = coro.gi_code.co_filename
+ if coro.gi_frame is not None:
+ text += ' at %s:%s' % (filename, coro.gi_frame.f_lineno)
+ else:
+ text += ' done at %s' % filename
+ res = res[:i] + '(<{}>)'.format(text) + res[i:]
return res
def get_stack(self, *, limit=None):
diff --git a/Lib/asyncio/test_utils.py b/Lib/asyncio/test_utils.py
index 9c3656ac2b..1062bae132 100644
--- a/Lib/asyncio/test_utils.py
+++ b/Lib/asyncio/test_utils.py
@@ -372,3 +372,10 @@ class MockPattern(str):
"""
def __eq__(self, other):
return bool(re.search(str(self), other, re.S))
+
+
+def get_function_source(func):
+ source = events._get_function_source(func)
+ if source is None:
+ raise ValueError("unable to get the source of %r" % (func,))
+ return source
diff --git a/Lib/test/test_asyncio/test_events.py b/Lib/test/test_asyncio/test_events.py
index e19d991fc4..2262a75226 100644
--- a/Lib/test/test_asyncio/test_events.py
+++ b/Lib/test/test_asyncio/test_events.py
@@ -5,6 +5,7 @@ import gc
import io
import os
import platform
+import re
import signal
import socket
try:
@@ -1737,52 +1738,46 @@ else:
return asyncio.SelectorEventLoop(selectors.SelectSelector())
+def noop():
+ pass
+
+
class HandleTests(unittest.TestCase):
+ def setUp(self):
+ self.loop = None
+
def test_handle(self):
def callback(*args):
return args
args = ()
- h = asyncio.Handle(callback, args, mock.Mock())
+ h = asyncio.Handle(callback, args, self.loop)
self.assertIs(h._callback, callback)
self.assertIs(h._args, args)
self.assertFalse(h._cancelled)
- r = repr(h)
- self.assertTrue(r.startswith(
- 'Handle('
- '<function HandleTests.test_handle.<locals>.callback'))
- self.assertTrue(r.endswith('())'))
-
h.cancel()
self.assertTrue(h._cancelled)
- r = repr(h)
- self.assertTrue(r.startswith(
- 'Handle('
- '<function HandleTests.test_handle.<locals>.callback'))
- self.assertTrue(r.endswith('())<cancelled>'), r)
-
def test_handle_from_handle(self):
def callback(*args):
return args
- m_loop = object()
- h1 = asyncio.Handle(callback, (), loop=m_loop)
+ h1 = asyncio.Handle(callback, (), loop=self.loop)
self.assertRaises(
- AssertionError, asyncio.Handle, h1, (), m_loop)
+ AssertionError, asyncio.Handle, h1, (), self.loop)
def test_callback_with_exception(self):
def callback():
raise ValueError()
- m_loop = mock.Mock()
- m_loop.call_exception_handler = mock.Mock()
+ self.loop = mock.Mock()
+ self.loop.call_exception_handler = mock.Mock()
- h = asyncio.Handle(callback, (), m_loop)
+ h = asyncio.Handle(callback, (), self.loop)
h._run()
- m_loop.call_exception_handler.assert_called_with({
+ self.loop.call_exception_handler.assert_called_with({
'message': test_utils.MockPattern('Exception in callback.*'),
'exception': mock.ANY,
'handle': h
@@ -1790,9 +1785,50 @@ class HandleTests(unittest.TestCase):
def test_handle_weakref(self):
wd = weakref.WeakValueDictionary()
- h = asyncio.Handle(lambda: None, (), object())
+ h = asyncio.Handle(lambda: None, (), self.loop)
wd['h'] = h # Would fail without __weakref__ slot.
+ def test_repr(self):
+ # simple function
+ h = asyncio.Handle(noop, (), self.loop)
+ src = test_utils.get_function_source(noop)
+ self.assertEqual(repr(h),
+ 'Handle(noop at %s:%s, ())' % src)
+
+ # cancelled handle
+ h.cancel()
+ self.assertEqual(repr(h),
+ 'Handle(noop at %s:%s, ())<cancelled>' % src)
+
+ # decorated function
+ cb = asyncio.coroutine(noop)
+ h = asyncio.Handle(cb, (), self.loop)
+ self.assertEqual(repr(h),
+ 'Handle(noop at %s:%s, ())' % src)
+
+ # partial function
+ cb = functools.partial(noop)
+ h = asyncio.Handle(cb, (), self.loop)
+ filename, lineno = src
+ regex = (r'^Handle\(functools.partial\('
+ r'<function noop .*>\) at %s:%s, '
+ r'\(\)\)$' % (re.escape(filename), lineno))
+ self.assertRegex(repr(h), regex)
+
+ # partial method
+ if sys.version_info >= (3, 4):
+ method = HandleTests.test_repr
+ cb = functools.partialmethod(method)
+ src = test_utils.get_function_source(method)
+ h = asyncio.Handle(cb, (), self.loop)
+
+ filename, lineno = src
+ regex = (r'^Handle\(functools.partialmethod\('
+ r'<function HandleTests.test_repr .*>, , \) at %s:%s, '
+ r'\(\)\)$' % (re.escape(filename), lineno))
+ self.assertRegex(repr(h), regex)
+
+
class TimerTests(unittest.TestCase):
diff --git a/Lib/test/test_asyncio/test_tasks.py b/Lib/test/test_asyncio/test_tasks.py
index 45a0dc1d21..92eb9daefb 100644
--- a/Lib/test/test_asyncio/test_tasks.py
+++ b/Lib/test/test_asyncio/test_tasks.py
@@ -116,21 +116,30 @@ class TaskTests(unittest.TestCase):
yield from []
return 'abc'
+ filename, lineno = test_utils.get_function_source(notmuch)
+ src = "%s:%s" % (filename, lineno)
+
t = asyncio.Task(notmuch(), loop=self.loop)
t.add_done_callback(Dummy())
- self.assertEqual(repr(t), 'Task(<notmuch>)<PENDING, [Dummy()]>')
+ self.assertEqual(repr(t),
+ 'Task(<notmuch at %s>)<PENDING, [Dummy()]>' % src)
+
t.cancel() # Does not take immediate effect!
- self.assertEqual(repr(t), 'Task(<notmuch>)<CANCELLING, [Dummy()]>')
+ self.assertEqual(repr(t),
+ 'Task(<notmuch at %s>)<CANCELLING, [Dummy()]>' % src)
self.assertRaises(asyncio.CancelledError,
self.loop.run_until_complete, t)
- self.assertEqual(repr(t), 'Task(<notmuch>)<CANCELLED>')
+ self.assertEqual(repr(t),
+ 'Task(<notmuch done at %s>)<CANCELLED>' % filename)
+
t = asyncio.Task(notmuch(), loop=self.loop)
self.loop.run_until_complete(t)
- self.assertEqual(repr(t), "Task(<notmuch>)<result='abc'>")
+ self.assertEqual(repr(t),
+ "Task(<notmuch done at %s>)<result='abc'>" % filename)
def test_task_repr_custom(self):
@asyncio.coroutine
- def coro():
+ def notmuch():
pass
class T(asyncio.Future):
@@ -141,10 +150,14 @@ class TaskTests(unittest.TestCase):
def __repr__(self):
return super().__repr__()
- gen = coro()
+ gen = notmuch()
t = MyTask(gen, loop=self.loop)
- self.assertEqual(repr(t), 'T[](<coro>)')
- gen.close()
+ filename = gen.gi_code.co_filename
+ lineno = gen.gi_frame.f_lineno
+ # FIXME: check for the name "coro" instead of "notmuch" because
+ # @asyncio.coroutine drops the name of the wrapped function:
+ # http://bugs.python.org/issue21205
+ self.assertEqual(repr(t), 'T[](<coro at %s:%s>)' % (filename, lineno))
def test_task_basics(self):
@asyncio.coroutine