Deadlock: BDD testing a python Tornado app with py.test and Splinter

I’m only a day into my new asynchronous web development project in Tornado, and I already have a deadlock. And this is remarkable, I think, because all I have done so far is a copy-and-paste the Hello World example app from the Tornado website. An example which “does not use any of Tornado’s asynchronous features”. So, how did I manage to get deadlock?

Well, on top of Hello World, I have a test. A functional test, written with pytest-bdd that uses pytest-splinter makes sure here is some sort of web server up and running. Now pytest-splinter provides a browser object as a py.test fixture. Tests can use this to access the site, so
long as a server is running. Since I’m shopping for py.test plugins, I added pytest-tornado whose http_server fixture starts a tornado HTTP server, serving the application I provide by defining an app fixture.

All set to go? I thought I was. But I got the big crunch. My splinter browser starts but no page loads. What’s up? Turns out the tests aren’t asynchronous. The splinter driver is blocking and the asynchronous web server never gets a look-in to serve the content. Pytest-tornado wants me
to write tests using Tornado’s asynchronous http client, so it and the server can play nicely together.

That’s all fair, but I want to use Splinter on this project so I’m going to want to start my server in a thread. I created an apprunner module that can start my Tornado app in a thread (and return the url to access it). And also provides a function to instruct the server to stop, and exit the
thread:

import threading
import tornado
from .application import Application


ioloop = tornado.ioloop.IOLoop.instance()


def start(TESTING_PORT=8192):
    """ Start Application runing on given port. Retrn the listening url"""
    global thread
    app = Application()
    app.listen(TESTING_PORT)
    thread = threading.Thread(target=ioloop.start)
    thread.start()
    return "http://localhost:{}".format(TESTING_PORT)


def stop():
    """ Ask IOLoop to stop running, and wait for its thread to complete"""
    global thread
    ioloop.add_callback(ioloop.stop)
    thread.join()

Then I define my own fixture to start and stop the server using pytest
yield_fixture:

@pytest.yield_fixture
def app():
    yield start()
    stop()

And now I can write lovely clean BDD steps using splinter browser and my
own app running in a thread, like this:

@when("I visit the site")
def visit_site(browser, app):
    browser.visit(app)