1python-dbusmock 2=============== 3 4Purpose 5------- 6With this program/Python library you can easily create mock objects on D-Bus. 7This is useful for writing tests for software which talks to D-Bus services 8such as upower, systemd, logind, gnome-session or others, and it is hard 9(or impossible without root privileges) to set the state of the real services 10to what you expect in your tests. 11 12Suppose you want to write tests for gnome-settings-daemon's power plugin, or 13another program that talks to upower. You want to verify that after the 14configured idle time the program suspends the machine. So your program calls 15``org.freedesktop.UPower.Suspend()`` on the system D-Bus. 16 17Now, your test suite should not really talk to the actual system D-Bus and the 18real upower; a ``make check`` that suspends your machine will not be considered 19very friendly by most people, and if you want to run this in continuous 20integration test servers or package build environments, chances are that your 21process does not have the privilege to suspend, or there is no system bus or 22upower to begin with. Likewise, there is no way for an user process to 23forcefully set the system/seat idle flag in logind, so your 24tests cannot set up the expected test environment on the real daemon. 25 26That's where mock objects come into play: They look like the real API (or at 27least the parts that you actually need), but they do not actually do anything 28(or only some action that you specify yourself). You can configure their 29state, behaviour and responses as you like in your test, without making any 30assumptions about the real system status. 31 32When using a local system/session bus, you can do unit or integration testing 33without needing root privileges or disturbing a running system. The Python API 34offers some convenience functions like ``start_session_bus()`` and 35``start_system_bus()`` for this, in a ``DBusTestCase`` class (subclass of the 36standard ``unittest.TestCase``). 37 38You can use this with any programming language, as you can run the mocker as a 39normal program. The actual setup of the mock (adding objects, methods, 40properties, and signals) all happen via D-Bus methods on the 41``org.freedesktop.DBus.Mock`` interface. You just don't have the convenience 42D-Bus launch API that way. 43 44 45Simple example in Python 46------------------------ 47Picking up the above example about mocking upower's ``Suspend()`` method, this 48is how you would set up a mock upower in your test case: 49 50.. code-block:: python 51 52 import dbus 53 import dbusmock 54 55 class TestMyProgram(dbusmock.DBusTestCase): 56 @classmethod 57 def setUpClass(cls): 58 cls.start_system_bus() 59 cls.dbus_con = cls.get_dbus(system_bus=True) 60 61 def setUp(self): 62 self.p_mock = self.spawn_server('org.freedesktop.UPower', 63 '/org/freedesktop/UPower', 64 'org.freedesktop.UPower', 65 system_bus=True, 66 stdout=subprocess.PIPE) 67 68 # Get a proxy for the UPower object's Mock interface 69 self.dbus_upower_mock = dbus.Interface(self.dbus_con.get_object( 70 'org.freedesktop.UPower', '/org/freedesktop/UPower'), 71 dbusmock.MOCK_IFACE) 72 73 self.dbus_upower_mock.AddMethod('', 'Suspend', '', '', '') 74 75 def tearDown(self): 76 self.p_mock.stdout.close() 77 self.p_mock.terminate() 78 self.p_mock.wait() 79 80 def test_suspend_on_idle(self): 81 # run your program in a way that should trigger one suspend call 82 83 # now check the log that we got one Suspend() call 84 self.assertRegex(self.p_mock.stdout.readline(), b'^[0-9.]+ Suspend$') 85 86Let's walk through: 87 88 - We derive our tests from ``dbusmock.DBusTestCase`` instead of 89 ``unittest.TestCase`` directly, to make use of the convenience API to start 90 a local system bus. 91 92 - ``setUpClass()`` starts a local system bus, and makes a connection to it available 93 to all methods as ``dbus_con``. ``True`` means that we connect to the 94 system bus, not the session bus. We can use the same bus for all tests, so 95 doing this once in ``setUpClass()`` instead of ``setUp()`` is enough. 96 97 - ``setUp()`` spawns the mock D-Bus server process for an initial 98 ``/org/freedesktop/UPower`` object with an ``org.freedesktop.UPower`` D-Bus 99 interface on the system bus. We capture its stdout to be able to verify that 100 methods were called. 101 102 We then call ``org.freedesktop.DBus.Mock.AddMethod()`` to add a 103 ``Suspend()`` method to our new object to the default D-Bus interface. This 104 will not do anything (except log its call to stdout). It takes no input 105 arguments, returns nothing, and does not run any custom code. 106 107 - ``tearDown()`` stops our mock D-Bus server again. We do this so that each 108 test case has a fresh and clean upower instance, but of course you can also 109 set up everything in ``setUpClass()`` if tests do not interfere with each 110 other on setting up the mock. 111 112 - ``test_suspend_on_idle()`` is the actual test case. It needs to run your 113 program in a way that should trigger one suspend call. Your program will 114 try to call ``Suspend()``, but as that's now being served by our mock 115 instead of upower, there will not be any actual machine suspend. Our 116 mock process will log the method call together with a time stamp; you can 117 use the latter for doing timing related tests, but we just ignore it here. 118 119Simple example from shell 120------------------------- 121 122We use the actual session bus for this example. You can use 123``dbus-run-session`` to start a private one as well if you want, but that is 124not part of the actual mocking. 125 126So let's start a mock at the D-Bus name ``com.example.Foo`` with an initial 127"main" object on path /, with the main D-Bus interface 128``com.example.Foo.Manager``: 129 130:: 131 132 python3 -m dbusmock com.example.Foo / com.example.Foo.Manager 133 134On another terminal, let's first see what it does: 135 136:: 137 138 gdbus introspect --session -d com.example.Foo -o / 139 140You'll see that it supports the standard D-Bus ``Introspectable`` and 141``Properties`` interfaces, as well as the ``org.freedesktop.DBus.Mock`` 142interface for controlling the mock, but no "real" functionality yet. So let's 143add a method: 144 145:: 146 147 gdbus call --session -d com.example.Foo -o / -m org.freedesktop.DBus.Mock.AddMethod '' Ping '' '' '' 148 149Now you can see the new method in ``introspect``, and call it: 150 151:: 152 153 gdbus call --session -d com.example.Foo -o / -m com.example.Foo.Manager.Ping 154 155The mock process in the other terminal will log the method call with a time 156stamp, and you'll see something like ``1348832614.970 Ping``. 157 158Now add another method with two int arguments and a return value and call it: 159 160:: 161 162 gdbus call --session -d com.example.Foo -o / -m org.freedesktop.DBus.Mock.AddMethod \ 163 '' Add 'ii' 'i' 'ret = args[0] + args[1]' 164 gdbus call --session -d com.example.Foo -o / -m com.example.Foo.Manager.Add 2 3 165 166This will print ``(5,)`` as expected (remember that the return value is always 167a tuple), and again the mock process will log the Add method call. 168 169You can do the same operations in e. g. d-feet or any other D-Bus language 170binding. 171 172Logging 173------- 174Usually you want to verify which methods have been called on the mock with 175which arguments. There are three ways to do that: 176 177 - By default, the mock process writes the call log to stdout. 178 179 - You can call the mock process with the ``-l``/``--logfile`` argument, or 180 specify a log file object in the ``spawn_server()`` method if you are using 181 Python. 182 183 - You can use the ``GetCalls()``, ``GetMethodCalls()`` and ``ClearCalls()`` 184 methods on the ``org.freedesktop.DBus.Mock`` D-Bus interface to get an array 185 of tuples describing the calls. 186 187 188Templates 189--------- 190Some D-Bus services are commonly used in test suites, such as UPower or 191NetworkManager. python-dbusmock provides "templates" which set up the common 192structure of these services (their main objects, properties, and methods) so 193that you do not need to carry around this common code, and only need to set up 194the particular properties and specific D-Bus objects that you need. These 195templates can be parameterized for common customizations, and they can provide 196additional convenience methods on the ``org.freedesktop.DBus.Mock`` interface 197to provide more abstract functionality like "add a battery". 198 199For example, for starting a server with the "upower" template in Python you can 200run 201 202:: 203 204 (self.p_mock, self.obj_upower) = self.spawn_server_template( 205 'upower', {'OnBattery': True}, stdout=subprocess.PIPE) 206 207or load a template into an already running server with the ``AddTemplate()`` 208method; this is particularly useful if you are not using Python: 209 210:: 211 212 python3 -m dbusmock --system org.freedesktop.UPower /org/freedesktop/UPower org.freedesktop.UPower 213 214 gdbus call --system -d org.freedesktop.UPower -o /org/freedesktop/UPower -m org.freedesktop.DBus.Mock.AddTemplate 'upower' '{"OnBattery": <true>}' 215 216This creates all expected properties such as ``DaemonVersion``, and changes the 217default for one of them (``OnBattery``) through the (optional) parameters dict. 218 219If you do not need to specify parameters, you can do this in a simpler way with 220 221:: 222 223 python3 -m dbusmock --template upower 224 225The template does not create any devices by default. You can add some with 226the template's convenience methods like 227 228:: 229 230 ac_path = self.dbusmock.AddAC('mock_AC', 'Mock AC') 231 bt_path = self.dbusmock.AddChargingBattery('mock_BAT', 'Mock Battery', 30.0, 1200) 232 233or calling ``AddObject()`` yourself with the desired properties, of course. 234 235If you want to contribute a template, look at dbusmock/templates/upower.py for 236a real-life implementation. You can copy dbusmock/templates/SKELETON to your 237new template file name and replace "CHANGEME" with the actual code/values. 238 239 240More Examples 241------------- 242Have a look at the test suite for two real-live use cases: 243 244 - ``tests/test_upower.py`` simulates upowerd, in a more complete way than in 245 above example and using the ``upower`` template. It verifies that 246 ``upower --dump`` is convinced that it's talking to upower. 247 248 - ``tests/test_api.py`` runs a mock on the session bus and exercises all 249 available functionality, such as adding additional objects, properties, 250 multiple methods, input arguments, return values, code in methods, raising 251 signals, and introspection. 252 253 254Documentation 255------------- 256The ``dbusmock`` module has extensive documentation built in, which you can 257read with e. g. ``pydoc3 dbusmock``. 258 259``pydoc3 dbusmock.DBusMockObject`` shows the D-Bus API of the mock object, 260i. e. methods like ``AddObject()``, ``AddMethod()`` etc. which are used to set 261up your mock object. 262 263``pydoc3 dbusmock.DBusTestCase`` shows the convenience Python API for writing 264test cases with local private session/system buses and launching the server. 265 266``pydoc3 dbusmock.templates`` shows all available templates. 267 268``pydoc3 dbusmock.templates.NAME`` shows the documentation and available 269parameters for the ``NAME`` template. 270 271``python3 -m dbusmock --help`` shows the arguments and options for running the 272mock server as a program. 273 274 275Development 276----------- 277python-dbusmock is hosted on github: 278 279 https://github.com/martinpitt/python-dbusmock 280 281Run the unit tests with 282 283 python3 -m unittest 284