(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
This commit is contained in:
Victor Stinner 2014-06-27 12:29:30 +02:00
parent 19b011109d
commit fe4a979099
2 changed files with 42 additions and 7 deletions

View File

@ -43,6 +43,7 @@ class CoroWrapper:
assert inspect.isgenerator(gen), gen assert inspect.isgenerator(gen), gen
self.gen = gen self.gen = gen
self.func = func self.func = func
self._source_traceback = traceback.extract_stack(sys._getframe(1))
def __iter__(self): def __iter__(self):
return self return self
@ -81,13 +82,13 @@ class CoroWrapper:
gen = getattr(self, 'gen', None) gen = getattr(self, 'gen', None)
frame = getattr(gen, 'gi_frame', None) frame = getattr(gen, 'gi_frame', None)
if frame is not None and frame.f_lasti == -1: if frame is not None and frame.f_lasti == -1:
func = self.func func = events._format_callback(self.func, ())
code = func.__code__ tb = ''.join(traceback.format_list(self._source_traceback))
filename = code.co_filename message = ('Coroutine %s was never yielded from\n'
lineno = code.co_firstlineno 'Coroutine object created at (most recent call last):\n'
logger.error( '%s'
'Coroutine %r defined at %s:%s was never yielded from', % (func, tb.rstrip()))
func.__name__, filename, lineno) logger.error(message)
def coroutine(func): def coroutine(func):
@ -112,6 +113,8 @@ def coroutine(func):
@functools.wraps(func) @functools.wraps(func)
def wrapper(*args, **kwds): def wrapper(*args, **kwds):
w = CoroWrapper(coro(*args, **kwds), func) w = CoroWrapper(coro(*args, **kwds), func)
if w._source_traceback:
del w._source_traceback[-1]
w.__name__ = func.__name__ w.__name__ = func.__name__
if _PY35: if _PY35:
w.__qualname__ = func.__qualname__ w.__qualname__ = func.__qualname__

View File

@ -1,6 +1,7 @@
"""Tests for tasks.py.""" """Tests for tasks.py."""
import os.path import os.path
import re
import sys import sys
import types import types
import unittest import unittest
@ -1572,6 +1573,37 @@ class TaskTests(test_utils.TestCase):
}) })
mock_handler.reset_mock() 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: class GatherTestsBase: