]> granicus.if.org Git - python/commitdiff
bpo-32703: Fix coroutine resource warning in case where there's an error (GH-5410)
authorYury Selivanov <yury@magic.io>
Mon, 29 Jan 2018 19:31:47 +0000 (14:31 -0500)
committerGitHub <noreply@github.com>
Mon, 29 Jan 2018 19:31:47 +0000 (14:31 -0500)
The commit removes one unnecessary "if" clause in genobject.c.  That "if" clause was masking un-awaited coroutines warnings just to make writing unittests more convenient.

Lib/test/test_coroutines.py
Misc/NEWS.d/next/Core and Builtins/2018-01-29-01-15-17.bpo-32703.mwrF4-.rst [new file with mode: 0644]
Objects/genobject.c

index 19a3444b6ad7a12f3b91e8f2098a039b4cb5c749..f3d77192ae765f5e416a6821d95275a50108594f 100644 (file)
@@ -520,34 +520,38 @@ class CoroutineTest(unittest.TestCase):
         async def foo():
             raise StopIteration
 
-        with silence_coro_gc():
-            self.assertRegex(repr(foo()), '^<coroutine object.* at 0x.*>$')
+        coro = foo()
+        self.assertRegex(repr(coro), '^<coroutine object.* at 0x.*>$')
+        coro.close()
 
     def test_func_4(self):
         async def foo():
             raise StopIteration
+        coro = foo()
 
         check = lambda: self.assertRaisesRegex(
             TypeError, "'coroutine' object is not iterable")
 
         with check():
-            list(foo())
+            list(coro)
 
         with check():
-            tuple(foo())
+            tuple(coro)
 
         with check():
-            sum(foo())
+            sum(coro)
 
         with check():
-            iter(foo())
+            iter(coro)
 
-        with silence_coro_gc(), check():
-            for i in foo():
+        with check():
+            for i in coro:
                 pass
 
-        with silence_coro_gc(), check():
-            [i for i in foo()]
+        with check():
+            [i for i in coro]
+
+        coro.close()
 
     def test_func_5(self):
         @types.coroutine
@@ -560,8 +564,11 @@ class CoroutineTest(unittest.TestCase):
         check = lambda: self.assertRaisesRegex(
             TypeError, "'coroutine' object is not iterable")
 
+        coro = foo()
         with check():
-            for el in foo(): pass
+            for el in coro:
+                pass
+        coro.close()
 
         # the following should pass without an error
         for el in bar():
@@ -588,35 +595,53 @@ class CoroutineTest(unittest.TestCase):
     def test_func_7(self):
         async def bar():
             return 10
+        coro = bar()
 
         def foo():
-            yield from bar()
-
-        with silence_coro_gc(), self.assertRaisesRegex(
-            TypeError,
-            "cannot 'yield from' a coroutine object in a non-coroutine generator"):
+            yield from coro
 
+        with self.assertRaisesRegex(
+                TypeError,
+                "cannot 'yield from' a coroutine object in "
+                "a non-coroutine generator"):
             list(foo())
 
+        coro.close()
+
     def test_func_8(self):
         @types.coroutine
         def bar():
-            return (yield from foo())
+            return (yield from coro)
 
         async def foo():
             return 'spam'
 
-        self.assertEqual(run_async(bar()), ([], 'spam') )
+        coro = foo()
+        self.assertEqual(run_async(bar()), ([], 'spam'))
+        coro.close()
 
     def test_func_9(self):
-        async def foo(): pass
+        async def foo():
+            pass
 
         with self.assertWarnsRegex(
-            RuntimeWarning, "coroutine '.*test_func_9.*foo' was never awaited"):
+                RuntimeWarning,
+                r"coroutine '.*test_func_9.*foo' was never awaited"):
 
             foo()
             support.gc_collect()
 
+        with self.assertWarnsRegex(
+                RuntimeWarning,
+                r"coroutine '.*test_func_9.*foo' was never awaited"):
+
+            with self.assertRaises(TypeError):
+                # See bpo-32703.
+                for _ in foo():
+                    pass
+
+            support.gc_collect()
+
     def test_func_10(self):
         N = 0
 
@@ -674,11 +699,14 @@ class CoroutineTest(unittest.TestCase):
     def test_func_13(self):
         async def g():
             pass
+
+        coro = g()
         with self.assertRaisesRegex(
-            TypeError,
-            "can't send non-None value to a just-started coroutine"):
+                TypeError,
+                "can't send non-None value to a just-started coroutine"):
+            coro.send('spam')
 
-            g().send('spam')
+        coro.close()
 
     def test_func_14(self):
         @types.coroutine
@@ -977,8 +1005,6 @@ class CoroutineTest(unittest.TestCase):
             return 42
 
         async def foo():
-            b = bar()
-
             db = {'b':  lambda: wrap}
 
             class DB:
@@ -1023,19 +1049,21 @@ class CoroutineTest(unittest.TestCase):
     def test_await_12(self):
         async def coro():
             return 'spam'
+        c = coro()
 
         class Awaitable:
             def __await__(self):
-                return coro()
+                return c
 
         async def foo():
             return await Awaitable()
 
         with self.assertRaisesRegex(
-            TypeError, r"__await__\(\) returned a coroutine"):
-
+                TypeError, r"__await__\(\) returned a coroutine"):
             run_async(foo())
 
+        c.close()
+
     def test_await_13(self):
         class Awaitable:
             def __await__(self):
@@ -1991,14 +2019,15 @@ class SysSetCoroWrapperTest(unittest.TestCase):
         finally:
             with self.assertWarns(DeprecationWarning):
                 sys.set_coroutine_wrapper(None)
+            f.close()
 
         with self.assertWarns(DeprecationWarning):
             self.assertIsNone(sys.get_coroutine_wrapper())
 
         wrapped = None
-        with silence_coro_gc():
-            foo()
+        coro = foo()
         self.assertFalse(wrapped)
+        coro.close()
 
     def test_set_wrapper_2(self):
         with self.assertWarns(DeprecationWarning):
@@ -2022,11 +2051,12 @@ class SysSetCoroWrapperTest(unittest.TestCase):
             sys.set_coroutine_wrapper(wrapper)
         try:
             with silence_coro_gc(), self.assertRaisesRegex(
-                RuntimeError,
-                r"coroutine wrapper.*\.wrapper at 0x.*attempted to "
-                r"recursively wrap .* wrap .*"):
+                    RuntimeError,
+                    r"coroutine wrapper.*\.wrapper at 0x.*attempted to "
+                    r"recursively wrap .* wrap .*"):
 
                 foo()
+
         finally:
             with self.assertWarns(DeprecationWarning):
                 sys.set_coroutine_wrapper(None)
diff --git a/Misc/NEWS.d/next/Core and Builtins/2018-01-29-01-15-17.bpo-32703.mwrF4-.rst b/Misc/NEWS.d/next/Core and Builtins/2018-01-29-01-15-17.bpo-32703.mwrF4-.rst
new file mode 100644 (file)
index 0000000..cfd166b
--- /dev/null
@@ -0,0 +1,2 @@
+Fix coroutine's ResourceWarning when there's an active error set when it's
+being finalized.
index 1fdb57c8ce8880c5a54d21035a23c0c548deba33..88b03c5ef313cab15be35ea104152707e7a30467 100644 (file)
@@ -44,9 +44,10 @@ _PyGen_Finalize(PyObject *self)
     PyObject *res = NULL;
     PyObject *error_type, *error_value, *error_traceback;
 
-    if (gen->gi_frame == NULL || gen->gi_frame->f_stacktop == NULL)
+    if (gen->gi_frame == NULL || gen->gi_frame->f_stacktop == NULL) {
         /* Generator isn't paused, so no need to close */
         return;
+    }
 
     if (PyAsyncGen_CheckExact(self)) {
         PyAsyncGenObject *agen = (PyAsyncGenObject*)self;
@@ -75,18 +76,18 @@ _PyGen_Finalize(PyObject *self)
        issue a RuntimeWarning. */
     if (gen->gi_code != NULL &&
         ((PyCodeObject *)gen->gi_code)->co_flags & CO_COROUTINE &&
-        gen->gi_frame->f_lasti == -1) {
-        if (!error_value) {
-            _PyErr_WarnUnawaitedCoroutine((PyObject *)gen);
-        }
+        gen->gi_frame->f_lasti == -1)
+    {
+        _PyErr_WarnUnawaitedCoroutine((PyObject *)gen);
     }
     else {
         res = gen_close(gen, NULL);
     }
 
     if (res == NULL) {
-        if (PyErr_Occurred())
+        if (PyErr_Occurred()) {
             PyErr_WriteUnraisable(self);
+        }
     }
     else {
         Py_DECREF(res);