diff options
author | Victor Stinner <victor.stinner@gmail.com> | 2014-06-27 10:28:41 (GMT) |
---|---|---|
committer | Victor Stinner <victor.stinner@gmail.com> | 2014-06-27 10:28:41 (GMT) |
commit | bbd96c6f47046e11f47de06550dcd1c816aad71c (patch) | |
tree | cfc97c37518b3e86bdf1e470ab8f7fea8dcf73f6 | |
parent | c4cca45baf89f694ac86ee950a742c0a56e8dc7f (diff) | |
download | cpython-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.py | 17 | ||||
-rw-r--r-- | Lib/test/test_asyncio/test_tasks.py | 32 |
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: |