>yieldtest_login()test_answers.py:11:________________________________________________________________________________________________________________________________________________________________env/lib/python3.6/site-packages/tornado/gen.py:1055:inrunvalue=future.result()env/lib/python3.6/site-packages/tornado/concurrent.py:238:inresultraise_exc_info(self._exc_info)<string>:4:inraise_exc_info???env/lib/python3.6/site-packages/tornado/gen.py:1143:inhandle_yieldself.future=convert_yielded(yielded)env/lib/python3.6/functools.py:803:inwrapperreturndispatch(args[0].__class__)(*args,**kw)________________________________________________________________________________________________________________________________________________________________yielded=<generatorobjecttest_loginat0x102420728>defconvert_yielded(yielded):"""Convert a yielded object into a `.Future`.
The default implementation accepts lists, dictionaries, and Futures.
If the `~functools.singledispatch` library is available, this function
may be extended to support additional types. For example::
@convert_yielded.register(asyncio.Future)
def _(asyncio_future):
return tornado.platform.asyncio.to_tornado_future(asyncio_future)
.. versionadded:: 4.1
"""# Lists and dicts containing YieldPoints were handled earlier.ifyieldedisNone:returnmomentelifisinstance(yielded,(list,dict)):returnmulti(yielded)elifis_future(yielded):returnyieldedelifisawaitable(yielded):return_wrap_awaitable(yielded)else:>raiseBadYieldError("yielded unknown object %r"%(yielded,))Etornado.gen.BadYieldError:yieldedunknownobject<generatorobjecttest_loginat0x102420728>env/lib/python3.6/site-packages/tornado/gen.py:1283:BadYieldError

WTF?

So there must be a stupid error, we check for typos, we go back and start copy/pasting code from the working test_schema to make sure we didn’t type @pytest.mark.test_gen or something. The failure remains.

After a while we reach the state where test_schema.py and test_answer.py is byte-for-byte identical, but answers fails and schema passes. We go home and rethink our lives.

Next day, we realise that when called on just one of those files, pytest will run TWO tests, it will find the test_login through the import as well as the test in the files we invoke on. And the order will be different, it will order the file alphabetically - so in case of test_answers it will first run that test, then test_login, but for test_schema the login test will run first.

WTF?

Renaming test_answers to test_manswers (sorted after login) confirms it, it then works.

But why does the order matter? Digger a bit deeper, we see that the value returned from test_login is in both cases of type generator. But Tornado is happy with one of them, but not the other. In the convert_yielded function (which among other things lets tornado also work with await/async generators), Tornado uses inspect.isawaitable to check if the passed generator can actually be a future. This is False when the test fails.

This is the code for isawaitable:

defisawaitable(object):"""Return true if object can be passed to an ``await`` expression."""return(isinstance(object,types.CoroutineType)orisinstance(object,types.GeneratorType)andbool(object.gi_code.co_flags&CO_ITERABLE_COROUTINE)orisinstance(object,collections.abc.Awaitable))

It’s the co_flags line that causes out problem - in the working case, the flag for being an iterable coroutine is set. co_flags is pretty deep in the python internals, containing a number of flags for the interpreter (the inspect docs has the full list). Our CO_ITERABLE_COROUTINE flag was added in in PEP492, which says that:

And there the function __code__ object is modified in place, setting the flag! Setting a breakpoint there lets us see that pytest-tornado calls tornado.gen.coroutine on our function, which in turn calls types.coroutine:

# On Python 3.5, set the coroutine flag on our generator, to allow it# to be used with 'await'.wrapped=funcifhasattr(types,'coroutine'):func=types.coroutine(func)

And this is how the test_login function only works if once called first as a pytest.

¯\_(ツ)_/¯

In the end, that’s the explanation, but there is no real solution - we cannot rely on having the tests in alphabetical order, so we move the reusable code out to it’s own function: