{"id":49,"date":"2006-06-19T19:32:33","date_gmt":"2006-06-19T17:32:33","guid":{"rendered":"https:\/\/raphael.slinckx.net\/blog\/projects\/dbus-testing-framework\/"},"modified":"2006-06-19T22:09:58","modified_gmt":"2006-06-19T20:09:58","slug":"dbus-testing-framework-tutorial","status":"publish","type":"page","link":"https:\/\/raphael.slinckx.net\/blog\/documents\/dbus-testing-framework-tutorial","title":{"rendered":"DBus Testing Framework Tutorial"},"content":{"rendered":"<h2>Goals<\/h2>\n<p>The main aim should be that its easy to write a linear test script<\/p>\n<p>Allowing to interact with other automated frameworks like dogtail is also a plus<\/p>\n<h2>Tutorial<\/h2>\n<p>This little tutorial will introduce you to the concets and API of the dbus testing framework without<br \/>\n\tentering too much in the gory details<\/p>\n<h3>General concepts<\/h3>\n<p>The framework is based on two things, the <em>Test case<\/em> and the <em>proxy<\/em> objects.<\/p>\n<h4>Test Cases<\/h4>\n<p>Each test case you want to make is a method of a class. More precisely it&#8217;s the <code>run(self):<\/code> method<br \/>\n\tof a <code>TestCase<\/code> subclass. To create a test case just define a new class, override the run method<br \/>\n\tand pass it to the <code>run(cases)<\/code> method of the framework, like that:<\/p>\n<pre>\r\nclass MyTestCase(TestCase):\r\n\tdef run(self):\r\n\t\t[Execute test script here]\r\n\r\nrun(MyTestCase)\r\n<\/pre>\n<p>You can also pass a list of test cases to the <code>run(cases)<\/code> method. That way, you can select<br \/>\n\twhich test case you want to run from the command line. Let&#8217;s say you defined FooTest, BarTest and BazTest<br \/>\n\tin a file names <code>mytests.py<\/code>.<\/p>\n<pre>\r\nclass FooTest(TestCase):\r\n\t[]\r\nclass BarTest(TestCase):\r\n\t[]\r\nclass BazTest(TestCase):\r\n\t[]\r\n\r\nrun([FooTest, BarTest, BazTest])\r\n<\/pre>\n<p>You can run in the console:<\/p>\n<pre>\r\n$ python mytests.py FooTest\r\n-> execute the FooTest test case\r\n<\/pre>\n<h4>Proxy objects<\/h4>\n<p>The framework comes with 3 types of pre-made TestCase subclasses each filling a specific testing need.<\/p>\n<ul>\n<li><code>ClientTestCase<\/code>: When you want to test a servie behavior (the test script emulates a client)<\/li>\n<li><code>ServiceTestCase<\/code>: When you want to test a client behavior (the test script emulates a dbus service)<\/li>\n<li><code>MixedTestCase<\/code>: When you want to test both a service and a client (possibly the same) behavior (the test script<br \/>\n\t\t  emulates a client and\/or a dbus service)<\/li>\n<\/ul>\n<p>So we can combine the two points seen above, and say that test cases must be subclasses of <code>{Client,Service,Mixed}TestCase<\/code><br \/>\n\tand must implement a <code>run(self)<\/code> method. These classes should then be passed to the <code>run(cases)<\/code> function.<\/p>\n<p>Let&#8217;s see more in detail the API of each possible working mode&#8230;<\/p>\n<h3>Client-side scripting<\/h3>\n<p>Client-side scripting takes you in the pants of a client and allows you to make calls on given dbus objects, and<br \/>\n\tlisten for signals emitted by these objects (and thus assert they happen at the right moment)<\/p>\n<p>First thing to do is to tell the framework where he can find the actual remote dbus object, by giving the<br \/>\n\tservice name, the remote object path and the interfaces implemented by the object. This is all usual Dbus jargon.<\/p>\n<p>First simple example, creates under nickname &#8220;nick&#8221; the given remote objet proxy.<\/p>\n<pre>\r\nself[\"nick\"] = (\"org.foo.bar\", \"\/org\/foo\/bar\", \"org.foo.IBar\")\r\n<\/pre>\n<p>If the remote object implements more than one interface, you must use a second definition format. This one<br \/>\n\t creates under nickname &#8220;nick&#8221; the given remote object proxy which implement two interfaces, IBar and IBaz.<br \/>\n\t Each interface has its own nickname (&#8220;iface1&#8221; and &#8220;iface2&#8221;).<\/p>\n<pre>\r\nself[\"nick\"] = (\"org.foo.bar\", \"\/org\/foo\/bar\",\r\n\t{\"iface1\": \"org.foo.IBar\", \"iface2\": \"org.foo.IBaz\"})\r\n<\/pre>\n<p>Now that we have a nickname to refer to our remote object we can access its methods and signal with the python dict mapping<\/p>\n<pre>\r\nself[\"nick\"][\"Method\"].xxx\r\nself[\"nick\"][\"Signal\"].xxx\r\n\r\n# If there is more than one interface:\r\nself[\"nick\"][\"iface1\"][\"Method\"].xxx\r\nself[\"nick\"][\"iface1\"][\"Signal\"].xxx\r\n<\/pre>\n<p>On each method or signal proxy object returned by the dictionnary, we can do several actions. If the proxy is a method:<\/p>\n<ul>\n<li>\n\t\t<code>call(self, *args, **kwargs) : result<\/code><\/p>\n<p>Call the method and block until the method returns. Returns the method return value. You can use the timeout=secs keyword<br \/>\n\t\targument to specify how much time the call should wait before failure. If not specified the default is used.<\/p>\n<\/li>\n<\/ul>\n<p>If the proxy is a signal:<\/p>\n<ul>\n<li><code>listen(self) : void<\/code>\n<p>Start listening to the signal (resets previous signal values)<\/p>\n<\/li>\n<li><code>wait(self, n=1, timeout=DEFAULT_TIMEOUT) : result<\/code>\n<p>Block until the signal has been received n times. Wait at most timeout seconds. The return value is the signal parameters, or a list<br \/>\n\t\tof signal parameters if n > 1, ordered by reception time.<\/p>\n<\/li>\n<\/ul>\n<h3>Service-side scripting<\/h3>\n<p>Service-side scripting takes you in the pants of a service and allows you to ensure the right calls sequences<br \/>\n\tare made against the exported objects, and emit signals from exported objects. You will be able to test<br \/>\n\tthat way that clients are behaving correctly to service events.<\/p>\n<p>First thing to do is to tell the framework what service he needs to export, by giving the<br \/>\n\tservice name, the remote object path and the methods\/signals exported by the object. This is all usual Dbus jargon.<\/p>\n<p>To acheive this you first need to define a class, subclassing MockService, in which you declare the exported methods and signals,<br \/>\n\tas you would do when using the dbus bindings in python, plus some more details<\/p>\n<pre>\r\nclass FooBarService(MockService):\r\n\t@intercept\r\n\t@dbus.service.method(\"org.foo.bar.IBaz\")\r\n\tdef Foo(self):\r\n\t\treturn \"Foo\"\r\n\r\n\t@intercept\r\n\t@dbus.service.method(\"org.foo.bar.IBaz\")\r\n\tdef Bar(self, param):\r\n\t\treturn param+3\r\n\r\n\t@dbus.service.signal(\"org.foo.bar.IBaz\")\r\n\tdef BazSig(self, s):\r\n\t\tpass\r\n<\/pre>\n<p>As you can see, each method is declared and a default implementation is provided. Also note that the <code>@intercept<\/code> annotation<br \/>\n\thas to be used for <em>methods<\/em> (not for signals or things break) and <em>after<\/em> the @method annotation (which means <em>before<\/em><br \/>\n\tin source code)<\/p>\n<p>Now we can create the service proxy object with a nickname as for the client-side scripting:<\/p>\n<pre>\r\nself[\"nick\"] = (FooBarService, \"org.foo.bar\", \"\/org\/foo\/bar\")\r\n<\/pre>\n<p>We pass as first argument the class of the exported object, the service name and object path<\/p>\n<p>Now that we have a nickname to refer to our service we can access its methods and signal with the python dict mapping<\/p>\n<pre>\r\nself[\"nick\"][\"Method\"].xxx\r\nself[\"nick\"][\"Signal\"].xxx\r\n<\/pre>\n<p>On each method or signal proxy object returned by the dictionnary, we can do several actions. If the proxy is a signal:<\/p>\n<ul>\n<li>\n\t\t<code>emit(self, *args, **kwargs) : void<\/code><\/p>\n<p>Emit the signal with the given arguments.<\/p>\n<\/li>\n<\/ul>\n<p>If the proxy is a method:<\/p>\n<ul>\n<li><code>listen_call(self, return_val=None) : void<\/code>\n<p>Start listening to a method call on this service (resets previous calls values). If the return_val is given an expression,<br \/>\n\t\tthe expression will be called with the parameters of the method call and its return value will be used instead of the<br \/>\n\t\tdefault method implementation as given in the MockService subclass<\/p>\n<pre>\r\n# When Method is called by a remote client, the return value will be the tuple (args, kwargs)\r\n# returned by the lambda expression instead of the default return value given in the MockService.\r\nself[\"nick\"][\"Method\"].listen_call(lambda *args, **kwargs: return args, kwargs)\r\n<\/pre>\n<\/li>\n<li><code>wait_call(self, listening=False, return_val=None, timeout=DEFAULT_TIMEOUT): result<\/code>\n<p>Block until the method call happens. If listening is False, then listen_call(return_val) is implicitely called. Else it is assumed<br \/>\n\t\tthat you have already called listen_call() yourself before. Wait at most timeout seconds. The return value are the arguments provided<br \/>\n\t\tto the method when it was called.<\/p>\n<\/li>\n<\/ul>\n<h3>Mixed type scripting<\/h3>\n<p>Mixed-type scripting allows one to use both service-side and client-side features of the testing framework. Use this<br \/>\n\twhen you need clients and service at the same time in a script, that possibly needs to call each other or interact in some ways.<\/p>\n<p>To create the service or client proxies, the syntax is the same as defined above for each type of proxies. One additional syntax<br \/>\n\tis available to defined in one step both a service and a client connected to it, if that&#8217;s useful:<\/p>\n<pre>\r\nself[\"nick\"] = (FooBarService, \"org.foo.bar\", \"\/org\/foo\/bar\", \"org.foo.bar.IBaz\")\r\n# or for the multi interface client\r\nself[\"nick\"] = (FooBarService, \"org.foo.bar\", \"\/org\/foo\/bar\",\r\n\t\t{\"iface1\": \"org.foo.bar.IBaz\", \"iface2\": \"org.foo.bar.IBaz2\"})\r\n<\/pre>\n<p>Once you have the nicknamed proxy, you can use it like defined above, depending on the type of the proxy under the nickname.<\/p>\n<pre>\r\nself[\"nick\"][\"Method\"].xxx\r\nself[\"nick\"][\"Signal\"].xxx\r\n<\/pre>\n<p>Where xxx depends on the type of the proxy. If the proxy is a mixed one, then you can use both client and service api on it<\/p>\n<h2>Examples<\/h2>\n<p>The following examples will guide you &#8220;visually&#8221; through the usage of this testing framework.<br \/>\n\t The snippets have to be enclosed in a run(self) method of a TestCase subclass to be chosen among the<br \/>\n\t available ones (ClientTestCase, ServiceTestCase, MixedTestCase)<\/p>\n<h3>Simple calls\/signals<\/h3>\n<p>Call this method, expect this reply<\/p>\n<pre>\r\nself[\"foo\"] = (\"org.freedesktop.Foo\",\"\/org\/freedesktop\/Foo\",\"org.freedesktop.Foo\")\r\nret = self[\"foo\"][\"Half\"].call(32)\r\nassert ret == 16\r\n<\/pre>\n<p>Call this method, wait for this signal<\/p>\n<pre>\r\nself[\"foo\"] = (\"org.freedesktop.Foo\",\"\/org\/freedesktop\/Foo\",\"org.freedesktop.Foo\")\r\nself[\"foo\"][\"HalfComputed\"].listen()\r\nself[\"foo\"][\"Half\"].call(32)\r\nret = self[\"foo\"][\"HalfComputed\"].wait()\r\nassert ret == 16\r\n<\/pre>\n<h3>Hello World examples<\/h3>\n<p>A simple client test for an hypothetic HelloWorld service<\/p>\n<pre>\r\nself[\"hello\"] = (\"org.freedesktop.HelloWorld\",\r\n\t\t\"\/org\/freedesktop\/HelloWorldObject\",\r\n\t\t\"org.freedesktop.HelloWorldIFace\")\r\n\r\necho = self[\"hello\"][\"Hello\"].call()\r\nassert echo == \"olleH\"\r\n\r\nself[\"hello\"].HalloSig.listen()\r\necho = self[\"hello\"][\"Hallo\"].call(\"echomethis\")\r\nassert echo == \"echomethis\"\r\nsigs = self[\"hello\"][\"HalloSig\"].wait()\r\nassert sigs[0] == \"echomethis\"\r\n\r\nself[\"hello\"][\"HalloSig\"].listen()\r\nsigs = self[\"hello\"][\"HalloSig\"].wait()\r\nassert sigs[0] == \"test\"\r\n<\/pre>\n<p>The same test, but on the service side for testing with an hypothetic HelloWorld Client. First the Service definition:<\/p>\n<pre>\r\nclass HelloService(MockService):\r\n\t@intercept\r\n\t@dbus.service.method(\"org.freedesktop.HelloWorldIFace\")\r\n\tdef Hello(self):\r\n\t\treturn \"olleH\"\r\n\r\n\t@intercept\r\n\t@dbus.service.method(\"org.freedesktop.HelloWorldIFace\")\r\n\tdef HalloOverriden(self, param):\r\n\t\treturn param\r\n\r\n\t@intercept\r\n\t@dbus.service.method(\"org.freedesktop.HelloWorldIFace\")\r\n\tdef Hallo(self, param):\r\n\t\tself.HalloSig(param)\r\n\t\treturn param\r\n\r\n\t@dbus.service.signal(\"org.freedesktop.HelloWorldIFace\")\r\n\tdef HalloSig(self, s):\r\n\t\tpass\r\n<\/pre>\n<p>Then the actual script:<\/p>\n<pre>\r\nself[\"hello\"] = (HelloService,\r\n\t\t\"org.freedesktop.HelloWorld\",\r\n\t\t\"\/org\/freedesktop\/HelloWorldObject\")\r\n\r\nself[\"hello\"][\"Hello\"].wait_call()\r\nself[\"hello\"][\"Hallo\"].wait_call()\r\nself[\"hello\"][\"HalloSig\"].emit(\"test\")\r\n<\/pre>\n<p>You can of course launch the two tests in separate processes and see that they behave correctly<\/p>\n<p>And finally more or less the same test with mixed scripting. Both the service and the client are in the script. This one<br \/>\n\tcan run standalone since the service and object are both provided.<\/p>\n<pre>\r\nself[\"hello\"] = (HelloService,\r\n\t\"org.freedesktop.HelloWorld\",\r\n\t\"\/org\/freedesktop\/HelloWorldObject\",\r\n\t\"org.freedesktop.HelloWorldIFace\")\r\n\r\nself[\"hello\"][\"Hello\"].listen_call()\r\necho = self[\"hello\"][\"Hello\"].call()\r\nself[\"hello\"][\"Hello\"].wait_call(True)\r\nassert echo == \"olleH\"\r\n\r\nself[\"hello\"][\"HalloSig\"].listen()\r\necho = self[\"hello\"][\"Hallo\"].call(\"echomethis\")\r\nassert echo == \"echomethis\"\r\n\r\nsigs = self[\"hello\"][\"HalloSig\"].wait()\r\nassert sigs[0] == \"echomethis\"\r\n\r\nself[\"hello\"][\"HalloSig\"].listen()\r\nself[\"hello\"][\"HalloSig\"].emit(\"test\")\r\nsigs = self[\"hello\"][\"HalloSig\"].wait()\r\nassert sigs[0] == \"test\"\r\n\r\nself[\"hello\"][\"HalloOverriden\"].listen_call(return_val=lambda x: x[1:-1])\r\necho = self[\"hello\"][\"HalloOverriden\"].call(\"TestString\")\r\nself[\"hello\"][\"HalloOverriden\"].wait_call(True)\r\nassert echo == \"estStrin\"\r\n<\/pre>\n<h3>Libnotify example<\/h3>\n<p>Open a notification bubble with no timeout, close it programmatically, and check that the Closed signal is emitted<\/p>\n<pre>\r\nself[\"notif\"] = (\"org.freedesktop.Notifications\",\r\n\t\t\"\/org\/freedesktop\/Notifications\",\r\n\t\t\"org.freedesktop.Notifications\")\r\n\r\nid = self[\"notif\"][\"Notify\"].call(\"notifname\", 0, \"\", \"title\", \"content\",[],{}, 0)\r\n\r\nself[\"notif\"][\"NotificationClosed\"].listen()\r\nself[\"notif\"][\"CloseNotification\"].call(id)\r\nresults = self[\"notif\"][\"NotificationClosed\"].wait()\r\n\r\nassert results[0] == id\r\n<\/pre>\n<h3>Advanced cases<\/h3>\n<p>Log on to two jabber accounts, talk from one to other account, check all<br \/>\n\t   parameters are correct and that the messages are well exchanged, Disconnect.<\/p>\n<pre>\r\ndef start_manager(self, nick):\r\n\tself[nick] = (\"org.freedesktop.Telepathy.ConnectionManager.gabble\",\r\n\t\t\"\/org\/freedesktop\/Telepathy\/ConnectionManager\/gabble\",\r\n\t\t\"org.freedesktop.Telepathy.ConnectionManager\")\r\n\r\ndef connect(self, nick, proto, params):\r\n\t# Connect to the manager\r\n\tconn, obj = self[nick][\"Connect\"].call(proto, params)\r\n\t# register connection as self.nick_conn\r\n\tself[nick+\"_conn\"] = (conn, obj, \"org.freedesktop.Telepathy.Connection\")\r\n\r\n\t# Retrieve connection status, and wait until connected\r\n\tstatus = self[nick+\"_conn\"][\"GetStatus\"].call()\r\n\twhile status != 0:\r\n\t\tif status == 2:\r\n\t\t\traise Exception(\"Could not connect\")\r\n\t\tself[nick+\"_conn\"][\"StatusChanged\"].listen()\r\n\t\tstatus, reason = self[nick+\"_conn\"][\"StatusChanged\"].wait()\r\n\r\n\treturn conn\r\n\r\ndef run(self):\r\n\t# Test Setup\r\n\tself.start_manager(\"client1\")\r\n\tself.start_manager(\"client2\")\r\n\r\n\t# Connect the two accounts\r\n\tconn1 = self.connect(\"client1\", \"jabber\",\r\n\t\t{\"account\": \"kikidonk@amessage.be\", \"password\": \"kikidonk\"})\r\n\tconn2 = self.connect(\"client2\", \"google-talk\",\r\n\t\t{ \"account\": \"tryggve.tryggvason@gmail.com\",\"password\": \"badger\"})\r\n\r\n\t# Start listening to NewChannel signal emitted when client2 starts to talk to client1\r\n\tself[\"client1_conn\"][\"NewChannel\"].listen()\r\n\r\n\t# Create a text channel\r\n\tself[\"client2_chan\"] = (\r\n\t\t# The connection service\r\n\t\tconn2,\r\n\t\t# A new text channel to the client 1\r\n\t\tself[\"client2_conn\"][\"RequestChannel\"].call(\r\n\t\t\t\"org.freedesktop.Telepathy.Channel.Type.Text\", 1,\r\n\t\t\tself[\"client2_conn\"][\"RequestHandle\"].call (1, \"kikidonk@amessage.be\"), False),\r\n\t\t# The interfaces for this text channel\r\n\t\t{\"text\": \"org.freedesktop.Telepathy.Channel.Type.Text\",\r\n\t\t \"chan\": \"org.freedesktop.Telepathy.Channel\"})\r\n\r\n\t# Send a test message to client 1 from client 2\r\n\tself[\"client2_chan\"][\"text\"][\"Send\"].call (0, \"Test\")\r\n\r\n\t# We got a newchannel from client 1\r\n\tobj, channel_type, handle_type, handle, suppress_handler = self[\"client1_conn\"][\"NewChannel\"].wait()\r\n\r\n\tassert channel_type == \"org.freedesktop.Telepathy.Channel.Type.Text\"\r\n\tassert handle_type == 1\r\n\tassert suppress_handler == False\r\n\r\n\t# Create the channel structure for client 1\r\n\tself[\"client1_chan\"] = (\r\n\t\tconn1,\r\n\t\tobj,\r\n\t\t{\"text\": \"org.freedesktop.Telepathy.Channel.Type.Text\",\r\n\t\t \"chan\": \"org.freedesktop.Telepathy.Channel\"})\r\n\r\n\t# Retreive pending messages\r\n\tmessages = self[\"client1_chan\"][\"text\"][\"ListPendingMessages\"].call()\r\n\r\n\tid, stamp, from_handle, msg_type, string = messages[0]\r\n\tassert len(messages) == 1\r\n\tassert from_handle == handle\r\n\tassert msg_type == 0\r\n\tassert string == \"Test\"\r\n\r\n\t# Close\/Disconnect stuff\r\n\tself[\"client2_chan\"][\"chan\"][\"Close\"].call()\r\n\tself[\"client1_chan\"][\"chan\"][\"Close\"].call()\r\n\tself[\"client2_conn\"][\"Disconnect\"].call()\r\n\tself[\"client1_conn\"][\"Disconnect\"].call()\r\n<\/pre>\n<p>Log on to two jabber accounts, add yourself to your buddy list,<br \/>\n\t   check you got that on the other end, allow it. Remove yourself, check you got that.<br \/>\n\t   Add yourself again, reject it, check you got that. etc<\/p>\n<pre>\r\nTo be written\r\n<\/pre>\n<p>Like in telepathy, there&#8217;s a voip-engine which is both a service and a client of a connection manager<br \/>\n\t   I want to be able to pretend to be a client, and call handle channel,<br \/>\n\t   then pretend to be a service, send out stuff, test that the right things<br \/>\n\t   are returned, test that prerequisite calls get made.<\/p>\n<pre>\r\nTo be written\r\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Goals The main aim should be that its easy to write a linear test script Allowing to interact with other automated frameworks like dogtail is also a plus Tutorial This little tutorial will introduce you to the concets and API of the dbus testing framework without entering too much in the gory details General concepts [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":47,"menu_order":0,"comment_status":"open","ping_status":"open","template":"page-comments.php","meta":{"footnotes":""},"class_list":["post-49","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/raphael.slinckx.net\/blog\/wp-json\/wp\/v2\/pages\/49","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/raphael.slinckx.net\/blog\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/raphael.slinckx.net\/blog\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/raphael.slinckx.net\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/raphael.slinckx.net\/blog\/wp-json\/wp\/v2\/comments?post=49"}],"version-history":[{"count":0,"href":"https:\/\/raphael.slinckx.net\/blog\/wp-json\/wp\/v2\/pages\/49\/revisions"}],"up":[{"embeddable":true,"href":"https:\/\/raphael.slinckx.net\/blog\/wp-json\/wp\/v2\/pages\/47"}],"wp:attachment":[{"href":"https:\/\/raphael.slinckx.net\/blog\/wp-json\/wp\/v2\/media?parent=49"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}