]> granicus.if.org Git - python/commitdiff
asyncio, Tulip issue 137: In debug mode, add the traceback where the coroutine
authorVictor Stinner <victor.stinner@gmail.com>
Fri, 27 Jun 2014 10:28:41 +0000 (12:28 +0200)
committerVictor Stinner <victor.stinner@gmail.com>
Fri, 27 Jun 2014 10:28:41 +0000 (12:28 +0200)
object was created to the "coroutine ... was never yield from" log

Lib/asyncio/tasks.py
Lib/test/test_asyncio/test_tasks.py

index 52ca33a8c3ef013ca694dba8323e80d0c72f3114..89ec3a4caa75072ebc92003a08df5d69c2d82883 100644 (file)
@@ -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__
index 8fd3e28f1c6a6f731e5aff6916fc506cc4038406..c5eb92b859b2880bb809ea010f03007ef81c9a8e 100644 (file)
@@ -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: