openSUSE:WebYaST Testing/PackageKit
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.