Kurt Peek Kurt Peek - 1 month ago 9
Python Question

How to start a server from the command line in a Python unittest

I'm building a tester for a Python script which performs works with a RethinkDB database. As part of the

setUp()
method, I'm trying to make the tester start up the RethinkDB server on
localhost
at port
28016
in case that has not yet been done.

I'm using
subprocess
to start the server. The problem is that, according to https://docs.python.org/2/library/subprocess.html,
subprocess
waits for the command to complete. In this case it seems that as long as the server is up and running, the process is not complete and testing does not continue beyond the
setUp()
stage.

Here is the script I'm trying:

import unittest
import rethinkdb as r
import subprocess

class TestController(unittest.TestCase):

HOST = "localhost"
PORT_OFFSET = 1
PORT = 28015 + PORT_OFFSET
DB = "ipercron"
TABLE = "sensor_data"


def setUp(self):
try:
self.conn = r.connect(self.HOST, self.PORT)
except r.ReqlDriverError:
print("The RethinkDB server is not yet ready. Starting it up...")
subprocess.call(["rethinkdb", "--port-offset", str(TestController.PORT_OFFSET)])
self.conn = r.connect(self.HOST, self.PORT)

if TestController.DB not in r.db_list().run(self.conn):
r.db_create(TestController.DB).run(self.conn)
self.conn.use(TestController.DB)

if TestController.TABLE not in r.table_list().run(self.conn):
r.table_create(TestController.TABLE).run(self.conn) # Create the table if it does not yet exist

r.table(TestController.TABLE).delete().run(self.conn) # Empty the table to start with a clean slate

def test_upper(self):
self.assertEqual('foo'.upper(), 'FOO')

suite = unittest.TestLoader().loadTestsFromTestCase(TestController)
unittest.TextTestRunner(verbosity=2).run(suite)


The
subprocess
is meant to perform the
rethinkdb --port-offset 1
command at the command line, and then continue with the script. However, when I run the script I get the usual message that the server is ready:

kurt@kurt-ThinkPad:~/dev/clones/ipercron-compose/controller$ python unittest_controller.py
test_upper (__main__.TestController) ... The RethinkDB server is not yet ready. Starting it up...
Running rethinkdb 2.3.5~0xenial (GCC 5.3.1)...
Running on Linux 4.4.0-42-generic x86_64
Loading data from directory /home/kurt/dev/clones/ipercron-compose/controller/rethinkdb_data
Listening for intracluster connections on port 29016
Listening for client driver connections on port 28016
Listening for administrative HTTP connections on port 8081
Listening on cluster addresses: 127.0.0.1, 127.0.1.1, ::1
Listening on driver addresses: 127.0.0.1, 127.0.1.1, ::1
Listening on http addresses: 127.0.0.1, 127.0.1.1, ::1
To fully expose RethinkDB on the network, bind to all addresses by running rethinkdb with the `--bind all` command line option.
Server ready, "kurt_ThinkPad_a0k" 07bb35f6-3a33-4e8b-9e9c-a78504457969


without any further action. How can I make the unittest proceed with the testing?

Answer

The problem is as you said, that subprocess.call is blocking and will wait for the command to finish. For scenarios where you need to spawn a child process without waiting for it to finish, you can use subprocess.Popen:

process = subprocess.Popen(["rethinkdb", "--port-offset", str(TestController.PORT_OFFSET)])

This gives you back a Popen object that provides a whole bunch of very useful methods to communicate with the child process. For example, you will probably want to use process.kill() in your unit test's tearDown() function to shut down your database.