File indexing completed on 2024-11-10 04:40:19

0001 /*
0002  * SPDX-FileCopyrightText: 2014 Daniel Vrátil <dvratil@redhat.com>
0003  *
0004  * SPDX-License-Identifier: LGPL-2.1-or-later
0005  *
0006  */
0007 
0008 #include "fakeakonadiserver.h"
0009 #include "cachecleaner.h"
0010 #include "debuginterface.h"
0011 #include "fakeclient.h"
0012 #include "fakeconnection.h"
0013 #include "fakedatastore.h"
0014 #include "fakeintervalcheck.h"
0015 #include "fakeitemretrievalmanager.h"
0016 #include "fakesearchmanager.h"
0017 #include "inspectablenotificationcollector.h"
0018 #include "resourcemanager.h"
0019 #include "search/searchtaskmanager.h"
0020 #include "storage/collectionstatistics.h"
0021 #include "storagejanitor.h"
0022 
0023 #include <QBuffer>
0024 #include <QCoreApplication>
0025 #include <QDir>
0026 #include <QSettings>
0027 #include <QStandardPaths>
0028 #include <QTest>
0029 
0030 #include "private/scope_p.h"
0031 #include "private/standarddirs_p.h"
0032 #include "shared/akapplication.h"
0033 #include <ctime>
0034 
0035 #include "aklocalserver.h"
0036 #include "preprocessormanager.h"
0037 #include "search/searchmanager.h"
0038 #include "storage/datastore.h"
0039 #include "storage/dbconfig.h"
0040 #include "utils.h"
0041 
0042 using namespace Akonadi;
0043 using namespace Akonadi::Server;
0044 
0045 Q_DECLARE_METATYPE(Akonadi::Server::InspectableNotificationCollector *)
0046 
0047 TestScenario TestScenario::create(qint64 tag, TestScenario::Action action, const Protocol::CommandPtr &response)
0048 {
0049     TestScenario sc;
0050     sc.action = action;
0051 
0052     QBuffer buffer(&sc.data);
0053     buffer.open(QIODevice::ReadWrite);
0054     {
0055         Protocol::DataStream stream(&buffer);
0056         stream << tag;
0057         Protocol::serialize(stream, response);
0058         stream.flush();
0059     }
0060 
0061     {
0062         buffer.seek(0);
0063         Protocol::DataStream os(&buffer);
0064         qint64 cmpTag;
0065         os >> cmpTag;
0066         Q_ASSERT(cmpTag == tag);
0067         Protocol::CommandPtr cmpResp = Protocol::deserialize(os.device());
0068 
0069         bool ok = false;
0070         [cmpTag, tag, cmpResp, response, &ok]() {
0071             QCOMPARE(cmpTag, tag);
0072             QCOMPARE(cmpResp->type(), response->type());
0073             QCOMPARE(cmpResp->isResponse(), response->isResponse());
0074             QCOMPARE(Protocol::debugString(cmpResp), Protocol::debugString(response));
0075             QCOMPARE(*cmpResp, *response);
0076             ok = true;
0077         }();
0078         if (!ok) {
0079             sc.data.clear();
0080             return sc;
0081         }
0082     }
0083     return sc;
0084 }
0085 
0086 FakeAkonadiServer::FakeAkonadiServer()
0087 {
0088     qputenv("AKONADI_INSTANCE", qPrintable(instanceName()));
0089     qputenv("XDG_DATA_HOME", qPrintable(QString(basePath() + QLatin1StringView("/local"))));
0090     qputenv("XDG_CONFIG_HOME", qPrintable(QString(basePath() + QLatin1StringView("/config"))));
0091     qputenv("HOME", qPrintable(basePath()));
0092     qputenv("KDEHOME", qPrintable(basePath() + QLatin1StringView("/kdehome")));
0093 
0094     mClient = std::make_unique<FakeClient>();
0095 
0096     DataStore::setFactory(std::make_unique<FakeDataStoreFactory>(this));
0097 }
0098 
0099 FakeAkonadiServer::~FakeAkonadiServer()
0100 {
0101     FakeAkonadiServer::quit();
0102 }
0103 
0104 QString FakeAkonadiServer::basePath()
0105 {
0106     return QStandardPaths::writableLocation(QStandardPaths::TempLocation)
0107         + QStringLiteral("/akonadiserver-test-%1").arg(QCoreApplication::instance()->applicationPid());
0108 }
0109 
0110 QString FakeAkonadiServer::socketFile()
0111 {
0112     return basePath() % QStringLiteral("/local/share/akonadi/akonadiserver.socket");
0113 }
0114 
0115 QString FakeAkonadiServer::instanceName()
0116 {
0117     return QStringLiteral("akonadiserver-test-%1").arg(QCoreApplication::instance()->applicationPid());
0118 }
0119 
0120 TestScenario::List FakeAkonadiServer::loginScenario(const QByteArray &sessionId)
0121 {
0122     SchemaVersion schema = SchemaVersion::retrieveAll().at(0);
0123 
0124     auto hello = Protocol::HelloResponsePtr::create();
0125     hello->setServerName(QStringLiteral("Akonadi"));
0126     hello->setMessage(QStringLiteral("Not Really IMAP server"));
0127     hello->setProtocolVersion(Protocol::version());
0128     hello->setGeneration(schema.generation());
0129 
0130     return {TestScenario::create(0, TestScenario::ServerCmd, hello),
0131             TestScenario::create(1, TestScenario::ClientCmd, Protocol::LoginCommandPtr::create(sessionId.isEmpty() ? instanceName().toLatin1() : sessionId)),
0132             TestScenario::create(1, TestScenario::ServerCmd, Protocol::LoginResponsePtr::create())};
0133 }
0134 
0135 TestScenario::List FakeAkonadiServer::selectResourceScenario(const QString &name)
0136 {
0137     const Resource resource = Resource::retrieveByName(name);
0138     return {TestScenario::create(3, TestScenario::ClientCmd, Protocol::SelectResourceCommandPtr::create(resource.name())),
0139             TestScenario::create(3, TestScenario::ServerCmd, Protocol::SelectResourceResponsePtr::create())};
0140 }
0141 
0142 void FakeAkonadiServer::disableItemRetrievalManager()
0143 {
0144     mDisableItemRetrievalManager = true;
0145 }
0146 
0147 bool FakeAkonadiServer::init()
0148 {
0149     try {
0150         initFake();
0151     } catch (const FakeAkonadiServerException &e) {
0152         qWarning() << "Server exception: " << e.what();
0153         qFatal("Fake Akonadi Server failed to start up, aborting test");
0154     }
0155     return true;
0156 }
0157 
0158 void FakeAkonadiServer::initFake()
0159 {
0160     qDebug() << "==== Fake Akonadi Server starting up ====";
0161 
0162     qputenv("XDG_DATA_HOME", qPrintable(QString(basePath() + QLatin1StringView("/local"))));
0163     qputenv("XDG_CONFIG_HOME", qPrintable(QString(basePath() + QLatin1StringView("/config"))));
0164     qputenv("AKONADI_INSTANCE", qPrintable(instanceName()));
0165     QSettings settings(StandardDirs::serverConfigFile(StandardDirs::WriteOnly), QSettings::IniFormat);
0166     settings.beginGroup(QStringLiteral("General"));
0167     settings.setValue(QStringLiteral("Driver"), QLatin1StringView("QSQLITE"));
0168     settings.endGroup();
0169 
0170     settings.beginGroup(QStringLiteral("QSQLITE"));
0171     settings.setValue(QStringLiteral("Name"), QString(basePath() + QLatin1StringView("/local/share/akonadi/akonadi.db")));
0172     settings.endGroup();
0173     settings.sync();
0174 
0175     DbConfig *dbConfig = DbConfig::configuredDatabase();
0176     if (dbConfig->driverName() != QLatin1StringView("QSQLITE")) {
0177         throw FakeAkonadiServerException(QLatin1StringView("Unexpected driver specified. Expected QSQLITE, got ") + dbConfig->driverName());
0178     }
0179 
0180     const QLatin1StringView initCon("initConnection");
0181     {
0182         QSqlDatabase db = QSqlDatabase::addDatabase(DbConfig::configuredDatabase()->driverName(), initCon);
0183         DbConfig::configuredDatabase()->apply(db);
0184         db.setDatabaseName(DbConfig::configuredDatabase()->databaseName());
0185         if (!db.isDriverAvailable(DbConfig::configuredDatabase()->driverName())) {
0186             throw FakeAkonadiServerException(QStringLiteral("SQL driver %s not available").arg(db.driverName()));
0187         }
0188         if (!db.isValid()) {
0189             throw FakeAkonadiServerException("Got invalid database");
0190         }
0191         if (db.open()) {
0192             qWarning() << "Database" << dbConfig->configuredDatabase()->databaseName() << "already exists, the test is not running in a clean environment!";
0193         }
0194         db.close();
0195     }
0196 
0197     QSqlDatabase::removeDatabase(initCon);
0198 
0199     dbConfig->setup();
0200 
0201     mDataStore = static_cast<FakeDataStore *>(FakeDataStore::self());
0202     if (!mDataStore->database().isOpen()) {
0203         throw FakeAkonadiServerException("Failed to open database");
0204     }
0205     mDataStore->setPopulateDb(mPopulateDb);
0206     if (!mDataStore->init()) {
0207         throw FakeAkonadiServerException("Failed to initialize datastore");
0208     }
0209 
0210     mTracer = std::make_unique<Tracer>();
0211     mCollectionStats = std::make_unique<CollectionStatistics>();
0212     mCacheCleaner = AkThread::create<CacheCleaner>();
0213     if (!mDisableItemRetrievalManager) {
0214         mItemRetrieval = AkThread::create<FakeItemRetrievalManager>();
0215     }
0216     mAgentSearchManager = AkThread::create<SearchTaskManager>();
0217 
0218     mDebugInterface = std::make_unique<DebugInterface>(*mTracer);
0219     mResourceManager = std::make_unique<ResourceManager>(*mTracer);
0220     mPreprocessorManager = std::make_unique<PreprocessorManager>(*mTracer);
0221     mPreprocessorManager->setEnabled(false);
0222     mIntervalCheck = AkThread::create<FakeIntervalCheck>(*mItemRetrieval);
0223     mSearchManager = AkThread::create<FakeSearchManager>(*mAgentSearchManager);
0224     mStorageJanitor = AkThread::create<StorageJanitor>(this);
0225 
0226     qDebug() << "==== Fake Akonadi Server started ====";
0227 }
0228 
0229 bool FakeAkonadiServer::quit()
0230 {
0231     qDebug() << "==== Fake Akonadi Server shutting down ====";
0232 
0233     // Stop listening for connections
0234     if (mCmdServer) {
0235         mCmdServer->close();
0236     }
0237 
0238     if (!qEnvironmentVariableIsSet("AKONADI_TEST_NOCLEANUP")) {
0239         bool ok = QDir(basePath()).removeRecursively();
0240         qDebug() << "Cleaned up" << basePath() << "success=" << ok;
0241     } else {
0242         qDebug() << "Skipping clean up of" << basePath();
0243     }
0244 
0245     mConnection.reset();
0246     mClient.reset();
0247 
0248     mStorageJanitor.reset();
0249     mSearchManager.reset();
0250     mIntervalCheck.reset();
0251     mPreprocessorManager.reset();
0252     mResourceManager.reset();
0253     mDebugInterface.reset();
0254 
0255     mAgentSearchManager.reset();
0256     mItemRetrieval.reset();
0257     mCacheCleaner.reset();
0258     mCollectionStats.reset();
0259     mTracer.reset();
0260 
0261     if (mDataStore) {
0262         mDataStore->close();
0263     }
0264 
0265     qDebug() << "==== Fake Akonadi Server shut down ====";
0266     return true;
0267 }
0268 
0269 void FakeAkonadiServer::setScenarios(const TestScenario::List &scenarios)
0270 {
0271     mClient->setScenarios(scenarios);
0272 }
0273 
0274 void FakeAkonadiServer::newCmdConnection(quintptr socketDescriptor)
0275 {
0276     mConnection = AkThread::create<FakeConnection>(socketDescriptor, *this);
0277     mConnection->waitForInitialized();
0278 
0279     mNtfCollector = dynamic_cast<InspectableNotificationCollector *>(mConnection->notificationCollector());
0280     Q_ASSERT(mNtfCollector);
0281 
0282     mNotificationSpy.reset(new QSignalSpy(mNtfCollector, &Server::InspectableNotificationCollector::notifySignal));
0283     Q_ASSERT(mNotificationSpy->isValid());
0284 
0285     // Now start replaying the scenario
0286     mClient->startScenario();
0287 }
0288 
0289 void FakeAkonadiServer::runTest()
0290 {
0291     mCmdServer = std::make_unique<AkLocalServer>();
0292     connect(mCmdServer.get(), static_cast<void (AkLocalServer::*)(quintptr)>(&AkLocalServer::newConnection), this, &FakeAkonadiServer::newCmdConnection);
0293     QVERIFY(mCmdServer->listen(socketFile()));
0294 
0295     QEventLoop serverLoop;
0296     connect(mClient.get(), &QThread::finished, this, [this, &serverLoop]() { // clazy:exclude=lambda-in-connect
0297         disconnect(mClient.get(), &QThread::finished, this, nullptr);
0298         // Flush any pending notifications and wait for them before shutting down the event loop
0299         if (mNtfCollector->dispatchNotifications()) {
0300             mNotificationSpy->wait();
0301         }
0302 
0303         serverLoop.quit();
0304     });
0305 
0306     // Start the client: the client will connect to the server
0307     mClient->start();
0308 
0309     // Wait until the client disconnects, i.e. until the scenario is completed.
0310     serverLoop.exec();
0311 
0312     mCmdServer->close();
0313 
0314     mConnection.reset();
0315 }
0316 
0317 QSharedPointer<QSignalSpy> FakeAkonadiServer::notificationSpy() const
0318 {
0319     return mNotificationSpy;
0320 }
0321 
0322 void FakeAkonadiServer::setPopulateDb(bool populate)
0323 {
0324     mPopulateDb = populate;
0325 }
0326 
0327 #include "moc_fakeakonadiserver.cpp"