summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVictor Stinner <victor.stinner@gmail.com>2014-06-27 10:28:41 (GMT)
committerVictor Stinner <victor.stinner@gmail.com>2014-06-27 10:28:41 (GMT)
commitbbd96c6f47046e11f47de06550dcd1c816aad71c (patch)
treecfc97c37518b3e86bdf1e470ab8f7fea8dcf73f6
parentc4cca45baf89f694ac86ee950a742c0a56e8dc7f (diff)
downloadcpython-bbd96c6f47046e11f47de06550dcd1c816aad71c.zip
cpython-bbd96c6f47046e11f47de06550dcd1c816aad71c.tar.gz
cpython-bbd96c6f47046e11f47de06550dcd1c816aad71c.tar.bz2
asyncio, Tulip issue 137: In debug mode, add the traceback where the coroutine
object was created to the "coroutine ... was never yield from" log
-rw-r--r--Lib/asyncio/tasks.py17
-rw-r--r--Lib/test/test_asyncio/test_tasks.py32
2 files changed, 42 insertions, 7 deletions
diff --git a/Lib/asyncio/tasks.py b/Lib/asyncio/tasks.py
index 52ca33a..89ec3a4 100644
--- a/Lib/asyncio/tasks.py
+++ b/Lib/asyncio/tasks.py
@@ -43,6 +43,7 @@ class CoroWrapper:
assert inspect.isgenerator(gen), gen
self.gen = gen
self.func = func
+ self._source_traceback = traceback.extract_stack(sys._getframe(1))
def __iter__(self):
return self
@@ -81,13 +82,13 @@ class CoroWrapper:
gen = getattr(self, 'gen', None)
frame = getattr(gen, 'gi_frame', None)
if frame is not None and frame.f_lasti == -1:
- func = self.func
- code = func.__code__
- filename = code.co_filename
- lineno = code.co_firstlineno
- logger.error(
- 'Coroutine %r defined at %s:%s was never yielded from',
- func.__name__, filename, lineno)
+ func = events._format_callback(self.func, ())
+ tb = ''.join(traceback.format_list(self._source_traceback))
+ message = ('Coroutine %s was never yielded from\n'
+ 'Coroutine object created at (most recent call last):\n'
+ '%s'
+ % (func, tb.rstrip()))
+ logger.error(message)
def coroutine(func):
@@ -112,6 +113,8 @@ def coroutine(func):
@functools.wraps(func)
def wrapper(*args, **kwds):
w = CoroWrapper(coro(*args, **kwds), func)
+ if w._source_traceback:
+ del w._source_traceback[-1]
w.__name__ = func.__name__
if _PY35:
w.__qualname__ = func.__qualname__
diff --git a/Lib/test/test_asyncio/test_tasks.py b/Lib/test/test_asyncio/test_tasks.py
index 8fd3e28..c5eb92b 100644
--- a/Lib/test/test_asyncio/test_tasks.py
+++ b/Lib/test/test_asyncio/test_tasks.py
@@ -1,6 +1,7 @@
"""Tests for tasks.py."""
import os.path
+import re
import sys
import types
import unittest
@@ -1572,6 +1573,37 @@ class TaskTests(test_utils.TestCase):
})
mock_handler.reset_mock()
+ @mock.patch('asyncio.tasks.logger')
+ def test_coroutine_never_yielded(self, m_log):
+ debug = asyncio.tasks._DEBUG
+ try:
+ asyncio.tasks._DEBUG = True
+ @asyncio.coroutine
+ def coro_noop():
+ pass
+ finally:
+ asyncio.tasks._DEBUG = debug
+
+ tb_filename = __file__
+ tb_lineno = sys._getframe().f_lineno + 1
+ coro = coro_noop()
+ coro = None
+ support.gc_collect()
+
+ self.assertTrue(m_log.error.called)
+ message = m_log.error.call_args[0][0]
+ func_filename, func_lineno = test_utils.get_function_source(coro_noop)
+ regex = (r'^Coroutine %s\(\) at %s:%s was never yielded from\n'
+ r'Coroutine object created at \(most recent call last\):\n'
+ r'.*\n'
+ r' File "%s", line %s, in test_coroutine_never_yielded\n'
+ r' coro = coro_noop\(\)$'
+ % (re.escape(coro_noop.__qualname__),
+ func_filename, func_lineno,
+ tb_filename, tb_lineno))
+
+ self.assertRegex(message, re.compile(regex, re.DOTALL))
+
class GatherTestsBase: