diff --git a/akonadi/tests/testrunner/main.cpp b/akonadi/tests/testrunner/main.cpp index 946526fd9..c6a0687d0 100644 --- a/akonadi/tests/testrunner/main.cpp +++ b/akonadi/tests/testrunner/main.cpp @@ -1,115 +1,115 @@ /* * * Copyright (c) 2008 Igor Trindade Oliveira * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "akonaditesting.h" #include "config.h" #include "setup.h" #include "shellscript.h" #include "testrunner.h" #include #include #include #include #include static SetupTest *setup = 0; static TestRunner *runner = 0; void sigHandler( int signal ) { kDebug() << "Received signal" << signal; static int sigCounter = 0; if ( sigCounter == 0 ) { // try clean shutdown if ( runner ) runner->terminate(); if ( setup ) setup->shutdown(); } else if ( sigCounter == 1 ) { // force shutdown if ( setup ) setup->shutdownHarder(); } else { // give up and just exit exit( 255 ); } ++sigCounter; } int main( int argc, char **argv ) { KAboutData aboutdata( "akonadi-TES", 0, ki18n( "Akonadi Testing Environment Setup" ), "1.0", ki18n( "Setup Environmnet" ), KAboutData::License_GPL, ki18n( "(c) 2008 Igor Trindade Oliveira" ) ); KCmdLineArgs::init( argc, argv, &aboutdata ); KCmdLineOptions options; options.add( "c" ).add( "config ", ki18n( "Configuration file to open" ), "config.xml" ); options.add( "!+[test]", ki18n( "Test to run automatically, interactive if none specified" ) ); KCmdLineArgs::addCmdLineOptions( options ); KApplication app; const KCmdLineArgs *args = KCmdLineArgs::parsedArgs(); if ( args->isSet( "config" ) ) Config::instance( args->getOption( "config" ) ); #ifdef Q_OS_UNIX signal( SIGINT, sigHandler ); signal( SIGQUIT, sigHandler ); #endif setup = new SetupTest(); setup->startAkonadiDaemon(); AkonadiTesting *testing = new AkonadiTesting(); testing->insertItemFromList(); ShellScript *sh = new ShellScript(); - sh->makeShellScript(); + sh->makeShellScript( setup->basePath() + "testenvironment.sh" ); if ( args->count() > 0 ) { QStringList testArgs; for ( int i = 0; i < args->count(); ++i ) testArgs << args->arg( i ); runner = new TestRunner( testArgs ); QObject::connect( setup, SIGNAL( setupDone() ), runner, SLOT( run() ) ); QObject::connect( runner, SIGNAL( finished() ), setup, SLOT( shutdown() ) ); } int exitCode = app.exec(); if ( runner ) { exitCode += runner->exitCode(); delete runner; } Config::destroyInstance(); delete testing; delete setup; setup = 0; delete sh; return exitCode; } diff --git a/akonadi/tests/testrunner/setup.cpp b/akonadi/tests/testrunner/setup.cpp index 6692e5903..c341f2284 100644 --- a/akonadi/tests/testrunner/setup.cpp +++ b/akonadi/tests/testrunner/setup.cpp @@ -1,387 +1,388 @@ /* * Copyright (c) 2008 Igor Trindade Oliveira * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "setup.h" #include "config.h" #include "symbols.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include QMap SetupTest::environment() const { QMap env; foreach ( const QString& val, QProcess::systemEnvironment() ) { const int p = val.indexOf( QLatin1Char( '=' ) ); if ( p > 0 ) { env[ val.left( p ).toUpper() ] = val.mid( p + 1 ); } } return env; } bool SetupTest::clearEnvironment() { const QStringList keys = environment().keys(); foreach ( const QString& key, keys ) { if ( key != QLatin1String( "HOME" ) ) { if ( !unsetenv( key.toAscii() ) ) { return false; } } } return true; } int SetupTest::addDBusToEnvironment( QIODevice& io ) { QByteArray data = io.readLine(); int pid = -1; Symbols *symbol = Symbols::instance(); while ( data.size() ) { if ( data[ data.size() - 1 ] == '\n' ) { data.resize( data.size() - 1 ); } QString val( data ); const int p = val.indexOf( '=' ); if ( p > 0 ) { const QString name = val.left( p ).toUpper(); val = val.mid( p + 1 ); if ( name == QLatin1String( "DBUS_SESSION_BUS_PID" ) ) { pid = val.toInt(); setenv( name.toAscii(), val.toAscii(), 1 ); symbol->insertSymbol( name, val ); } else if ( name == QLatin1String( "DBUS_SESSION_BUS_ADDRESS" ) ) { setenv( name.toAscii(), val.toAscii(), 1 ); symbol->insertSymbol( name, val ); } } data = io.readLine(); } return pid; } int SetupTest::startDBusDaemon() { QProcess dbusprocess; QStringList dbusargs; dbusprocess.start( "/usr/bin/dbus-launch", dbusargs ); bool ok = dbusprocess.waitForStarted() && dbusprocess.waitForFinished(); if ( !ok ) { kDebug() << "error starting dbus-launch"; dbusprocess.kill(); return -1; } int dbuspid = addDBusToEnvironment( dbusprocess ); return dbuspid; } void SetupTest::stopDBusDaemon( int dbuspid ) { kDebug() << dbuspid; if ( dbuspid ) kill( dbuspid, 15 ); sleep( 1 ); if ( dbuspid ) kill( dbuspid, 9 ); } void SetupTest::registerWithInternalDBus( const QString &address ) { mInternalBus = new QDBusConnection( QDBusConnection::connectToBus( address, QLatin1String( "InternalBus" ) ) ); - mInternalBus->registerService( QLatin1String( "org.kde.Akonaditest" ) ); + mInternalBus->registerService( QLatin1String( "org.kde.Akonadi.Testrunner" ) ); mInternalBus->registerObject( QLatin1String( "/MainApplication" ), KApplication::kApplication(), QDBusConnection::ExportScriptableSlots | QDBusConnection::ExportScriptableProperties | QDBusConnection::ExportAdaptors ); + mInternalBus->registerObject( QLatin1String( "/" ), this, QDBusConnection::ExportScriptableSlots ); QDBusConnectionInterface *busInterface = mInternalBus->interface(); connect( busInterface, SIGNAL( serviceOwnerChanged( QString, QString, QString ) ), this, SLOT( dbusNameOwnerChanged( QString, QString, QString ) ) ); } void SetupTest::startAkonadiDaemon() { mAkonadiDaemonProcess->setProgram( QLatin1String( "akonadi_control" ) ); mAkonadiDaemonProcess->start(); mAkonadiDaemonProcess->waitForStarted( 5000 ); kDebug() << mAkonadiDaemonProcess->pid(); } void SetupTest::stopAkonadiDaemon() { mAkonadiDaemonProcess->terminate(); if ( !mAkonadiDaemonProcess->waitForFinished( 5000 ) ) { kDebug() << "Problem finishing process."; } mAkonadiDaemonProcess->close(); } void SetupTest::setupAgents() { if ( mAgentsCreated ) return; mAgentsCreated = true; Config *config = Config::instance(); QDBusInterface agentDBus( QLatin1String( "org.freedesktop.Akonadi.Control" ), QLatin1String( "/AgentManager" ), QLatin1String( "org.freedesktop.Akonadi.AgentManager" ), *mInternalBus ); const QList > agents = config->agents(); typedef QPair StringBoolPair; foreach ( const StringBoolPair &agent, agents ) { kDebug() << "Creating agent" << agent.first << "..."; QDBusReply reply = agentDBus.call( QLatin1String( "createAgentInstance" ), agent.first ); if ( reply.isValid() && !reply.value().isEmpty() ) { mPendingAgents << reply.value(); mPendingResources << reply.value(); if ( agent.second ) { mPendingSyncs << reply.value(); } } else { kError() << "createAgentInstance call failed:" << reply.error(); } } if ( mPendingAgents.isEmpty() ) emit setupDone(); } void SetupTest::dbusNameOwnerChanged( const QString &name, const QString &oldOwner, const QString &newOwner ) { kDebug() << name << oldOwner << newOwner; // TODO: find out why it does not work properly when reacting on // org.freedesktop.Akonadi.Control if ( name == QLatin1String( "org.freedesktop.Akonadi" ) ) { if ( oldOwner.isEmpty() ) // startup setupAgents(); else if ( mShuttingDown ) // our own shutdown shutdownHarder(); return; } if ( name.startsWith( QLatin1String( "org.freedesktop.Akonadi.Agent." ) ) && oldOwner.isEmpty() ) { const QString identifier = name.mid( 30 ); if ( mPendingAgents.contains( identifier ) ) { kDebug() << "Agent" << identifier << "started."; mPendingAgents.removeAll( identifier ); if ( mPendingAgents.isEmpty() && mPendingResources.isEmpty() ) QTimer::singleShot( 5000, this, SLOT(synchronizeResources()) ); } } if ( name.startsWith( QLatin1String( "org.freedesktop.Akonadi.Resource." ) ) && oldOwner.isEmpty() ) { const QString identifier = name.mid( 33 ); if ( mPendingResources.contains( identifier ) ) { kDebug() << "Resource" << identifier << "registered."; mPendingResources.removeAll( identifier ); if ( mPendingAgents.isEmpty() && mPendingResources.isEmpty() ) QTimer::singleShot( 5000, this, SLOT(synchronizeResources()) ); } } } void SetupTest::synchronizeResources() { foreach ( const QString id, mPendingSyncs ) { QDBusInterface *iface = new QDBusInterface( QString::fromLatin1( "org.freedesktop.Akonadi.Resource.%1").arg( id ), "/", "org.freedesktop.Akonadi.Resource", *mInternalBus, this ); mSyncMapper->setMapping( iface, id ); connect( iface, SIGNAL(synchronized()), mSyncMapper, SLOT(map()) ); if ( mPendingSyncs.contains( id ) ) { kDebug() << "Synchronizing resource" << id << "..."; QDBusReply reply = iface->call( "synchronize" ); if ( !reply.isValid() ) kError() << "Syncing resource" << id << "failed: " << reply.error(); } } } void SetupTest::resourceSynchronized(const QString& agentId) { if ( mPendingSyncs.contains( agentId ) ) { kDebug() << "Agent" << agentId << "synchronized."; mPendingSyncs.removeAll( agentId ); if ( mPendingSyncs.isEmpty() ) emit setupDone(); } } void SetupTest::copyDirectory( const QString &src, const QString &dst ) { QDir srcDir( src ); srcDir.setFilter( QDir::Dirs | QDir::Files | QDir::NoSymLinks | QDir::NoDotAndDotDot ); const QFileInfoList list = srcDir.entryInfoList(); for ( int i = 0; i < list.size(); ++i ) { if ( list.at( i ).isDir() ) { const QDir tmpDir( dst ); tmpDir.mkdir( list.at( i ).fileName() ); copyDirectory( list.at( i ).absoluteFilePath(), dst + QDir::separator() + list.at( i ).fileName() ); } else { QFile::copy( srcDir.absolutePath() + QDir::separator() + list.at( i ).fileName(), dst + QDir::separator() + list.at( i ).fileName() ); } } } void SetupTest::createTempEnvironment() { const QDir tmpDir( basePath() ); const QString testRunnerKdeHomeDir = QLatin1String( "kdehome" ); const QString testRunnerDataDir = QLatin1String( "data" ); const QString testRunnerConfigDir = QLatin1String( "config" ); tmpDir.mkdir( testRunnerKdeHomeDir ); tmpDir.mkdir( testRunnerConfigDir ); tmpDir.mkdir( testRunnerDataDir ); const Config *config = Config::instance(); copyDirectory( config->kdeHome(), basePath() + testRunnerKdeHomeDir ); copyDirectory( config->xdgConfigHome(), basePath() + testRunnerConfigDir ); copyDirectory( config->xdgDataHome(), basePath() + testRunnerDataDir ); } void SetupTest::deleteDirectory( const QString &dirName ) { Q_ASSERT( dirName.startsWith( QDir::tempPath() ) ); // just to be sure we don't run amok anywhere QDir dir( dirName ); dir.setFilter( QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot ); const QFileInfoList list = dir.entryInfoList(); for ( int i = 0; i < list.size(); ++i ) { if ( list.at( i ).isDir() && !list.at( i ).isSymLink() ) { deleteDirectory( list.at( i ).absoluteFilePath() ); const QDir tmpDir( list.at( i ).absoluteDir() ); tmpDir.rmdir( list.at( i ).fileName() ); } else { QFile::remove( list.at( i ).absoluteFilePath() ); } } dir.cdUp(); dir.rmdir( dirName ); } void SetupTest::cleanTempEnvironment() { deleteDirectory( basePath() ); } SetupTest::SetupTest() : mShuttingDown( false ), mSyncMapper( new QSignalMapper( this ) ), mAgentsCreated( false ) { clearEnvironment(); cleanTempEnvironment(); createTempEnvironment(); setenv( "KDEHOME", qPrintable( basePath() + "kdehome" ), 1 ); setenv( "XDG_DATA_HOME", qPrintable( basePath() + "data" ), 1 ); setenv( "XDG_CONFIG_HOME", qPrintable( basePath() + "config" ), 1 ); Symbols *symbol = Symbols::instance(); symbol->insertSymbol( "KDEHOME", basePath() + QLatin1String( "kdehome" ) ); symbol->insertSymbol( "XDG_DATA_HOME", basePath() + QLatin1String( "data" ) ); symbol->insertSymbol( "XDG_CONFIG_HOME", basePath() + QLatin1String( "config" ) ); symbol->insertSymbol( "AKONADI_TESTRUNNER_PID", QString::number( QCoreApplication::instance()->applicationPid() ) ); mDBusDaemonPid = startDBusDaemon(); #if (QT_VERSION >= QT_VERSION_CHECK(4, 4, 2)) const QString dbusAddress = symbol->symbols()[ QLatin1String( "DBUS_SESSION_BUS_ADDRESS" ) ]; registerWithInternalDBus( dbusAddress ); #endif mAkonadiDaemonProcess = new KProcess( this ); connect( mSyncMapper, SIGNAL(mapped(QString)), SLOT(resourceSynchronized(QString)) ); } SetupTest::~SetupTest() { cleanTempEnvironment(); delete mAkonadiDaemonProcess; } void SetupTest::shutdown() { if ( mShuttingDown ) return; mShuttingDown = true; // check first if the Akonadi server is still running, otherwise D-Bus auto-launch will actually start it here if ( mInternalBus->interface()->isServiceRegistered( "org.freedesktop.Akonadi.Control" ) ) { kDebug() << "Shutting down Akonadi control..."; QDBusInterface controlIface( QLatin1String( "org.freedesktop.Akonadi.Control" ), QLatin1String( "/ControlManager" ), QLatin1String( "org.freedesktop.Akonadi.ControlManager" ), *mInternalBus ); QDBusReply reply = controlIface.call( "shutdown" ); if ( !reply.isValid() ) { kWarning() << "Failed to shutdown Akonadi control: " << reply.error().message(); shutdownHarder(); } else { // safety timeout QTimer::singleShot( 30 * 1000, this, SLOT( shutdownHarder() ) ); } } else { shutdownHarder(); } } void SetupTest::shutdownHarder() { kDebug(); mShuttingDown = false; stopAkonadiDaemon(); stopDBusDaemon( mDBusDaemonPid ); QCoreApplication::instance()->quit(); } QString SetupTest::basePath() const { const QString tempDir = QString::fromLatin1( "akonadi_testrunner-%1" ) .arg( QCoreApplication::instance()->applicationPid() ); if ( !QDir::temp().exists( tempDir ) ) QDir::temp().mkdir( tempDir ); return QDir::tempPath() + QDir::separator() + tempDir + QDir::separator(); } diff --git a/akonadi/tests/testrunner/setup.h b/akonadi/tests/testrunner/setup.h index 4f43fd16b..94d6ac687 100644 --- a/akonadi/tests/testrunner/setup.h +++ b/akonadi/tests/testrunner/setup.h @@ -1,80 +1,81 @@ /* * Copyright (c) 2008 Igor Trindade Oliveira * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #ifndef SETUP_H #define SETUP_H #include #include #include class QDBusConnection; class QIODevice; class KProcess; class QSignalMapper; class SetupTest : public QObject { Q_OBJECT + Q_CLASSINFO( "D-Bus Interface", "org.kde.Akonadi.Testrunner" ) public: SetupTest(); ~SetupTest(); void startAkonadiDaemon(); void stopAkonadiDaemon(); - + QString basePath() const; + public Q_SLOTS: - void shutdown(); - void shutdownHarder(); + Q_SCRIPTABLE void shutdown(); + Q_SCRIPTABLE void shutdownHarder(); Q_SIGNALS: void setupDone(); private Q_SLOTS: void dbusNameOwnerChanged( const QString &name, const QString &oldOwner, const QString &newOwner ); void resourceSynchronized( const QString &agentId ); private: bool clearEnvironment(); QMap environment() const; int addDBusToEnvironment( QIODevice &device ); int startDBusDaemon(); void stopDBusDaemon( int dbusPid ); void registerWithInternalDBus( const QString &address ); void setupAgents(); void copyDirectory( const QString &src, const QString &dst ); void createTempEnvironment(); void deleteDirectory( const QString &dirName ); void cleanTempEnvironment(); - QString basePath() const; private slots: void synchronizeResources(); private: KProcess *mAkonadiDaemonProcess; int mDBusDaemonPid; QDBusConnection *mInternalBus; QStringList mPendingAgents; QStringList mPendingResources; QStringList mPendingSyncs; bool mShuttingDown; QSignalMapper *mSyncMapper; bool mAgentsCreated; }; #endif diff --git a/akonadi/tests/testrunner/shellscript.cpp b/akonadi/tests/testrunner/shellscript.cpp index 842327ec0..adf2597c9 100644 --- a/akonadi/tests/testrunner/shellscript.cpp +++ b/akonadi/tests/testrunner/shellscript.cpp @@ -1,81 +1,83 @@ /* * Copyright (c) 2008 Igor Trindade Oliveira * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "shellscript.h" #include "config.h" #include "symbols.h" +#include #include #include ShellScript::ShellScript() { mSymbol = Symbols::instance(); } void ShellScript::writeEnvironmentVariables() { QHashIterator it( mSymbol->symbols() ); while ( it.hasNext() ) { it.next(); + mScript.append( QString::fromLatin1("_old_%1=$%2\n").arg( it.key(), it.key() ) ); mScript.append( it.key() ); mScript.append( QLatin1Char( '=' ) ); mScript.append( it.value() ); mScript.append( QLatin1Char( '\n' ) ); mScript.append( QLatin1String( "export " ) ); mScript.append( it.key() ); mScript.append( QLatin1Char( '\n' ) ); } mScript.append( QLatin1String( "\n\n" ) ); } void ShellScript::writeShutdownFunction() { - const QString s = + QString s = "function shutdown-testenvironment()\n" "{\n" - " echo Stopping Akonadi server\n" - " qdbus org.freedesktop.Akonadi.Control /ControlManager org.freedesktop.Akonadi.ControlManager.shutdown\n" - " echo \"Stopping testrunner with PID \" $AKONADI_TESTRUNNER_PID\n" - " kill $AKONADI_TESTRUNNER_PID\n" - " # wait a bit before killing D-Bus\n" - " echo \"Waiting 10 seconds before killing D-Bus\"\n" - " sleep 10\n" - " echo \"Killing D-Bus with PID \" $DBUS_SESSION_BUS_PID\n" - " kill $DBUS_SESSION_BUS_PID\n" - " rm -fr /tmp/akonadi_testrunner\n" - "}\n\n"; + " qdbus org.kde.Akonadi.Testrunner / org.kde.Akonadi.Testrunner.shutdown\n"; + QHashIterator it( mSymbol->symbols() ); + while ( it.hasNext() ) { + it.next(); + s.append( QString::fromLatin1( " %1=$_old_%2\n" ).arg( it.key(), it.key() ) ); + s.append( QString::fromLatin1( " export %1\n" ).arg( it.key() ) ); + } + s.append( "}\n\n" ); mScript.append( s ); } void ShellScript::makeShellScript( const QString &fileName ) { + kDebug() << fileName; QFile file( fileName ); //can user define the file name/location? - file.open( QIODevice::WriteOnly ); - - writeEnvironmentVariables(); - writeShutdownFunction(); + if ( file.open( QIODevice::WriteOnly ) ) { + writeEnvironmentVariables(); + writeShutdownFunction(); - file.write( mScript.toAscii(), qstrlen( mScript.toAscii() ) ); - file.close(); + file.write( mScript.toAscii(), qstrlen( mScript.toAscii() ) ); + file.close(); + } else { + kError() << "Failed to write" << fileName; + } } diff --git a/akonadi/tests/testrunner/shellscript.h b/akonadi/tests/testrunner/shellscript.h index 934ee5157..7288a0213 100644 --- a/akonadi/tests/testrunner/shellscript.h +++ b/akonadi/tests/testrunner/shellscript.h @@ -1,38 +1,38 @@ /* * Copyright (c) 2008 Igor Trindade Oliveira * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #ifndef SHELLSCRIPT_H #define SHELLSCRIPT_H #include class Symbols; class ShellScript { public: ShellScript(); - void makeShellScript( const QString &filename = QLatin1String( "testenvironment.sh" ) ); + void makeShellScript( const QString &filename ); private: void writeEnvironmentVariables(); void writeShutdownFunction(); Symbols *mSymbol; QString mScript; }; #endif