19
20
#include "config.h" //krazy:exclude=includes
20
#include "dbusconnectionpool.h"
23
#include <kapplication.h>
22
#include <akonadi/agentinstance.h>
23
#include <akonadi/agentinstancecreatejob.h>
24
#include <akonadi/resourcesynchronizationjob.h>
24
27
#include <kconfiggroup.h>
25
28
#include <kdebug.h>
26
29
#include <KProcess>
27
30
#include <KStandardDirs>
28
#include <KToolInvocation>
30
32
#include <QtCore/QCoreApplication>
31
33
#include <QtCore/QDir>
32
34
#include <QtCore/QFile>
33
35
#include <QtCore/QFileInfo>
34
36
#include <QtCore/QTimer>
35
#include <QSignalMapper>
36
#include <QtDBus/QDBusConnectionInterface>
37
#include <QtDBus/QDBusInterface>
38
#include <QtDBus/QDBusReply>
39
#include <QtNetwork/QHostInfo>
42
38
#include <unistd.h>
45
#include <AvailabilityMacros.h>
48
QMap<QString, QString> SetupTest::environment() const
50
QMap<QString, QString> env;
52
foreach ( const QString& val, QProcess::systemEnvironment() ) {
53
const int p = val.indexOf( QLatin1Char( '=' ) );
55
env[ val.left( p ).toUpper() ] = val.mid( p + 1 );
63
bool SetupTest::clearEnvironment()
65
const QStringList keys = environment().keys();
67
foreach ( const QString& key, keys ) {
68
if ( key != QLatin1String( "HOME" ) ) {
69
// work around a bug in the Mac OS X 10.4.0 SDK
70
#if defined(MAC_OS_X_VERSION_MAX_ALLOWED) && (MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_4)
71
/* on OSX 10.4, unsetenv is a void, not a boolean */
72
unsetenv( key.toLatin1() );
74
if ( !unsetenv( key.toLatin1() ) ) {
84
int SetupTest::addDBusToEnvironment( QIODevice& io )
86
QByteArray data = io.readLine();
88
Symbols *symbol = Symbols::instance();
90
while ( data.size() ) {
91
if ( data[ data.size() - 1 ] == '\n' ) {
92
data.resize( data.size() - 1 );
96
const int p = val.indexOf( '=' );
98
const QString name = val.left( p ).toUpper();
99
val = val.mid( p + 1 );
100
if ( name == QLatin1String( "DBUS_SESSION_BUS_PID" ) ) {
102
setenv( name.toLatin1(), val.toAscii(), 1 );
103
symbol->insertSymbol( name, val );
104
} else if ( name == QLatin1String( "DBUS_SESSION_BUS_ADDRESS" ) ) {
105
setenv( name.toLatin1(), val.toAscii(), 1 );
106
symbol->insertSymbol( name, val );
109
data = io.readLine();
115
void SetupTest::generateDBusConfigFile( const QString& path )
117
static const char* configFileContents =
118
"<!DOCTYPE busconfig PUBLIC \"-//freedesktop//DTD D-Bus Bus Configuration 1.0//EN\"\n"
119
"\"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd\">\n"
121
"<type>session</type>"
123
"<listen>unix:path=%1</listen>\n"
124
"<standard_session_servicedirs />\n"
125
"<policy context=\"default\">\n"
126
"<allow send_destination=\"*\" eavesdrop=\"true\"/>\n"
127
"<allow eavesdrop=\"true\"/>\n"
128
"<allow own=\"*\"/>\n"
132
QFile confFile(path);
133
if ( confFile.open( QIODevice::WriteOnly ) ) {
134
const QString socketPath = basePath() + QDir::separator() + QLatin1String("dbus.socket");
135
const QString data = QString::fromLatin1( configFileContents ).arg( socketPath );
136
const qint64 bytes = confFile.write( data.toUtf8() );
137
Q_ASSERT( bytes > 0 );
141
int SetupTest::startDBusDaemon()
143
QStringList dbusargs;
145
const QString dbusConfigFilePath = basePath() + QDir::separator() + QLatin1String("dbus-session.conf");
146
generateDBusConfigFile( dbusConfigFilePath );
147
Q_ASSERT( QFile::exists( dbusConfigFilePath ) );
149
dbusargs << QString::fromLatin1("--config-file=%1").arg( dbusConfigFilePath );
152
QProcess dbusprocess;
153
dbusprocess.start( QLatin1String("dbus-launch"), dbusargs );
154
bool ok = dbusprocess.waitForStarted() && dbusprocess.waitForFinished();
156
kWarning() << "error starting dbus-launch";
158
exit(1); // failure to start an internal dbus must be considered a fatal unit test error
161
int dbuspid = addDBusToEnvironment( dbusprocess );
165
void SetupTest::stopDBusDaemon( int dbuspid )
176
void SetupTest::registerWithInternalDBus( const QString &address )
178
// FIXME: make this work on Windows, address is always empty there as dbus-launch does not return any environment variables like on Unix there
180
mInternalBus = QDBusConnection::connectToBus( address, QLatin1String( "InternalBus" ) );
182
mInternalBus.registerService( QLatin1String( "org.kde.Akonadi.Testrunner" ) );
183
mInternalBus.registerObject( QLatin1String( "/MainApplication" ),
184
KApplication::kApplication(),
185
QDBusConnection::ExportScriptableSlots |
186
QDBusConnection::ExportScriptableProperties |
187
QDBusConnection::ExportAdaptors );
188
mInternalBus.registerObject( QLatin1String( "/" ), this, QDBusConnection::ExportScriptableSlots );
190
connect( mInternalBus.interface(), SIGNAL(serviceOwnerChanged(QString,QString,QString)),
191
this, SLOT(dbusNameOwnerChanged(QString,QString,QString)) );
194
40
bool SetupTest::startAkonadiDaemon()
42
Q_ASSERT(Akonadi::ServerManager::hasInstanceIdentifier());
196
44
if ( !mAkonadiDaemonProcess ) {
197
45
mAkonadiDaemonProcess = new KProcess( this );
198
46
connect( mAkonadiDaemonProcess, SIGNAL(finished(int)),
199
47
this, SLOT(slotAkonadiDaemonProcessFinished(int)) );
202
mAkonadiDaemonProcess->setProgram( QLatin1String( "akonadi_control" ) );
50
mAkonadiDaemonProcess->setProgram( QLatin1String( "akonadi_control" ), QStringList() << QLatin1String("--instance") << instanceId() );
203
51
mAkonadiDaemonProcess->start();
204
52
const bool started = mAkonadiDaemonProcess->waitForStarted( 5000 );
205
53
kDebug() << "Started akonadi daemon with pid:" << mAkonadiDaemonProcess->pid();
226
74
if ( mAgentsCreated )
229
// Start KLauncher now, so that kdeinit4 and kded4 are started. Otherwise, those might get started
230
// on demand, for example in the Knut resource.
231
// This on-demand starting can cause crashes due to timing issues.
232
KToolInvocation::klauncher();
234
76
mAgentsCreated = true;
235
77
Config *config = Config::instance();
236
QDBusInterface agentDBus( QLatin1String( "org.freedesktop.Akonadi.Control" ), QLatin1String( "/AgentManager" ),
237
QLatin1String( "org.freedesktop.Akonadi.AgentManager" ), mInternalBus );
239
78
const QList<QPair<QString,bool> > agents = config->agents();
240
79
typedef QPair<QString,bool> StringBoolPair;
241
80
foreach ( const StringBoolPair &agent, agents ) {
242
81
kDebug() << "Creating agent" << agent.first << "...";
243
QDBusReply<QString> reply = agentDBus.call( QLatin1String( "createAgentInstance" ), agent.first );
244
if ( reply.isValid() && !reply.value().isEmpty() ) {
245
mPendingAgents << reply.value();
246
mPendingResources << reply.value();
247
if ( agent.second ) {
248
mPendingSyncs << reply.value();
251
kError() << "createAgentInstance call failed:" << reply.error();
83
Akonadi::AgentInstanceCreateJob *job = new Akonadi::AgentInstanceCreateJob( agent.first, this );
84
job->setProperty( "sync", agent.second );
85
connect( job, SIGNAL(result(KJob*)), SLOT(agentCreationResult(KJob*)) );
255
if ( mPendingAgents.isEmpty() )
259
void SetupTest::dbusNameOwnerChanged( const QString &name, const QString &oldOwner, const QString &newOwner )
93
void SetupTest::agentCreationResult(KJob* job)
261
kDebug() << name << oldOwner << newOwner;
263
if ( name == QLatin1String( "org.freedesktop.Akonadi.Control" ) ) {
264
if ( oldOwner.isEmpty() ) // startup
266
else if ( mShuttingDown ) // our own shutdown
97
kError() << job->errorString();
271
if ( name.startsWith( QLatin1String( "org.freedesktop.Akonadi.Agent." ) ) && oldOwner.isEmpty() ) {
272
const QString identifier = name.mid( 30 );
273
if ( mPendingAgents.contains( identifier ) ) {
274
kDebug() << "Agent" << identifier << "started.";
275
mPendingAgents.removeAll( identifier );
276
if ( mPendingAgents.isEmpty() && mPendingResources.isEmpty() )
277
QTimer::singleShot( 5000, this, SLOT(synchronizeResources()) );
281
if ( name.startsWith( QLatin1String( "org.freedesktop.Akonadi.Resource." ) ) && oldOwner.isEmpty() ) {
282
const QString identifier = name.mid( 33 );
283
if ( mPendingResources.contains( identifier ) ) {
284
kDebug() << "Resource" << identifier << "registered.";
285
mPendingResources.removeAll( identifier );
286
if ( mPendingAgents.isEmpty() && mPendingResources.isEmpty() )
287
QTimer::singleShot( 5000, this, SLOT(synchronizeResources()) );
292
void SetupTest::synchronizeResources()
294
foreach ( const QString &id, mPendingSyncs ) {
295
QDBusInterface *iface = new QDBusInterface( QString::fromLatin1( "org.freedesktop.Akonadi.Resource.%1").arg( id ),
296
"/", "org.freedesktop.Akonadi.Resource", mInternalBus, this );
297
mSyncMapper->setMapping( iface, id );
298
connect( iface, SIGNAL(synchronized()), mSyncMapper, SLOT(map()) );
299
if ( mPendingSyncs.contains( id ) ) {
300
kDebug() << "Synchronizing resource" << id << "...";
301
QDBusReply<void> reply = iface->call( "synchronize" );
302
if ( !reply.isValid() )
303
kError() << "Syncing resource" << id << "failed: " << reply.error();
308
void SetupTest::resourceSynchronized(const QString& agentId)
310
if ( mPendingSyncs.contains( agentId ) ) {
311
kDebug() << "Agent" << agentId << "synchronized.";
312
mPendingSyncs.removeAll( agentId );
313
if ( mPendingSyncs.isEmpty() )
101
const bool needsSync = job->property( "sync" ).toBool();
104
Akonadi::ResourceSynchronizationJob *sync = new Akonadi::ResourceSynchronizationJob(
105
qobject_cast<Akonadi::AgentInstanceCreateJob*>(job)->instance(), this );
106
connect( sync, SIGNAL(result(KJob*)), SLOT(synchronizationResult(KJob*)) );
114
void SetupTest::synchronizationResult(KJob* job)
117
if ( job->error() ) {
118
kError() << job->errorString();
126
void SetupTest::serverStateChanged(Akonadi::ServerManager::State state)
128
if ( state == Akonadi::ServerManager::Running )
130
else if ( mShuttingDown && state == Akonadi::ServerManager::NotRunning )
134
void SetupTest::copyXdgDirectory(const QString& src, const QString& dst)
136
const QDir srcDir( src );
137
foreach ( const QFileInfo &fi, srcDir.entryInfoList( QDir::Dirs | QDir::Files | QDir::NoSymLinks | QDir::NoDotAndDotDot ) ) {
139
if ( fi.fileName() == QLatin1String("akonadi") ) {
140
// namespace according to instance identifier
141
copyDirectory( fi.absoluteFilePath(), dst + QDir::separator() + QLatin1String("akonadi") + QDir::separator()
142
+ QLatin1String("instance") + QDir::separator() + instanceId() );
144
copyDirectory( fi.absoluteFilePath(), dst + QDir::separator() + fi.fileName() );
147
QFile::copy( fi.absoluteFilePath(), dst + QDir::separator() + fi.fileName() );
152
void SetupTest::copyKdeHomeDirectory(const QString& src, const QString& dst)
154
const QDir srcDir( src );
155
QDir::root().mkpath( dst );
157
foreach ( const QFileInfo &fi, srcDir.entryInfoList( QDir::Dirs | QDir::Files | QDir::NoSymLinks | QDir::NoDotAndDotDot ) ) {
159
copyKdeHomeDirectory( fi.absoluteFilePath(), dst + QDir::separator() + fi.fileName() );
161
if ( fi.fileName().startsWith( QLatin1String("akonadi_") ) && fi.fileName().endsWith( QLatin1String("rc") ) ) {
162
// namespace according to instance identifier
163
const QString baseName = fi.fileName().left( fi.fileName().size() - 2 );
164
QFile::copy( fi.absoluteFilePath(), dst + QDir::separator() + Akonadi::ServerManager::addNamespace( baseName ) + QLatin1String("rc") );
166
QFile::copy( fi.absoluteFilePath(), dst + QDir::separator() + fi.fileName() );
318
172
void SetupTest::copyDirectory( const QString &src, const QString &dst )
321
srcDir.setFilter( QDir::Dirs | QDir::Files | QDir::NoSymLinks | QDir::NoDotAndDotDot );
174
const QDir srcDir( src );
175
QDir::root().mkpath( dst );
323
const QFileInfoList list = srcDir.entryInfoList();
324
for ( int i = 0; i < list.size(); ++i ) {
325
if ( list.at( i ).isDir() ) {
326
const QDir tmpDir( dst );
327
tmpDir.mkdir( list.at( i ).fileName() );
328
copyDirectory( list.at( i ).absoluteFilePath(), dst + QDir::separator() + list.at( i ).fileName() );
177
foreach ( const QFileInfo &fi, srcDir.entryInfoList( QDir::Dirs | QDir::Files | QDir::NoSymLinks | QDir::NoDotAndDotDot ) ) {
179
copyDirectory( fi.absoluteFilePath(), dst + QDir::separator() + fi.fileName() );
330
QFile::copy( srcDir.absolutePath() + QDir::separator() + list.at( i ).fileName(), dst + QDir::separator() + list.at( i ).fileName() );
181
QFile::copy( fi.absoluteFilePath(), dst + QDir::separator() + fi.fileName() );
335
186
void SetupTest::createTempEnvironment()
188
kDebug() << "Creating test environment in" << basePath();
337
190
const QDir tmpDir( basePath() );
338
191
const QString testRunnerKdeHomeDir = QLatin1String( "kdehome" );
339
192
const QString testRunnerDataDir = QLatin1String( "data" );
340
193
const QString testRunnerConfigDir = QLatin1String( "config" );
194
const QString testRunnerTmpDir = QLatin1String( "tmp" );
342
196
tmpDir.mkdir( testRunnerKdeHomeDir );
343
197
tmpDir.mkdir( testRunnerConfigDir );
344
198
tmpDir.mkdir( testRunnerDataDir );
199
tmpDir.mkdir( testRunnerTmpDir );
346
201
const Config *config = Config::instance();
347
copyDirectory( config->kdeHome(), basePath() + testRunnerKdeHomeDir );
348
copyDirectory( config->xdgConfigHome(), basePath() + testRunnerConfigDir );
349
copyDirectory( config->xdgDataHome(), basePath() + testRunnerDataDir );
202
copyKdeHomeDirectory( config->kdeHome(), basePath() + testRunnerKdeHomeDir );
203
copyXdgDirectory( config->xdgConfigHome(), basePath() + testRunnerConfigDir );
204
copyXdgDirectory( config->xdgDataHome(), basePath() + testRunnerDataDir );
351
// copy sycoca file from the host to increase startup speed
352
const QString sycoca = KStandardDirs::locateLocal( "cache", "ksycoca4" );
353
const QString cacheDir = basePath() + testRunnerKdeHomeDir + QDir::separator() + "cache-" + QHostInfo::localHostName() + QDir::separator();
354
QFile::copy( sycoca, cacheDir + "ksycoca4" );
355
QFile::copy( sycoca + "stamp", cacheDir + "ksycoca4stamp" );
206
setEnvironmentVariable( "KDEHOME", basePath() + testRunnerKdeHomeDir );
207
setEnvironmentVariable( "XDG_DATA_HOME", basePath() + testRunnerDataDir );
208
setEnvironmentVariable( "XDG_CONFIG_HOME", basePath() + testRunnerConfigDir );
209
setEnvironmentVariable( "TMPDIR", basePath() + testRunnerTmpDir );
358
212
// TODO Qt5: use QDir::removeRecursively
433
280
if ( mShuttingDown )
435
282
mShuttingDown = true;
436
// check first if the Akonadi server is still running, otherwise D-Bus auto-launch will actually start it here
437
if ( mInternalBus.interface()->isServiceRegistered( "org.freedesktop.Akonadi.Control" ) ) {
438
kDebug() << "Shutting down Akonadi control...";
439
QDBusInterface controlIface( QLatin1String( "org.freedesktop.Akonadi.Control" ), QLatin1String( "/ControlManager" ),
440
QLatin1String( "org.freedesktop.Akonadi.ControlManager" ), mInternalBus );
441
QDBusReply<void> reply = controlIface.call( "shutdown" );
442
if ( !reply.isValid() ) {
443
kWarning() << "Failed to shutdown Akonadi control: " << reply.error().message();
284
switch ( Akonadi::ServerManager::self()->state() ) {
285
case Akonadi::ServerManager::Running:
286
case Akonadi::ServerManager::Starting:
287
case Akonadi::ServerManager::Upgrading:
288
kDebug() << "Shutting down Akonadi control...";
289
Akonadi::ServerManager::self()->stop();
291
QTimer::singleShot( 30 * 1000, this, SLOT(shutdownHarder()) );
293
case Akonadi::ServerManager::NotRunning:
294
case Akonadi::ServerManager::Broken:
445
295
shutdownHarder();
296
case Akonadi::ServerManager::Stopping:
447
297
// safety timeout
448
298
QTimer::singleShot( 30 * 1000, this, SLOT(shutdownHarder()) );
450
// in case we indirectly started KDE processes, stop those before we kill their D-Bus
457
void SetupTest::shutdownKde()
459
if ( mInternalBus.interface()->isServiceRegistered( "org.kde.klauncher" ) ) {
460
QDBusInterface klauncherIface( QLatin1String( "org.kde.klauncher" ), QLatin1String( "/" ),
461
QLatin1String( "org.kde.KLauncher" ), mInternalBus );
462
QDBusReply<void> reply = klauncherIface.call( "terminate_kdeinit" );
463
if ( !reply.isValid() )
464
kDebug() << reply.error();
466
if ( mInternalBus.interface()->isServiceRegistered( "org.kde.kded" ) ) {
467
QDBusInterface klauncherIface( QLatin1String( "org.kde.kded" ), QLatin1String( "/kded" ),
468
QLatin1String( "org.kde.kded" ), mInternalBus );
469
QDBusReply<void> reply = klauncherIface.call( "quit" );
470
if ( !reply.isValid() )
471
kDebug() << reply.error();