File indexing completed on 2024-11-10 04:40:08
0001 /* 0002 * SPDX-FileCopyrightText: 2008 Igor Trindade Oliveira <igor_trindade@yahoo.com.br> 0003 * SPDX-FileCopyrightText: 2013 Volker Krause <vkrause@kde.org> 0004 * 0005 * SPDX-License-Identifier: LGPL-2.1-or-later 0006 */ 0007 0008 #include "setup.h" 0009 #include "akonaditest_debug.h" 0010 #include "config.h" //krazy:exclude=includes 0011 0012 #include "agentinstance.h" 0013 #include "agentinstancecreatejob.h" 0014 #include "private/standarddirs_p.h" 0015 #include "resourcesynchronizationjob.h" 0016 0017 #include <KConfig> 0018 #include <KConfigGroup> 0019 #include <KProcess> 0020 0021 #include <QCoreApplication> 0022 #include <QDBusConnection> 0023 #include <QDir> 0024 #include <QFile> 0025 #include <QSettings> 0026 #include <QTimer> 0027 #include <chrono> 0028 0029 using namespace std::chrono_literals; 0030 0031 bool SetupTest::startAkonadiDaemon() 0032 { 0033 Q_ASSERT(Akonadi::ServerManager::hasInstanceIdentifier()); 0034 0035 if (!mAkonadiDaemonProcess) { 0036 mAkonadiDaemonProcess = std::make_unique<KProcess>(); 0037 connect(mAkonadiDaemonProcess.get(), &KProcess::finished, this, &SetupTest::slotAkonadiDaemonProcessFinished); 0038 } 0039 0040 mAkonadiDaemonProcess->setProgram(Akonadi::StandardDirs::findExecutable(QStringLiteral("akonadi_control")), {QStringLiteral("--instance"), instanceId()}); 0041 mAkonadiDaemonProcess->start(); 0042 const bool started = mAkonadiDaemonProcess->waitForStarted(5000); 0043 qCInfo(AKONADITEST_LOG) << "Started akonadi daemon with pid:" << mAkonadiDaemonProcess->processId(); 0044 return started; 0045 } 0046 0047 void SetupTest::stopAkonadiDaemon() 0048 { 0049 if (!mAkonadiDaemonProcess) { 0050 return; 0051 } 0052 disconnect(mAkonadiDaemonProcess.get(), &KProcess::finished, this, nullptr); 0053 mAkonadiDaemonProcess->terminate(); 0054 const bool finished = mAkonadiDaemonProcess->waitForFinished(5000); 0055 if (!finished) { 0056 qCDebug(AKONADITEST_LOG) << "Problem finishing process."; 0057 } 0058 mAkonadiDaemonProcess.reset(); 0059 } 0060 0061 void SetupTest::setupAgents() 0062 { 0063 if (mAgentsCreated) { 0064 return; 0065 } 0066 mAgentsCreated = true; 0067 Config *config = Config::instance(); 0068 const auto agents = config->agents(); 0069 for (const auto &[instance, sync] : agents) { 0070 qCDebug(AKONADITEST_LOG) << "Creating agent" << instance << "..."; 0071 ++mSetupJobCount; 0072 auto job = new Akonadi::AgentInstanceCreateJob(instance, this); 0073 job->setProperty("sync", sync); 0074 connect(job, &Akonadi::AgentInstanceCreateJob::result, this, &SetupTest::agentCreationResult); 0075 job->start(); 0076 } 0077 0078 checkSetupDone(); 0079 } 0080 0081 void SetupTest::agentCreationResult(KJob *job) 0082 { 0083 qCDebug(AKONADITEST_LOG) << "Agent created"; 0084 --mSetupJobCount; 0085 if (job->error()) { 0086 qCritical() << "Failed to create agent:" << job->errorString(); 0087 setupFailed(); 0088 } else { 0089 const bool needsSync = job->property("sync").toBool(); 0090 const auto instance = qobject_cast<Akonadi::AgentInstanceCreateJob *>(job)->instance(); 0091 qCDebug(AKONADITEST_LOG) << "Agent" << instance.identifier() << "created"; 0092 if (needsSync) { 0093 ++mSetupJobCount; 0094 qCDebug(AKONADITEST_LOG) << "Scheduling Agent sync of" << instance.identifier(); 0095 auto sync = new Akonadi::ResourceSynchronizationJob(instance, this); 0096 connect(sync, &Akonadi::ResourceSynchronizationJob::result, this, &SetupTest::synchronizationResult); 0097 sync->start(); 0098 } 0099 } 0100 0101 checkSetupDone(); 0102 } 0103 0104 void SetupTest::synchronizationResult(KJob *job) 0105 { 0106 auto instance = qobject_cast<Akonadi::ResourceSynchronizationJob *>(job)->resource(); 0107 qCDebug(AKONADITEST_LOG) << "Sync of" << instance.identifier() << "done"; 0108 0109 --mSetupJobCount; 0110 if (job->error()) { 0111 qCritical() << job->errorString(); 0112 setupFailed(); 0113 } 0114 0115 checkSetupDone(); 0116 } 0117 0118 void SetupTest::serverStateChanged(Akonadi::ServerManager::State state) 0119 { 0120 if (state == Akonadi::ServerManager::Running) { 0121 setupAgents(); 0122 } else if (mShuttingDown && state == Akonadi::ServerManager::NotRunning) { 0123 shutdownHarder(); 0124 } 0125 } 0126 0127 void SetupTest::copyXdgDirectory(const QString &src, const QString &dst) 0128 { 0129 qCDebug(AKONADITEST_LOG) << "Copying" << src << "to" << dst; 0130 const QDir srcDir(src); 0131 const auto entries = srcDir.entryInfoList(QDir::Dirs | QDir::Files | QDir::NoSymLinks | QDir::NoDotAndDotDot); 0132 for (const auto &fi : entries) { 0133 if (fi.isDir()) { 0134 if (fi.fileName() == QLatin1StringView("akonadi")) { 0135 // namespace according to instance identifier 0136 #ifdef Q_OS_WIN 0137 const bool isXdgConfig = src.contains(QLatin1StringView("/xdgconfig/")); 0138 copyDirectory(fi.absoluteFilePath(), 0139 dst + QStringLiteral("/akonadi/") + (isXdgConfig ? QStringLiteral("config/") : QStringLiteral("data/")) 0140 + QStringLiteral("instance/") + instanceId()); 0141 #else 0142 copyDirectory(fi.absoluteFilePath(), dst + QStringLiteral("/akonadi/instance/") + instanceId()); 0143 #endif 0144 } else { 0145 copyDirectory(fi.absoluteFilePath(), dst + QLatin1Char('/') + fi.fileName()); 0146 } 0147 } else { 0148 if (fi.fileName().startsWith(QLatin1StringView("akonadi_")) && fi.fileName().endsWith(QLatin1StringView("rc"))) { 0149 // namespace according to instance identifier 0150 const QString baseName = fi.fileName().left(fi.fileName().size() - 2); 0151 const QString dstPath = dst + QLatin1Char('/') + Akonadi::ServerManager::addNamespace(baseName) + QStringLiteral("rc"); 0152 if (!QFile::copy(fi.absoluteFilePath(), dstPath)) { 0153 qCWarning(AKONADITEST_LOG) << "Failed to copy" << fi.absoluteFilePath() << "to" << dstPath; 0154 } 0155 } else { 0156 const QString dstPath = dst + QLatin1Char('/') + fi.fileName(); 0157 if (!QFile::copy(fi.absoluteFilePath(), dstPath)) { 0158 qCWarning(AKONADITEST_LOG) << "Failed to copy" << fi.absoluteFilePath() << "to" << dstPath; 0159 } 0160 } 0161 } 0162 } 0163 } 0164 0165 void SetupTest::copyDirectory(const QString &src, const QString &dst) 0166 { 0167 const QDir srcDir(src); 0168 QDir::root().mkpath(dst); 0169 const auto entries = srcDir.entryInfoList(QDir::Dirs | QDir::Files | QDir::NoSymLinks | QDir::NoDotAndDotDot); 0170 for (const auto &fi : entries) { 0171 const QString dstPath = dst + QLatin1Char('/') + fi.fileName(); 0172 if (fi.isDir()) { 0173 copyDirectory(fi.absoluteFilePath(), dstPath); 0174 } else { 0175 if (!QFile::copy(fi.absoluteFilePath(), dstPath)) { 0176 qCWarning(AKONADITEST_LOG) << "Failed to copy" << fi.absoluteFilePath() << "to" << dstPath; 0177 } 0178 } 0179 } 0180 } 0181 0182 void SetupTest::createTempEnvironment() 0183 { 0184 qCDebug(AKONADITEST_LOG) << "Creating test environment in" << basePath(); 0185 0186 const Config *config = Config::instance(); 0187 #ifdef Q_OS_WIN 0188 // Always copy the generic xdgconfig dir 0189 copyXdgDirectory(config->basePath() + QStringLiteral("/xdgconfig"), basePath()); 0190 if (!config->xdgConfigHome().isEmpty()) { 0191 copyXdgDirectory(config->xdgConfigHome(), basePath()); 0192 } 0193 copyXdgDirectory(config->xdgDataHome(), basePath()); 0194 setEnvironmentVariable("XDG_DATA_HOME", basePath()); 0195 setEnvironmentVariable("XDG_CONFIG_HOME", basePath()); 0196 writeAkonadiserverrc(basePath() + QStringLiteral("/akonadi/config/instance/%1/akonadiserverrc").arg(instanceId())); 0197 #else 0198 const QDir tmpDir(basePath()); 0199 const QString testRunnerDataDir = QStringLiteral("data"); 0200 const QString testRunnerConfigDir = QStringLiteral("config"); 0201 const QString testRunnerTmpDir = QStringLiteral("tmp"); 0202 0203 tmpDir.mkpath(testRunnerConfigDir); 0204 tmpDir.mkpath(testRunnerDataDir); 0205 tmpDir.mkpath(testRunnerTmpDir); 0206 0207 // Always copy the generic xdgconfig dir 0208 copyXdgDirectory(config->basePath() + QStringLiteral("/xdgconfig"), basePath() + testRunnerConfigDir); 0209 if (!config->xdgConfigHome().isEmpty()) { 0210 copyXdgDirectory(config->xdgConfigHome(), basePath() + testRunnerConfigDir); 0211 } 0212 copyXdgDirectory(config->xdgDataHome(), basePath() + testRunnerDataDir); 0213 0214 setEnvironmentVariable("XDG_DATA_HOME", basePath() + testRunnerDataDir); 0215 setEnvironmentVariable("XDG_CONFIG_HOME", basePath() + testRunnerConfigDir); 0216 setEnvironmentVariable("TMPDIR", basePath() + testRunnerTmpDir); 0217 writeAkonadiserverrc(basePath() + testRunnerConfigDir + QStringLiteral("/akonadi/instance/%1/akonadiserverrc").arg(instanceId())); 0218 #endif 0219 0220 QString backend; 0221 if (Config::instance()->dbBackend() == QLatin1StringView("pgsql")) { 0222 backend = QStringLiteral("postgresql"); 0223 } else { 0224 backend = Config::instance()->dbBackend(); 0225 } 0226 setEnvironmentVariable("TESTRUNNER_DB_ENVIRONMENT", backend); 0227 } 0228 0229 void SetupTest::writeAkonadiserverrc(const QString &path) 0230 { 0231 QString backend; 0232 if (Config::instance()->dbBackend() == QLatin1StringView("sqlite")) { 0233 backend = QStringLiteral("QSQLITE"); 0234 } else if (Config::instance()->dbBackend() == QLatin1StringView("mysql")) { 0235 backend = QStringLiteral("QMYSQL"); 0236 } else if (Config::instance()->dbBackend() == QLatin1StringView("pgsql")) { 0237 backend = QStringLiteral("QPSQL"); 0238 } else { 0239 qCCritical(AKONADITEST_LOG, "Invalid backend name %s", qPrintable(backend)); 0240 return; 0241 } 0242 0243 QSettings settings(path, QSettings::IniFormat); 0244 settings.beginGroup(QStringLiteral("General")); 0245 settings.setValue(QStringLiteral("Driver"), backend); 0246 settings.endGroup(); 0247 settings.beginGroup(QStringLiteral("Search")); 0248 settings.setValue(QStringLiteral("Manager"), QStringLiteral("Dummy")); 0249 settings.endGroup(); 0250 settings.beginGroup(QStringLiteral("Debug")); 0251 settings.setValue(QStringLiteral("Tracer"), QStringLiteral("null")); 0252 settings.endGroup(); 0253 qCDebug(AKONADITEST_LOG) << "Written akonadiserverrc to" << settings.fileName(); 0254 } 0255 0256 void SetupTest::cleanTempEnvironment() const 0257 { 0258 #ifdef Q_OS_WIN 0259 QDir(basePath() + QStringLiteral("akonadi/config/instance/") + instanceId()).removeRecursively(); 0260 QDir(basePath() + QStringLiteral("akonadi/data/instance/") + instanceId()).removeRecursively(); 0261 #else 0262 QDir(basePath()).removeRecursively(); 0263 #endif 0264 } 0265 0266 SetupTest::SetupTest() 0267 : mAkonadiDaemonProcess(nullptr) 0268 , mShuttingDown(false) 0269 , mAgentsCreated(false) 0270 , mTrackAkonadiProcess(true) 0271 , mSetupJobCount(0) 0272 , mExitCode(0) 0273 { 0274 setupInstanceId(); 0275 cleanTempEnvironment(); 0276 createTempEnvironment(); 0277 0278 // switch off agent auto-starting by default, can be re-enabled if really needed inside the config.xml 0279 setEnvironmentVariable("AKONADI_DISABLE_AGENT_AUTOSTART", QStringLiteral("true")); 0280 setEnvironmentVariable("AKONADI_TESTRUNNER_PID", QString::number(QCoreApplication::instance()->applicationPid())); 0281 // enable all debugging, so we get some useful information when test fails 0282 setEnvironmentVariable("QT_LOGGING_RULES", 0283 QStringLiteral("* = true\n" 0284 "qt.* = false\n" 0285 "kf5.coreaddons.desktopparser.debug = false")); 0286 0287 setEnvironmentVariable("KIO_DISABLE_CACHE_CLEANER", QStringLiteral("yes")); 0288 0289 QHashIterator<QString, QString> iter(Config::instance()->envVars()); 0290 while (iter.hasNext()) { 0291 iter.next(); 0292 qCDebug(AKONADITEST_LOG) << "Setting environment variable" << iter.key() << "=" << iter.value(); 0293 setEnvironmentVariable(iter.key().toLocal8Bit(), iter.value()); 0294 } 0295 0296 // No kres-migrator please 0297 KConfig migratorConfig(basePath() + QStringLiteral("config/kres-migratorrc")); 0298 KConfigGroup migrationCfg(&migratorConfig, QStringLiteral("Migration")); 0299 migrationCfg.writeEntry("Enabled", false); 0300 0301 connect(Akonadi::ServerManager::self(), &Akonadi::ServerManager::stateChanged, this, &SetupTest::serverStateChanged); 0302 0303 QDBusConnection::sessionBus().registerService(QStringLiteral("org.kde.Akonadi.Testrunner-") 0304 + QString::number(QCoreApplication::instance()->applicationPid())); 0305 QDBusConnection::sessionBus().registerObject(QStringLiteral("/"), this, QDBusConnection::ExportScriptableSlots); 0306 } 0307 0308 SetupTest::~SetupTest() 0309 { 0310 cleanTempEnvironment(); 0311 } 0312 0313 void SetupTest::shutdown() 0314 { 0315 if (mShuttingDown) { 0316 return; 0317 } 0318 mShuttingDown = true; 0319 0320 switch (Akonadi::ServerManager::self()->state()) { 0321 case Akonadi::ServerManager::Running: 0322 case Akonadi::ServerManager::Starting: 0323 case Akonadi::ServerManager::Upgrading: 0324 qCInfo(AKONADITEST_LOG) << "Shutting down Akonadi control..."; 0325 Akonadi::ServerManager::self()->stop(); 0326 // safety timeout 0327 QTimer::singleShot(30s, this, &SetupTest::shutdownHarder); 0328 break; 0329 case Akonadi::ServerManager::NotRunning: 0330 case Akonadi::ServerManager::Broken: 0331 shutdownHarder(); 0332 break; 0333 case Akonadi::ServerManager::Stopping: 0334 // safety timeout 0335 QTimer::singleShot(30s, this, &SetupTest::shutdownHarder); 0336 break; 0337 } 0338 } 0339 0340 void SetupTest::shutdownHarder() 0341 { 0342 qCDebug(AKONADITEST_LOG) << "Forcing akonaditest shutdown"; 0343 mShuttingDown = false; 0344 stopAkonadiDaemon(); 0345 QCoreApplication::instance()->exit(mExitCode); 0346 } 0347 0348 void SetupTest::restartAkonadiServer() 0349 { 0350 qCDebug(AKONADITEST_LOG) << "Restarting Akonadi"; 0351 disconnect(mAkonadiDaemonProcess.get(), &KProcess::finished, this, nullptr); 0352 Akonadi::ServerManager::self()->stop(); 0353 const bool shutdownResult = mAkonadiDaemonProcess->waitForFinished(); 0354 if (!shutdownResult) { 0355 qCWarning(AKONADITEST_LOG) << "Akonadi control did not shut down in time, killing it."; 0356 mAkonadiDaemonProcess->kill(); 0357 } 0358 // we don't use Control::start() since we want to be able to kill 0359 // it forcefully, if necessary, and know the pid 0360 startAkonadiDaemon(); 0361 // from here on, the server exiting is an error again 0362 connect(mAkonadiDaemonProcess.get(), &KProcess::finished, this, &SetupTest::slotAkonadiDaemonProcessFinished); 0363 } 0364 0365 QString SetupTest::basePath() const 0366 { 0367 #ifdef Q_OS_WIN 0368 // On Windows we are forced to share the same data directory as production instances 0369 // because there's no way to override QStandardPaths like we can on Unix. 0370 // This means that on Windows we rely on Instances providing us the necessary isolation 0371 return QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); 0372 #else 0373 QString sysTempDirPath = QDir::tempPath(); 0374 #ifdef Q_OS_UNIX 0375 // QDir::tempPath() makes sure to use the fully sym-link exploded 0376 // absolute path to the temp dir. That is nice, but on OSX it makes 0377 // that path really long. MySQL chokes on this, for it's socket path, 0378 // so work around that 0379 sysTempDirPath = QStringLiteral("/tmp"); 0380 #endif 0381 0382 const QDir sysTempDir(sysTempDirPath); 0383 const QString tempDir = QStringLiteral("/aktestrunner-%1/").arg(QCoreApplication::instance()->applicationPid()); 0384 if (!sysTempDir.exists(tempDir)) { 0385 sysTempDir.mkdir(tempDir); 0386 } 0387 return sysTempDirPath + tempDir; 0388 #endif 0389 } 0390 0391 void SetupTest::slotAkonadiDaemonProcessFinished(int exitCode) 0392 { 0393 if (mTrackAkonadiProcess || exitCode != EXIT_SUCCESS) { 0394 qCWarning(AKONADITEST_LOG) << "Akonadi server process was terminated externally!"; 0395 Q_EMIT serverExited(exitCode); 0396 } 0397 mAkonadiDaemonProcess.reset(); 0398 } 0399 0400 void SetupTest::trackAkonadiProcess(bool track) 0401 { 0402 mTrackAkonadiProcess = track; 0403 } 0404 0405 QString SetupTest::instanceId() const 0406 { 0407 return QStringLiteral("testrunner-") + QString::number(QCoreApplication::instance()->applicationPid()); 0408 } 0409 0410 void SetupTest::setupInstanceId() 0411 { 0412 setEnvironmentVariable("AKONADI_INSTANCE", instanceId()); 0413 } 0414 0415 void SetupTest::checkSetupDone() 0416 { 0417 qCDebug(AKONADITEST_LOG) << "checkSetupDone: pendingJobs =" << mSetupJobCount << ", exitCode =" << mExitCode; 0418 if (mSetupJobCount == 0) { 0419 if (mExitCode != 0) { 0420 qCInfo(AKONADITEST_LOG) << "Setup has failed, aborting test."; 0421 shutdown(); 0422 } else { 0423 qCInfo(AKONADITEST_LOG) << "Setup successful"; 0424 Q_EMIT setupDone(); 0425 } 0426 } 0427 } 0428 0429 void SetupTest::setupFailed() 0430 { 0431 mExitCode = 1; 0432 } 0433 0434 void SetupTest::setEnvironmentVariable(const QByteArray &name, const QString &value) 0435 { 0436 mEnvVars.push_back(qMakePair(name, value.toLocal8Bit())); 0437 qputenv(name.constData(), value.toLatin1()); 0438 } 0439 0440 QList<SetupTest::EnvVar> SetupTest::environmentVariables() const 0441 { 0442 return mEnvVars; 0443 } 0444 0445 #include "moc_setup.cpp"