Commit c39b0766 authored by Stefan Behnel's avatar Stefan Behnel

adapt coroutine implementation to latest Py3.5

parent 72e7dda3
...@@ -86,7 +86,6 @@ static CYTHON_INLINE PyObject *__Pyx_Coroutine_GetAwaitableIter(PyObject *o); /* ...@@ -86,7 +86,6 @@ static CYTHON_INLINE PyObject *__Pyx_Coroutine_GetAwaitableIter(PyObject *o); /*
static PyObject *__Pyx__Coroutine_GetAwaitableIter(PyObject *o); /*proto*/ static PyObject *__Pyx__Coroutine_GetAwaitableIter(PyObject *o); /*proto*/
//////////////////// GetAwaitIter //////////////////// //////////////////// GetAwaitIter ////////////////////
//@requires: Coroutine
//@requires: ObjectHandling.c::PyObjectGetAttrStr //@requires: ObjectHandling.c::PyObjectGetAttrStr
//@requires: ObjectHandling.c::PyObjectCallNoArg //@requires: ObjectHandling.c::PyObjectCallNoArg
//@requires: ObjectHandling.c::PyObjectCallOneArg //@requires: ObjectHandling.c::PyObjectCallOneArg
...@@ -109,19 +108,21 @@ static PyObject *__Pyx__Coroutine_GetAwaitableIter(PyObject *obj) { ...@@ -109,19 +108,21 @@ static PyObject *__Pyx__Coroutine_GetAwaitableIter(PyObject *obj) {
if (likely(am && am->am_await)) { if (likely(am && am->am_await)) {
res = (*am->am_await)(obj); res = (*am->am_await)(obj);
} else } else
#endif
#if (CYTHON_COMPILING_IN_CPYTHON && PY_VERSION_HEX >= 0x030500B2) || defined(PyCoro_CheckExact)
if (PyCoro_CheckExact(obj)) {
Py_INCREF(obj);
return obj;
} else
#endif
#if CYTHON_COMPILING_IN_CPYTHON && defined(CO_ITERABLE_COROUTINE)
if (PyGen_CheckExact(obj) && ((PyGenObject*)obj)->gi_code && ((PyCodeObject *)((PyGenObject*)obj)->gi_code)->co_flags & CO_ITERABLE_COROUTINE) {
// Python generator marked with "@types.coroutine" decorator
Py_INCREF(obj);
return obj;
} else
#endif #endif
{ {
#if PY_VERSION_HEX >= 0x030500B1
#if CYTHON_COMPILING_IN_CPYTHON
if (PyGen_CheckCoroutineExact(obj)) {
// Python generator marked with "@types.coroutine" decorator
Py_INCREF(obj);
return obj;
}
#endif
// no slot => no method
goto slot_error;
#else
PyObject *method = __Pyx_PyObject_GetAttrStr(obj, PYIDENT("__await__")); PyObject *method = __Pyx_PyObject_GetAttrStr(obj, PYIDENT("__await__"));
if (unlikely(!method)) goto slot_error; if (unlikely(!method)) goto slot_error;
#if CYTHON_COMPILING_IN_CPYTHON #if CYTHON_COMPILING_IN_CPYTHON
...@@ -136,7 +137,6 @@ static PyObject *__Pyx__Coroutine_GetAwaitableIter(PyObject *obj) { ...@@ -136,7 +137,6 @@ static PyObject *__Pyx__Coroutine_GetAwaitableIter(PyObject *obj) {
#endif #endif
res = __Pyx_PyObject_CallNoArg(method); res = __Pyx_PyObject_CallNoArg(method);
Py_DECREF(method); Py_DECREF(method);
#endif
} }
if (unlikely(!res)) goto bad; if (unlikely(!res)) goto bad;
if (!PyIter_Check(res)) { if (!PyIter_Check(res)) {
...@@ -149,8 +149,8 @@ static PyObject *__Pyx__Coroutine_GetAwaitableIter(PyObject *obj) { ...@@ -149,8 +149,8 @@ static PyObject *__Pyx__Coroutine_GetAwaitableIter(PyObject *obj) {
#ifdef __Pyx_Coroutine_USED #ifdef __Pyx_Coroutine_USED
is_coroutine |= __Pyx_Coroutine_CheckExact(res); is_coroutine |= __Pyx_Coroutine_CheckExact(res);
#endif #endif
#if PY_VERSION_HEX >= 0x030500B1 #if (CYTHON_COMPILING_IN_CPYTHON && PY_VERSION_HEX >= 0x030500B2) || defined(PyCoro_CheckExact)
is_coroutine |= PyGen_CheckCoroutineExact(res); is_coroutine |= PyCoro_CheckExact(res);
#endif #endif
if (unlikely(is_coroutine)) { if (unlikely(is_coroutine)) {
/* __await__ must return an *iterator*, not /* __await__ must return an *iterator*, not
...@@ -1009,15 +1009,18 @@ static PyObject *__Pyx_CoroutineAwait_no_new(CYTHON_UNUSED PyTypeObject *type, C ...@@ -1009,15 +1009,18 @@ static PyObject *__Pyx_CoroutineAwait_no_new(CYTHON_UNUSED PyTypeObject *type, C
} }
static PyMethodDef __pyx_CoroutineAwait_methods[] = { static PyMethodDef __pyx_CoroutineAwait_methods[] = {
{"send", (PyCFunction) __Pyx_CoroutineAwait_Send, METH_O, 0}, {"send", (PyCFunction) __Pyx_CoroutineAwait_Send, METH_O,
{"throw", (PyCFunction) __Pyx_CoroutineAwait_Throw, METH_VARARGS, 0}, (char*) PyDoc_STR("send(arg) -> send 'arg' into coroutine,\nreturn next yielded value or raise StopIteration.")},
{"close", (PyCFunction) __Pyx_CoroutineAwait_Close, METH_NOARGS, 0}, {"throw", (PyCFunction) __Pyx_CoroutineAwait_Throw, METH_VARARGS,
(char*) PyDoc_STR("throw(typ[,val[,tb]]) -> raise exception in coroutine,\nreturn next yielded value or raise StopIteration.")},
{"close", (PyCFunction) __Pyx_CoroutineAwait_Close, METH_NOARGS,
(char*) PyDoc_STR("close() -> raise GeneratorExit inside coroutine.")},
{0, 0, 0, 0} {0, 0, 0, 0}
}; };
static PyTypeObject __pyx_CoroutineAwaitType_type = { static PyTypeObject __pyx_CoroutineAwaitType_type = {
PyVarObject_HEAD_INIT(0, 0) PyVarObject_HEAD_INIT(0, 0)
"coroutine_await", /*tp_name*/ "coroutine_wrapper", /*tp_name*/
sizeof(__pyx_CoroutineAwaitObject), /*tp_basicsize*/ sizeof(__pyx_CoroutineAwaitObject), /*tp_basicsize*/
0, /*tp_itemsize*/ 0, /*tp_itemsize*/
(destructor) __Pyx_CoroutineAwait_dealloc,/*tp_dealloc*/ (destructor) __Pyx_CoroutineAwait_dealloc,/*tp_dealloc*/
...@@ -1154,11 +1157,15 @@ static PyObject *__Pyx_Coroutine_compare(PyObject *obj, PyObject *other, int op) ...@@ -1154,11 +1157,15 @@ static PyObject *__Pyx_Coroutine_compare(PyObject *obj, PyObject *other, int op)
#endif #endif
static PyMethodDef __pyx_Coroutine_methods[] = { static PyMethodDef __pyx_Coroutine_methods[] = {
{"send", (PyCFunction) __Pyx_Coroutine_Send, METH_O, 0}, {"send", (PyCFunction) __Pyx_Coroutine_Send, METH_O,
{"throw", (PyCFunction) __Pyx_Coroutine_Throw, METH_VARARGS, 0}, (char*) PyDoc_STR("send(arg) -> send 'arg' into coroutine,\nreturn next yielded value or raise StopIteration.")},
{"close", (PyCFunction) __Pyx_Coroutine_Close, METH_NOARGS, 0}, {"throw", (PyCFunction) __Pyx_Coroutine_Throw, METH_VARARGS,
(char*) PyDoc_STR("throw(typ[,val[,tb]]) -> raise exception in coroutine,\nreturn next yielded value or raise StopIteration.")},
{"close", (PyCFunction) __Pyx_Coroutine_Close, METH_NOARGS,
(char*) PyDoc_STR("close() -> raise GeneratorExit inside coroutine.")},
#if PY_VERSION_HEX < 0x030500B1 #if PY_VERSION_HEX < 0x030500B1
{"__await__", (PyCFunction) __Pyx_Coroutine_await, METH_NOARGS, 0}, {"__await__", (PyCFunction) __Pyx_Coroutine_await, METH_NOARGS,
(char*) PyDoc_STR("")},
#endif #endif
{0, 0, 0, 0} {0, 0, 0, 0}
}; };
...@@ -1270,9 +1277,12 @@ static int __pyx_Coroutine_init(void) { ...@@ -1270,9 +1277,12 @@ static int __pyx_Coroutine_init(void) {
//@requires: PatchGeneratorABC //@requires: PatchGeneratorABC
static PyMethodDef __pyx_Generator_methods[] = { static PyMethodDef __pyx_Generator_methods[] = {
{"send", (PyCFunction) __Pyx_Coroutine_Send, METH_O, 0}, {"send", (PyCFunction) __Pyx_Coroutine_Send, METH_O,
{"throw", (PyCFunction) __Pyx_Coroutine_Throw, METH_VARARGS, 0}, (char*) PyDoc_STR("send(arg) -> send 'arg' into generator,\nreturn next yielded value or raise StopIteration.")},
{"close", (PyCFunction) __Pyx_Coroutine_Close, METH_NOARGS, 0}, {"throw", (PyCFunction) __Pyx_Coroutine_Throw, METH_VARARGS,
(char*) PyDoc_STR("throw(typ[,val[,tb]]) -> raise exception in generator,\nreturn next yielded value or raise StopIteration.")},
{"close", (PyCFunction) __Pyx_Coroutine_Close, METH_NOARGS,
(char*) PyDoc_STR("close() -> raise GeneratorExit inside generator.")},
{0, 0, 0, 0} {0, 0, 0, 0}
}; };
......
...@@ -333,6 +333,8 @@ VER_DEP_MODULES = { ...@@ -333,6 +333,8 @@ VER_DEP_MODULES = {
]), ]),
(3,4): (operator.lt, lambda x: x in ['run.py34_signature', (3,4): (operator.lt, lambda x: x in ['run.py34_signature',
]), ]),
(3,5): (operator.lt, lambda x: x in ['run.py35_pep492_interop',
]),
} }
INCLUDE_DIRS = [ d for d in os.getenv('INCLUDE', '').split(os.pathsep) if d ] INCLUDE_DIRS = [ d for d in os.getenv('INCLUDE', '').split(os.pathsep) if d ]
......
# cython: language_level=3, binding=True
# mode: run
# tag: pep492, asyncfor, await
import types
def run_async(coro):
#assert coro.__class__ is types.GeneratorType
assert coro.__class__.__name__ in ('coroutine', 'GeneratorWrapper'), coro.__class__.__name__
buffer = []
result = None
while True:
try:
buffer.append(coro.send(None))
except StopIteration as ex:
result = ex.args[0] if ex.args else None
break
return buffer, result
def run_async__await__(coro):
assert coro.__class__.__name__ in ('coroutine', 'GeneratorWrapper'), coro.__class__.__name__
aw = coro.__await__()
buffer = []
result = None
i = 0
while True:
try:
if i % 2:
buffer.append(next(aw))
else:
buffer.append(aw.send(None))
i += 1
except StopIteration as ex:
result = ex.args[0] if ex.args else None
break
return buffer, result
async def await_pyobject(awaitable):
"""
>>> async def simple():
... return 10
>>> buffer, result = run_async(await_pyobject(simple()))
>>> result
10
>>> async def awaiting(awaitable):
... return await awaitable
>>> buffer, result = run_async(await_pyobject(awaiting(simple())))
>>> result
10
"""
return await awaitable
def await_cyobject():
"""
>>> async def run_await(awaitable):
... return await awaitable
>>> simple, awaiting = await_cyobject()
>>> buffer, result = run_async(run_await(simple()))
>>> result
10
>>> buffer, result = run_async(run_await(awaiting(simple())))
>>> result
10
"""
async def simple():
return 10
async def awaiting(awaitable):
return await awaitable
return simple, awaiting
...@@ -38,6 +38,9 @@ except ImportError: ...@@ -38,6 +38,9 @@ except ImportError:
@property @property
def gi_running(self): def gi_running(self):
return self.__wrapped__.gi_running return self.__wrapped__.gi_running
cr_code = gi_code
cr_frame = gi_frame
cr_running = gi_running
def __next__(self): def __next__(self):
return next(self.__wrapped__) return next(self.__wrapped__)
def __iter__(self): def __iter__(self):
...@@ -86,6 +89,25 @@ def run_async(coro): ...@@ -86,6 +89,25 @@ def run_async(coro):
return buffer, result return buffer, result
def run_async__await__(coro):
assert coro.__class__.__name__ in ('coroutine', 'GeneratorWrapper'), coro.__class__.__name__
aw = coro.__await__()
buffer = []
result = None
i = 0
while True:
try:
if i % 2:
buffer.append(next(aw))
else:
buffer.append(aw.send(None))
i += 1
except StopIteration as ex:
result = ex.args[0] if ex.args else None
break
return buffer, result
@contextlib.contextmanager @contextlib.contextmanager
def silence_coro_gc(): def silence_coro_gc():
with warnings.catch_warnings(): with warnings.catch_warnings():
...@@ -166,9 +188,14 @@ class CoroutineTest(unittest.TestCase): ...@@ -166,9 +188,14 @@ class CoroutineTest(unittest.TestCase):
else: else:
self.assertTrue(False, "'%s' did not match '%s'" % (first_match, regex)) self.assertTrue(False, "'%s' did not match '%s'" % (first_match, regex))
def assertRegex(self, value, regex): if not hasattr(unittest.TestCase, 'assertRegex'):
self.assertTrue(re.search(regex, str(value)), def assertRegex(self, value, regex):
"'%s' did not match '%s'" % (value, regex)) self.assertTrue(re.search(regex, str(value)),
"'%s' did not match '%s'" % (value, regex))
if not hasattr(unittest.TestCase, 'assertIn'):
def assertIn(self, member, container, msg=None):
self.assertTrue(member in container, msg)
def test_gen_1(self): def test_gen_1(self):
def gen(): yield def gen(): yield
...@@ -180,13 +207,15 @@ class CoroutineTest(unittest.TestCase): ...@@ -180,13 +207,15 @@ class CoroutineTest(unittest.TestCase):
f = foo() f = foo()
self.assertEqual(f.__class__.__name__, 'coroutine') self.assertEqual(f.__class__.__name__, 'coroutine')
#self.assertIsInstance(f, types.GeneratorType) #self.assertIsInstance(f, types.CoroutineType)
#self.assertTrue(bool(foo.__code__.co_flags & 0x80)) #self.assertTrue(bool(foo.__code__.co_flags & 0x80))
#self.assertTrue(bool(foo.__code__.co_flags & 0x20)) #self.assertTrue(bool(foo.__code__.co_flags & 0x20))
#self.assertTrue(bool(f.gi_code.co_flags & 0x80)) #self.assertTrue(bool(f.cr_code.co_flags & 0x80))
#self.assertTrue(bool(f.gi_code.co_flags & 0x20)) #self.assertTrue(bool(f.cr_code.co_flags & 0x20))
self.assertEqual(run_async(f), ([], 10)) self.assertEqual(run_async(f), ([], 10))
self.assertEqual(run_async__await__(foo()), ([], 10))
def bar(): pass def bar(): pass
self.assertFalse(bool(bar.__code__.co_flags & 0x80)) self.assertFalse(bool(bar.__code__.co_flags & 0x80))
...@@ -196,7 +225,7 @@ class CoroutineTest(unittest.TestCase): ...@@ -196,7 +225,7 @@ class CoroutineTest(unittest.TestCase):
raise StopIteration raise StopIteration
with self.assertRaisesRegex( with self.assertRaisesRegex(
RuntimeError, "generator raised StopIteration"): RuntimeError, "coroutine raised StopIteration"):
run_async(foo()) run_async(foo())
...@@ -212,7 +241,7 @@ class CoroutineTest(unittest.TestCase): ...@@ -212,7 +241,7 @@ class CoroutineTest(unittest.TestCase):
raise StopIteration raise StopIteration
check = lambda: self.assertRaisesRegex( check = lambda: self.assertRaisesRegex(
TypeError, "coroutine-objects do not support iteration") TypeError, "'coroutine' object is not iterable")
with check(): with check():
list(foo()) list(foo())
...@@ -245,7 +274,7 @@ class CoroutineTest(unittest.TestCase): ...@@ -245,7 +274,7 @@ class CoroutineTest(unittest.TestCase):
await bar() await bar()
check = lambda: self.assertRaisesRegex( check = lambda: self.assertRaisesRegex(
TypeError, "coroutine-objects do not support iteration") TypeError, "'coroutine' object is not iterable")
with check(): with check():
for el in foo(): pass for el in foo(): pass
...@@ -282,7 +311,7 @@ class CoroutineTest(unittest.TestCase): ...@@ -282,7 +311,7 @@ class CoroutineTest(unittest.TestCase):
with silence_coro_gc(), self.assertRaisesRegex( with silence_coro_gc(), self.assertRaisesRegex(
TypeError, TypeError,
"cannot 'yield from' a coroutine object from a generator"): "cannot 'yield from' a coroutine object in a non-coroutine generator"):
list(foo()) list(foo())
...@@ -334,7 +363,7 @@ class CoroutineTest(unittest.TestCase): ...@@ -334,7 +363,7 @@ class CoroutineTest(unittest.TestCase):
self.assertEqual(N, 0) self.assertEqual(N, 0)
aw.close() aw.close()
self.assertEqual(N, 1) self.assertEqual(N, 1)
with self.assertRaises(TypeError): with self.assertRaises(TypeError): # removed from CPython test suite?
type(aw).close(None) type(aw).close(None)
coro = foo() coro = foo()
...@@ -343,9 +372,67 @@ class CoroutineTest(unittest.TestCase): ...@@ -343,9 +372,67 @@ class CoroutineTest(unittest.TestCase):
with self.assertRaises(ZeroDivisionError): with self.assertRaises(ZeroDivisionError):
aw.throw(ZeroDivisionError, None, None) aw.throw(ZeroDivisionError, None, None)
self.assertEqual(N, 102) self.assertEqual(N, 102)
with self.assertRaises(TypeError): with self.assertRaises(TypeError): # removed from CPython test suite?
type(aw).throw(None, None, None, None) type(aw).throw(None, None, None, None)
def test_func_11(self):
async def func(): pass
coro = func()
# Test that PyCoro_Type and _PyCoroWrapper_Type types were properly
# initialized
self.assertIn('__await__', dir(coro))
self.assertIn('__iter__', dir(coro.__await__()))
self.assertIn('coroutine_wrapper', repr(coro.__await__()))
coro.close() # avoid RuntimeWarning
def test_func_12(self):
async def g():
i = me.send(None)
await None
me = g()
with self.assertRaisesRegex(ValueError,
"coroutine already executing"):
me.send(None)
def test_func_13(self):
async def g():
pass
with self.assertRaisesRegex(
TypeError,
"can't send non-None value to a just-started coroutine"):
g().send('spam')
def test_func_14(self):
@types_coroutine
def gen():
yield
async def coro():
try:
await gen()
except GeneratorExit:
await gen()
c = coro()
c.send(None)
with self.assertRaisesRegex(RuntimeError,
"coroutine ignored GeneratorExit"):
c.close()
def test_corotype_1(self):
async def f(): pass
ct = type(f())
self.assertIn('into coroutine', ct.send.__doc__)
self.assertIn('inside coroutine', ct.close.__doc__)
self.assertIn('in coroutine', ct.throw.__doc__)
self.assertIn('of the coroutine', ct.__dict__['__name__'].__doc__)
self.assertIn('of the coroutine', ct.__dict__['__qualname__'].__doc__)
self.assertEqual(ct.__name__, 'coroutine')
async def f(): pass
c = f()
self.assertIn('coroutine object', repr(c))
c.close()
def test_await_1(self): def test_await_1(self):
async def foo(): async def foo():
...@@ -364,6 +451,7 @@ class CoroutineTest(unittest.TestCase): ...@@ -364,6 +451,7 @@ class CoroutineTest(unittest.TestCase):
await AsyncYieldFrom([1, 2, 3]) await AsyncYieldFrom([1, 2, 3])
self.assertEqual(run_async(foo()), ([1, 2, 3], None)) self.assertEqual(run_async(foo()), ([1, 2, 3], None))
self.assertEqual(run_async__await__(foo()), ([1, 2, 3], None))
def test_await_4(self): def test_await_4(self):
async def bar(): async def bar():
...@@ -432,7 +520,7 @@ class CoroutineTest(unittest.TestCase): ...@@ -432,7 +520,7 @@ class CoroutineTest(unittest.TestCase):
db = {'b': lambda: wrap} db = {'b': lambda: wrap}
class DB: class DB(object):
b = staticmethod(wrap) b = staticmethod(wrap)
return (await bar() + await wrap()() + await db['b']()()() + return (await bar() + await wrap()() + await db['b']()()() +
...@@ -506,9 +594,9 @@ class CoroutineTest(unittest.TestCase): ...@@ -506,9 +594,9 @@ class CoroutineTest(unittest.TestCase):
coro = foo() coro = foo()
it = coro.__await__() it = coro.__await__()
self.assertEqual(type(it).__name__, 'coroutine_await') self.assertEqual(type(it).__name__, 'coroutine_wrapper')
with self.assertRaisesRegex(TypeError, "cannot instantiate 'coroutine_await' type"): with self.assertRaisesRegex(TypeError, "cannot instantiate 'coroutine_wrapper' type"):
type(it)() # cannot instantiate type(it)() # cannot instantiate
with self.assertRaisesRegex(StopIteration, "123"): with self.assertRaisesRegex(StopIteration, "123"):
...@@ -676,7 +764,6 @@ class CoroutineTest(unittest.TestCase): ...@@ -676,7 +764,6 @@ class CoroutineTest(unittest.TestCase):
self.assertEqual(CNT, 1) self.assertEqual(CNT, 1)
def test_with_9(self): def test_with_9(self):
CNT = 0 CNT = 0
...@@ -1108,12 +1195,89 @@ class SysSetCoroWrapperTest(unittest.TestCase): ...@@ -1108,12 +1195,89 @@ class SysSetCoroWrapperTest(unittest.TestCase):
foo() foo()
self.assertFalse(wrapped) self.assertFalse(wrapped)
def test_set_wrapper_2(self):
self.assertIsNone(sys.get_coroutine_wrapper())
with self.assertRaisesRegex(TypeError, "callable expected, got int"):
sys.set_coroutine_wrapper(1)
self.assertIsNone(sys.get_coroutine_wrapper())
def test_set_wrapper_3(self):
async def foo():
return 'spam'
def wrapper(coro):
async def wrap(coro):
return await coro
return wrap(coro)
sys.set_coroutine_wrapper(wrapper)
try:
with silence_coro_gc(), self.assertRaisesRegex(
RuntimeError,
"coroutine wrapper.*\.wrapper at 0x.*attempted to "
"recursively wrap .* wrap .*"):
foo()
finally:
sys.set_coroutine_wrapper(None)
def test_set_wrapper_4(self):
@types_coroutine
def foo():
return 'spam'
wrapped = None
def wrap(gen):
nonlocal wrapped
wrapped = gen
return gen
sys.set_coroutine_wrapper(wrap)
try:
foo()
self.assertIs(
wrapped, None,
"generator-based coroutine was wrapped via "
"sys.set_coroutine_wrapper")
finally:
sys.set_coroutine_wrapper(None)
class CAPITest(unittest.TestCase):
def test_tp_await_1(self):
from _testcapi import awaitType as at
async def foo():
future = at(iter([1]))
return (await future)
self.assertEqual(foo().send(None), 1)
def test_tp_await_2(self):
# Test tp_await to __await__ mapping
from _testcapi import awaitType as at
future = at(iter([1]))
self.assertEqual(next(future.__await__()), 1)
def test_tp_await_3(self):
from _testcapi import awaitType as at
async def foo():
future = at(1)
return (await future)
with self.assertRaisesRegex(
TypeError, "__await__.*returned non-iterator of type 'int'"):
self.assertEqual(foo().send(None), 1)
# disable some tests that only apply to CPython # disable some tests that only apply to CPython
# TODO? # TODO?
if True or sys.version_info < (3, 5): if True or sys.version_info < (3, 5):
SysSetCoroWrapperTest = None SysSetCoroWrapperTest = None
CAPITest = None
if sys.version_info < (3, 5): # (3, 4, 4) if sys.version_info < (3, 5): # (3, 4, 4)
CoroAsyncIOCompatTest = None CoroAsyncIOCompatTest = None
......
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