Commit f8c0aafb authored by Stefan Behnel's avatar Stefan Behnel

implement some error cases from the PEP 492 test suite

parent d78673fc
...@@ -8,8 +8,23 @@ static CYTHON_INLINE PyObject* __Pyx_Generator_Yield_From(__pyx_CoroutineObject ...@@ -8,8 +8,23 @@ static CYTHON_INLINE PyObject* __Pyx_Generator_Yield_From(__pyx_CoroutineObject
static CYTHON_INLINE PyObject* __Pyx_Generator_Yield_From(__pyx_CoroutineObject *gen, PyObject *source) { static CYTHON_INLINE PyObject* __Pyx_Generator_Yield_From(__pyx_CoroutineObject *gen, PyObject *source) {
PyObject *source_gen, *retval; PyObject *source_gen, *retval;
source_gen = PyObject_GetIter(source); source_gen = PyObject_GetIter(source);
if (unlikely(!source_gen)) if (unlikely(!source_gen)) {
#ifdef __Pyx_Coroutine_USED
#if CYTHON_COMPILING_IN_CPYTHON
// avoid exception instantiation if possible
if (PyErr_Occurred() == PyExc_TypeError
#else
if (PyErr_ExceptionMatches(PyExc_TypeError)
#endif
&& __Pyx_Coroutine_CheckExact(source)) {
PyErr_Clear();
// TODO: this should only happen for types.coroutine()ed generators, but we can't determine that here
Py_INCREF(source);
source_gen = source;
} else
#endif
return NULL; return NULL;
}
// source_gen is now the iterator, make the first next() call // source_gen is now the iterator, make the first next() call
retval = Py_TYPE(source_gen)->tp_iternext(source_gen); retval = Py_TYPE(source_gen)->tp_iternext(source_gen);
if (likely(retval)) { if (likely(retval)) {
...@@ -871,6 +886,54 @@ static __pyx_CoroutineObject *__Pyx__Coroutine_New(PyTypeObject* type, __pyx_cor ...@@ -871,6 +886,54 @@ static __pyx_CoroutineObject *__Pyx__Coroutine_New(PyTypeObject* type, __pyx_cor
//@requires: CoroutineBase //@requires: CoroutineBase
//@requires: PatchGeneratorABC //@requires: PatchGeneratorABC
static void __Pyx_Coroutine_check_and_dealloc(PyObject *self) {
__pyx_CoroutineObject *gen = (__pyx_CoroutineObject *) self;
if (gen->resume_label == 0 && !PyErr_Occurred()) {
#if PY_VERSION_HEX >= 0x03030000 || defined(PyErr_WarnFormat)
PyErr_WarnFormat(PyExc_RuntimeWarning, 1, "coroutine '%.50S' was never awaited", gen->gi_qualname);
#else
PyObject *msg, *qualname;
char *cname, *cmsg;
#if PY_MAJOR_VERSION >= 3
qualname = PyUnicode_AsUTF8String(gen->gi_qualname);
if (likely(qualname)) {
cname = PyBytes_AS_STRING(qualname);
} else {
PyErr_Clear();
cname = (char*) "?";
}
msg = PyBytes_FromFormat(
#else
qualname = gen->gi_qualname;
cname = PyString_AS_STRING(qualname);
msg = PyString_FromFormat(
#endif
"coroutine '%.50s' was never awaited", cname);
#if PY_MAJOR_VERSION >= 3
Py_XDECREF(qualname);
#endif
if (unlikely(!msg)) {
PyErr_Clear();
cmsg = (char*) "coroutine was never awaited";
} else {
#if PY_MAJOR_VERSION >= 3
cmsg = PyBytes_AS_STRING(msg);
#else
cmsg = PyString_AS_STRING(msg);
#endif
}
if (unlikely(PyErr_WarnEx(PyExc_RuntimeWarning, cmsg, 1) < 0))
PyErr_WriteUnraisable(self);
Py_XDECREF(msg);
#endif
}
__Pyx_Coroutine_dealloc(self);
}
#if PY_VERSION_HEX >= 0x030500B1 #if PY_VERSION_HEX >= 0x030500B1
static PyAsyncMethods __pyx_Coroutine_as_async { static PyAsyncMethods __pyx_Coroutine_as_async {
0, /*am_await*/ 0, /*am_await*/
...@@ -884,7 +947,7 @@ static PyTypeObject __pyx_CoroutineType_type = { ...@@ -884,7 +947,7 @@ static PyTypeObject __pyx_CoroutineType_type = {
"coroutine", /*tp_name*/ "coroutine", /*tp_name*/
sizeof(__pyx_CoroutineObject), /*tp_basicsize*/ sizeof(__pyx_CoroutineObject), /*tp_basicsize*/
0, /*tp_itemsize*/ 0, /*tp_itemsize*/
(destructor) __Pyx_Coroutine_dealloc,/*tp_dealloc*/ (destructor) __Pyx_Coroutine_check_and_dealloc,/*tp_dealloc*/
0, /*tp_print*/ 0, /*tp_print*/
0, /*tp_getattr*/ 0, /*tp_getattr*/
0, /*tp_setattr*/ 0, /*tp_setattr*/
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
import re import re
import gc import gc
import sys import sys
import types #import types
import os.path import os.path
import inspect import inspect
import unittest import unittest
...@@ -11,6 +11,35 @@ import warnings ...@@ -11,6 +11,35 @@ import warnings
import contextlib import contextlib
# fake types.coroutine() decorator
class types_coroutine(object):
def __init__(self, gen):
self._gen = gen
class as_coroutine(object):
def __init__(self, gen):
self._gen = gen
self.send = gen.send
self.throw = gen.throw
self.close = gen.close
def __await__(self):
return self._gen
def __iter__(self):
return self._gen
def __call__(self, *args, **kwargs):
return self.as_coroutine(self._gen(*args, **kwargs))
# compiled exec()
def exec(code_string, l, g):
from Cython.Shadow import inline
ns = inline(code_string, locals=l, globals=g, lib_dir=os.path.dirname(__file__))
g.update(ns)
class AsyncYieldFrom: class AsyncYieldFrom:
def __init__(self, obj): def __init__(self, obj):
self.obj = obj self.obj = obj
...@@ -29,7 +58,7 @@ class AsyncYield: ...@@ -29,7 +58,7 @@ class AsyncYield:
def run_async(coro): def run_async(coro):
#assert coro.__class__ is types.GeneratorType #assert coro.__class__ is types.GeneratorType
assert coro.__class__.__name__ == 'coroutine' assert coro.__class__.__name__ in ('coroutine', 'as_coroutine')
buffer = [] buffer = []
result = None result = None
...@@ -53,9 +82,6 @@ def silence_coro_gc(): ...@@ -53,9 +82,6 @@ def silence_coro_gc():
class TokenizerRegrTest(unittest.TestCase): class TokenizerRegrTest(unittest.TestCase):
def test_oneline_defs(self): def test_oneline_defs(self):
import Cython.Shadow
compile_dir = os.path.dirname(__file__)
buf = [] buf = []
for i in range(500): for i in range(500):
buf.append('def i{i}(): return {i}'.format(i=i)) buf.append('def i{i}(): return {i}'.format(i=i))
...@@ -63,16 +89,14 @@ class TokenizerRegrTest(unittest.TestCase): ...@@ -63,16 +89,14 @@ class TokenizerRegrTest(unittest.TestCase):
# Test that 500 consequent, one-line defs is OK # Test that 500 consequent, one-line defs is OK
ns = {} ns = {}
#exec(buf, ns, ns) exec(buf, ns, ns)
ns = Cython.Shadow.inline(buf, locals=ns, globals=ns, lib_dir=compile_dir)
self.assertEqual(ns['i499'](), 499) self.assertEqual(ns['i499'](), 499)
# Test that 500 consequent, one-line defs *and* # Test that 500 consequent, one-line defs *and*
# one 'async def' following them is OK # one 'async def' following them is OK
buf += '\nasync def foo():\n return' buf += '\nasync def foo():\n return'
ns = {} ns = {}
#exec(buf, ns, ns) exec(buf, ns, ns)
ns = Cython.Shadow.inline(buf, locals=ns, globals=ns, lib_dir=compile_dir)
self.assertEqual(ns['i499'](), 499) self.assertEqual(ns['i499'](), 499)
if hasattr(inspect, 'iscoroutinefunction'): if hasattr(inspect, 'iscoroutinefunction'):
self.assertTrue(inspect.iscoroutinefunction(ns['foo'])) self.assertTrue(inspect.iscoroutinefunction(ns['foo']))
...@@ -80,6 +104,20 @@ class TokenizerRegrTest(unittest.TestCase): ...@@ -80,6 +104,20 @@ class TokenizerRegrTest(unittest.TestCase):
class CoroutineTest(unittest.TestCase): class CoroutineTest(unittest.TestCase):
def setUpClass(cls):
# never mark warnings as "already seen" to prevent them from being suppressed
from warnings import simplefilter
simplefilter("always")
@contextlib.contextmanager
def assertRaises(self, exc_type):
try:
yield
except exc_type:
self.assertTrue(True)
else:
self.assertTrue(False)
@contextlib.contextmanager @contextlib.contextmanager
def assertRaisesRegex(self, exc_type, regex): def assertRaisesRegex(self, exc_type, regex):
# the error messages usually don't match, so we just ignore them # the error messages usually don't match, so we just ignore them
...@@ -90,6 +128,28 @@ class CoroutineTest(unittest.TestCase): ...@@ -90,6 +128,28 @@ class CoroutineTest(unittest.TestCase):
else: else:
self.assertTrue(False) self.assertTrue(False)
@contextlib.contextmanager
def assertWarnsRegex(self, exc_type, regex):
from warnings import catch_warnings
with catch_warnings(record=True) as log:
yield
first_match = None
for warning in log:
w = warning.message
if not isinstance(w, exc_type):
continue
if first_match is None:
first_match = w
if re.search(regex, str(w)):
self.assertTrue(True)
return
if first_match is None:
self.assertTrue(False, "no warning was raised of type '%s'" % exc_type.__name__)
else:
self.assertTrue(False, "'%s' did not match '%s'" % (first_match, regex))
def assertRegex(self, value, regex): def assertRegex(self, value, regex):
self.assertTrue(re.search(regex, str(value)), self.assertTrue(re.search(regex, str(value)),
"'%s' did not match '%s'" % (value, regex)) "'%s' did not match '%s'" % (value, regex))
...@@ -160,7 +220,7 @@ class CoroutineTest(unittest.TestCase): ...@@ -160,7 +220,7 @@ class CoroutineTest(unittest.TestCase):
[i for i in foo()] [i for i in foo()]
def test_func_5(self): def test_func_5(self):
@types.coroutine @types_coroutine
def bar(): def bar():
yield 1 yield 1
...@@ -181,7 +241,7 @@ class CoroutineTest(unittest.TestCase): ...@@ -181,7 +241,7 @@ class CoroutineTest(unittest.TestCase):
self.assertEqual(next(iter(bar())), 1) self.assertEqual(next(iter(bar())), 1)
def test_func_6(self): def test_func_6(self):
@types.coroutine @types_coroutine
def bar(): def bar():
yield 1 yield 1
yield 2 yield 2
...@@ -209,7 +269,7 @@ class CoroutineTest(unittest.TestCase): ...@@ -209,7 +269,7 @@ class CoroutineTest(unittest.TestCase):
list(foo()) list(foo())
def test_func_8(self): def test_func_8(self):
@types.coroutine @types_coroutine
def bar(): def bar():
return (yield from foo()) return (yield from foo())
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment