Like many KDE application, strigidaemon uses DBus to talk to other programs. Debugging inter-process communication is never very convenient and strigidaemon is no exception. So far, there are no unit tests for checking the quality of the DBus communication in Strigi. I set about to write some and found it was not so easy, so I’m documenting what I did for the benefit of all the other developers using DBus.
Here’s a summary of what is needed to start debugging DBus communication. In the tests, we will start a private dbus-daemon for handling the communication between client and server. In our examples, strigidaemon is the server and we test by sending messages from the client (libstrigiqtdbus) to the server. We will be debugging the code that has not been installed, but resides in the build directory, since this is the normal situation for unit tests.
Environment variables like PATH
,
LD_LIBRARY_PATH
, XDG_DATA_DIRS
,
KDEDIRS
all point to your installed software. We will clear
them all and use absolute paths to make sure we are debugging the right
version of our code. I do not want the unit tests to go wild with my
production strigi index!
Clearing the environment can be done by calling
unsetenv()
for each environment variable. For strigidaemon,
we still need HOME to be defined at the moment, so we do not clear that
variable.
You can start as many dbus-daemons as you like with
dbus-launch
. In the unit tests, we start
dbus-launch
with QProcess. dbus-launch
launches dbus-daemon
and returns immediately. This will
print something like this:
DBUS_SESSION_BUS_ADDRESS=unix:abstract=/tmp/dbus-gK3yfCY77n,guid=41b8da09fac5220821e05b00475f0d32 DBUS_SESSION_BUS_PID=6926
These two variables tell your applications how to talk to the
dbus-daemon
. So we read the output from
dbus-launch
and pass the these variables in the environment
of your unit test process. Since the dbus-daemon
has
detached from dbus-launch
, we need to remember its PID so
we can stop our private DBus daemon it after we have finished
testing.
We are starting strigidaemon
. Because the unit test
process has the right environment variables for talking to the daemon,
strigidaemon
can also do this. After starting
strigidaemon
we give it one second to become responsive to
client calls.
Now everything is set up to start testing. We have two processes
running: dbus-daemon
and strigidaemon
. The API
for doing the DBus calls is provided by the library
libstrigiqtdbus
. You could also use introspection to figure
the API out, though. QtDBus picks up the connection settings for the
private DBus daemon when you ask it for a session connection
(QDBusConnection::sessionBus()
).
At this point, you have to decide if you want to reuse the server process for all your tests. This is much faster, but could make it more difficult to debug some problems, since the root of the problem you see in one test may lie in a previous test.
strigidaemon
did not detach, so we can stop it with
QProcess::terminate()
. To stop the DBus daemon, we call
kill(dbuspid, 15)
, sleep one second and call
kill(dbuspid, 9)
to make sure dbus-daemon
is
terminated.
So now we have a way of doing DBus unit tests which takes care of starting and stopping the private DBus daemon and server program. It discards environment information and should not influence your running environment. All of this is achieved without needing a completely separate environment.
#include "config.h"
#include "strigiclient.h"
#include #include #include `
/**
* Retrieve the environment settings as a QMap.
**/
QMap
() {
getEnvironmentQMap env;
foreach (const QString& val, QProcess::systemEnvironment()) {
int p = val.indexOf('=');
if (p > 0) {
[val.left(p).toUpper()] = val.mid(p+1);
env}
}
return env;
}
/**
* Unset all environment variables except HOME.
**/
void
() {
clearEnvironmentQMap environment = getEnvironment();
foreach (const QString& s, environment.keys()) {
if (s != "HOME") {
(s.toAscii());
unsetenv}
}
}
/**
* Parse the output from the dbus-launch invocation and set the DBUS
* environment variable in the environment of the current application.
**/
int
(QIODevice& io) {
addDBusToEnvironmentQByteArray data = io.readLine();
int pid = -1;
while (data.size()) {
if (data[data.size()-1] == 'n') {
.resize(data.size()-1);
data}
QString val(data);
int p = val.indexOf('=');
if (p > 0) {
QString name = val.left(p).toUpper();
= val.mid(p+1);
val if (name == "DBUS_SESSION_BUS_PID") {
= val.toInt();
pid (name.toAscii(), val.toAscii(), 1);
setenv} else if (name == "DBUS_SESSION_BUS_ADDRESS") {
(name.toAscii(), val.toAscii(), 1);
setenv}
}
= io.readLine();
data }
return pid;
}
int
() {
startDBusDaemon// start the dbus process
QProcess dbusprocess;
//dbusprocess.setEnvironment(env);
QStringList dbusargs;
.start("/usr/bin/dbus-launch", dbusargs);
dbusprocessbool ok = dbusprocess.waitForStarted() &&
.waitForFinished();
dbusprocessif (!ok) {
qDebug() << "error starting dbus-launch";
.kill();
dbusprocessreturn -1;
}
// add the dbus settings to the environment
int dbuspid = addDBusToEnvironment(dbusprocess);
return dbuspid;
}
void
(int dbuspid) {
stopDBusDaemon// stop the dbus-daemon nicely
if (dbuspid) kill(dbuspid, 15);
(1);
sleep// stop the dbus-daemon harsly (if it is still running)
if (dbuspid) kill(dbuspid, 9);
}
QProcess*
() {
startStrigiDaemonQString strigiDaemon = BINARYDIR"/src/daemon/strigidaemon";
QProcess* strigiDaemonProcess = new QProcess();
QStringList args;
->start(strigiDaemon, args);
strigiDaemonProcess->waitForStarted();
strigiDaemonProcess
return strigiDaemonProcess;
}
void
(QProcess* strigiDaemonProcess) {
stopStrigiDaemon->terminate();
strigiDaemonProcessif (!strigiDaemonProcess->waitForFinished(5000)) {
qDebug() << "Problem finishing process.";
}
//qDebug() << strigiDaemonProcess->readAllStandardError();
//qDebug() << strigiDaemonProcess->readAllStandardOutput();
->close();
strigiDaemonProcessdelete strigiDaemonProcess;
}
void
() {
doTests;
StrigiClient strigiclientqDebug() << strigiclient.getStatus();
}
int
() {
main// unset all environment variables except HOME
();
clearEnvironment
// start the required daemons and wait for them to start up
int dbuspid = startDBusDaemon();
// set some environment variables so that strigi can find the desired
// files from the source and build directories
// This ensures we test the development version, not the installed version
("XDG_DATA_HOME",
setenv"/src/streamanalyzer/fieldproperties", 1);
SOURCEDIR("XDG_DATA_DIRS",
setenv"/src/streamanalyzer/fieldproperties", 1);
SOURCEDIR("STRIGI_PLUGIN_PATH", BINARYDIR"/src/luceneindexer/:"
setenv"/src/estraierindexer:"BINARYDIR"/src/sqliteindexer", 1);
BINARYDIRQProcess* strigiDaemonProcess = startStrigiDaemon();
(1);
sleep
();
doTests
// stop the daemons
(strigiDaemonProcess);
stopStrigiDaemon(dbuspid);
stopDBusDaemonreturn 0;
}
Comments
Nice
This is becoming important in our integrated environment - thanks for taking the time to work out some best practices. I should come up with some unit tests for Solid::Networking and its NetworkManager backends too.
By Will Stephenson at Tue, 12/11/2007 - 23:10