Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to write a functional test for a DBUS service written in Python?

(Title was: "How to write a unit test for a DBUS service written in Python?")

I've started to write a DBUS service using dbus-python, but I'm having trouble writing a test case for it.

Here is an example of the test I am trying to create. Notice that I have put a GLib event loop in the setUp(), this is where the problem hits:

import unittest

import gobject
import dbus
import dbus.service
import dbus.glib

class MyDBUSService(dbus.service.Object):
    def __init__(self):
        bus_name = dbus.service.BusName('test.helloservice', bus = dbus.SessionBus())
        dbus.service.Object.__init__(self, bus_name, '/test/helloservice')

    @dbus.service.method('test.helloservice')
    def hello(self):
        return "Hello World!"


class BaseTestCase(unittest.TestCase):

    def setUp(self):
        myservice = MyDBUSService()
        loop = gobject.MainLoop()
        loop.run()
        # === Test blocks here ===

    def testHelloService(self):
        bus = dbus.SessionBus()
        helloservice = bus.get_object('test.helloservice', '/test/helloservice')
        hello = helloservice.get_dbus_method('hello', 'test.helloservice')
        assert hello() == "Hello World!"

if __name__ == '__main__':
    unittest.main()

My problem is that the DBUS implementation requires you to start an event loop so that it can start dispatching events. The common approach is to use GLib's gobject.MainLoop().start() (although I'm not married to this approach, if someone has a better suggestion). If you don't start an event loop, the service still blocks, and you also cannot query it.

If I start my service in the test, the event loop blocks the test from completing. I know the service is working because I can query the service externally using the qdbus tool, but I can't automate this inside the test that starts it.

I'm considering doing some kind of process forking inside the test to handle this, but I was hoping someone might have a neater solution, or at least a good starting place for how I would write a test like this.

like image 594
seanhodges Avatar asked Feb 04 '09 10:02

seanhodges


1 Answers

With some help from Ali A's post, I have managed to solve my problem. The blocking event loop needed to be launched into a separate process, so that it can listen for events without blocking the test.

Please be aware my question title contained some incorrect terminology, I was trying to write a functional test, as opposed to a unit test. I was aware of the distinction, but didn't realise my mistake until later.

I've adjusted the example in my question. It loosely resembles the "test_pidavim.py" example, but uses an import for "dbus.glib" to handle the glib loop dependencies instead of coding in all the DBusGMainLoop stuff:

import unittest

import os
import sys
import subprocess
import time

import dbus
import dbus.service
import dbus.glib
import gobject

class MyDBUSService(dbus.service.Object):

    def __init__(self):
        bus_name = dbus.service.BusName('test.helloservice', bus = dbus.SessionBus())
        dbus.service.Object.__init__(self, bus_name, '/test/helloservice')

    def listen(self):
        loop = gobject.MainLoop()
        loop.run()

    @dbus.service.method('test.helloservice')
    def hello(self):
        return "Hello World!"


class BaseTestCase(unittest.TestCase):

    def setUp(self):
        env = os.environ.copy()
        self.p = subprocess.Popen(['python', './dbus_practice.py', 'server'], env=env)
        # Wait for the service to become available
        time.sleep(1)
        assert self.p.stdout == None
        assert self.p.stderr == None

    def testHelloService(self):
        bus = dbus.SessionBus()
        helloservice = bus.get_object('test.helloservice', '/test/helloservice')
        hello = helloservice.get_dbus_method('hello', 'test.helloservice')
        assert hello() == "Hello World!"

    def tearDown(self):
        # terminate() not supported in Python 2.5
        #self.p.terminate()
        os.kill(self.p.pid, 15)

if __name__ == '__main__':

    arg = ""
    if len(sys.argv) > 1:
        arg = sys.argv[1]

    if arg == "server":
        myservice = MyDBUSService()
        myservice.listen()

    else:
        unittest.main()
like image 148
seanhodges Avatar answered Nov 15 '22 16:11

seanhodges