openSUSE:WebYaST Testing/PackageKit

Jump to: navigation, search

Writing tests involving PackageKit

Following the Don't access the system conventions, here's how to simulate PackageKit calls when writing WebYaST tests.

About PackageKit

PackageKit is a D-Bus service offering package and repository management. With the help of PolicyKit it allows unprivileged users to install software and updates.

PackageKit provides two interfaces, namely "org.freedesktop.PackageKit" for synchronous and "org.freedesktop.PackageKit.Transaction" for asynchronous operations. The former is used for PackageKit internal functions, the latter passes requests to the PackageKit backend and reports back through signals.

Mocking PackageKit

Mocking the synchronous calls is easy, the same rules as for standard D-Bus services apply.

The tricky part is the asynchronous interface. It uses the D-Bus signal mechanism for results. There are normal (depending on the function), error, and finish signals to be coped with.

A signal has a name (identifying the signal) and a signature. The latter describes the signal properties and their types.

A typical signal would be "Package" with 'info', 'id' and 'summary' properties.

When mocking a PackageKit function, one has to know (and mock) the function itself, including its signature and the reporting signal.

Testing with PackageKit

For tests involving PackageKit, the software webservice plugin provides a packagekit_stub.rb helper file below its test/ directory. (You shouldn't need PackageKit anywhere outside this plugin !)

After including the test_helper, add

 require File.join(File.dirname(__FILE__), "..", "packagekit_stub")

to your test file.

This allows you to create a PackageKit stub object in your tests 'setup' function

   @pk_stub = PackageKitStub.new

Then create transaction interface and proxy stubs with

   @transaction, @packagekit = PackageKit.connect

Mocking a specific function

As outlined above, you need to know the function and its result signal. Function mocking for transactions is a bit more complicated since its asynchronous nature requires to separate the function call from the result signal handler.

So we need to mock the called function and mock the resulting signals.

The function

As an example lets take 'SearchName'.

Search name takes two incoming string arguments, the repositories to be searched (where) and the package name (what).

   m = DBus::Method.new("SearchName")
   m.from_prototype("in repos:s, in name:s")

(See here for signature syntax and here for types)

Now the function needs to be added to the transaction interface

   transaction.methods[m.name] = m

and we need a dummy implementation

   class <<transaction
     def SearchName repos, name
       # dummy !
     end
   end

The result signals

'SearchName' reports results through the "Package" signal, passing three string properties info, id, and summary.

Mocking of results is supported by the PackageKitResultSet class. An instance is created by passing the signal name and its signature.

   rset = PackageKitResultSet.new "Package", :info => :s, :id => :s, :summary => :s

(Since a trailing Hash can be passed without enclosing {}'s in Ruby, the signature looks like multiple parameters)

Adding result signals is done via '<<', grouping the signal properties in an Array

   rset << ["info1", "id1", "summary1"]

A string-type property can also be specified as symbol

   rset << [:info2, :id2, :summary2]

Now we pass this PackageKitResultSet instance to the stub object.

   @pk_stub.result = rset

(!!! This assignment creates and queues the signals on the D-Bus. The next call to a transaction will receive these signals.)

Calling a mocked function

Once you have mocked the function and the resulting signal, the actual function call is indistinguishable from the real call (when using WebYaSTs PackageKit class)

For the above 'SearchName' example, it goes like this

  PackageKit.transact( "SearchName", ["installed;~devel", "yast2"], "Package") do |info,id,summary|
    ... do something with the result ...
  end

Here we're calling 'SearchName' through the transaction interface, passing ["installed;~devel", "yast2"] and listening for "Package" signals. Each "Package" signal is expected to have three properties which are available as info, id, and summary within the Ruby block.