From fe4a979099300e502eff8a1fd6f6d6a596771dda Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 27 Jun 2014 12:29:30 +0200 Subject: [PATCH] (Merge 3.4) asyncio, Tulip issue 137: In debug mode, add the traceback where the coroutine object was created to the "coroutine ... was never yield from" log --- Lib/asyncio/tasks.py | 17 ++++++++------- 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 52ca33a8c3e..89ec3a4caa7 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 8fd3e28f1c6..c5eb92b859b 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: