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"