fakeakonadiserver.cpp 11.7 KB
Newer Older
1
2
3
/*
 * Copyright (C) 2014  Daniel Vrátil <dvratil@redhat.com>
 *
4
5
6
7
 * 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.
8
 *
9
 * This library is distributed in the hope that it will be useful,
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
13
 *
14
15
16
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
17
18
19
20
 *
 */

#include "fakeakonadiserver.h"
21
#include "fakeconnection.h"
22
#include "fakedatastore.h"
23
#include "fakesearchmanager.h"
24
#include "fakeclient.h"
25
#include "fakeitemretrievalmanager.h"
26
#include "inspectablenotificationcollector.h"
27
#include "fakeintervalcheck.h"
28
#include "storage/collectionstatistics.h"
29
30
31
32
33
#include "cachecleaner.h"
#include "storagejanitor.h"
#include "resourcemanager.h"
#include "debuginterface.h"
#include "search/searchtaskmanager.h"
34
35
36
37

#include <QSettings>
#include <QCoreApplication>
#include <QDir>
38
#include <QTest>
Laurent Montel's avatar
Laurent Montel committed
39
#include <QBuffer>
Daniel Vrátil's avatar
Daniel Vrátil committed
40
#include <QStandardPaths>
41

42
#include <ctime>
43
44
#include <private/protocol_p.h>
#include <private/scope_p.h>
45
#include <private/standarddirs_p.h>
46
47
#include <shared/akapplication.h>

48
#include "aklocalserver.h"
49
50
51
52
53
#include "storage/dbconfig.h"
#include "storage/datastore.h"
#include "preprocessormanager.h"
#include "search/searchmanager.h"
#include "utils.h"
54
55
56
57

using namespace Akonadi;
using namespace Akonadi::Server;

58
Q_DECLARE_METATYPE(Akonadi::Server::InspectableNotificationCollector*)
59
60

TestScenario TestScenario::create(qint64 tag, TestScenario::Action action,
61
                                  const Protocol::CommandPtr &response)
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
{
    TestScenario sc;
    sc.action = action;

    QBuffer buffer(&sc.data);
    buffer.open(QIODevice::ReadWrite);
    {
        QDataStream stream(&buffer);
        stream << tag;
        Protocol::serialize(&buffer, response);
    }

    {
        buffer.seek(0);
        QDataStream os(&buffer);
        qint64 cmpTag;
        os >> cmpTag;
        Q_ASSERT(cmpTag == tag);
80
        Protocol::CommandPtr cmpResp = Protocol::deserialize(os.device());
81
82
83
84

        bool ok = false;
        [cmpTag, tag, cmpResp, response, &ok]() {
            QCOMPARE(cmpTag, tag);
85
86
87
88
            QCOMPARE(cmpResp->type(), response->type());
            QCOMPARE(cmpResp->isResponse(), response->isResponse());
            QCOMPARE(Protocol::debugString(cmpResp), Protocol::debugString(response));
            QCOMPARE(*cmpResp, *response);
89
90
91
92
93
94
95
96
97
98
99
            ok = true;
        }();
        if (!ok) {
            sc.data.clear();
            return sc;
        }
    }
    return sc;
}


100
FakeAkonadiServer::FakeAkonadiServer()
101
    : AkonadiServer()
102
{
103
    qputenv("AKONADI_INSTANCE", qPrintable(instanceName()));
104
105
    qputenv("XDG_DATA_HOME", qPrintable(QString(basePath() + QLatin1String("/local"))));
    qputenv("XDG_CONFIG_HOME", qPrintable(QString(basePath() + QLatin1String("/config"))));
106
107
108
    qputenv("HOME", qPrintable(basePath()));
    qputenv("KDEHOME", qPrintable(basePath() + QLatin1String("/kdehome")));

109
    mClient = std::make_unique<FakeClient>();
110

111
    DataStore::setFactory(std::make_unique<FakeDataStoreFactory>(*this));
112
113
}

114
115
116
117
FakeAkonadiServer::~FakeAkonadiServer()
{
    quit();
}
118

119
QString FakeAkonadiServer::basePath()
120
{
Daniel Vrátil's avatar
Daniel Vrátil committed
121
122
    return QStandardPaths::writableLocation(QStandardPaths::TempLocation)
                + QStringLiteral("/akonadiserver-test-%1").arg(QCoreApplication::instance()->applicationPid());
123
124
}

125
QString FakeAkonadiServer::socketFile()
126
{
127
    return basePath() % QStringLiteral("/local/share/akonadi/akonadiserver.socket");
128
129
}

130
QString FakeAkonadiServer::instanceName()
131
{
Laurent Montel's avatar
Laurent Montel committed
132
    return QStringLiteral("akonadiserver-test-%1").arg(QCoreApplication::instance()->applicationPid());
133
134
}

135
TestScenario::List FakeAkonadiServer::loginScenario(const QByteArray &sessionId)
136
{
137
138
    SchemaVersion schema = SchemaVersion::retrieveAll().first();

139
140
141
142
143
    auto hello = Protocol::HelloResponsePtr::create();
    hello->setServerName(QStringLiteral("Akonadi"));
    hello->setMessage(QStringLiteral("Not Really IMAP server"));
    hello->setProtocolVersion(Protocol::version());
    hello->setGeneration(schema.generation());
144

145
    return {
146
        TestScenario::create(0, TestScenario::ServerCmd, hello),
147
        TestScenario::create(1,TestScenario::ClientCmd,
148
                             Protocol::LoginCommandPtr::create(sessionId.isEmpty() ? instanceName().toLatin1() : sessionId)),
149
        TestScenario::create(1, TestScenario::ServerCmd,
150
                             Protocol::LoginResponsePtr::create())
151
    };
152
153
}

154
TestScenario::List FakeAkonadiServer::selectResourceScenario(const QString &name)
155
156
{
    const Resource resource = Resource::retrieveByName(name);
157
158
    return {
        TestScenario::create(3, TestScenario::ClientCmd,
159
                             Protocol::SelectResourceCommandPtr::create(resource.name())),
160
        TestScenario::create(3, TestScenario::ServerCmd,
161
                             Protocol::SelectResourceResponsePtr::create())
162
    };
163
164
}

165
166
167
168
169
void FakeAkonadiServer::disableItemRetrievalManager()
{
    mDisableItemRetrievalManager = true;
}

170
bool FakeAkonadiServer::init()
171
172
173
174
175
176
177
178
179
180
181
{
    try {
        initFake();
    } catch (const FakeAkonadiServerException &e) {
        qWarning() << "Server exception: " << e.what();
        qFatal("Fake Akonadi Server failed to start up, aborting test");
    }
    return true;
}

void FakeAkonadiServer::initFake()
182
183
184
185
186
187
{
    qDebug() << "==== Fake Akonadi Server starting up ====";

    qputenv("XDG_DATA_HOME", qPrintable(QString(basePath() + QLatin1String("/local"))));
    qputenv("XDG_CONFIG_HOME", qPrintable(QString(basePath() + QLatin1String("/config"))));
    qputenv("AKONADI_INSTANCE", qPrintable(instanceName()));
188
    QSettings settings(StandardDirs::serverConfigFile(StandardDirs::WriteOnly), QSettings::IniFormat);
Laurent Montel's avatar
Laurent Montel committed
189
190
    settings.beginGroup(QStringLiteral("General"));
    settings.setValue(QStringLiteral("Driver"), QLatin1String("QSQLITE3"));
191
192
    settings.endGroup();

Laurent Montel's avatar
Laurent Montel committed
193
194
    settings.beginGroup(QStringLiteral("QSQLITE3"));
    settings.setValue(QStringLiteral("Name"), QString(basePath() + QLatin1String("/local/share/akonadi/akonadi.db")));
195
196
197
198
199
    settings.endGroup();
    settings.sync();

    DbConfig *dbConfig = DbConfig::configuredDatabase();
    if (dbConfig->driverName() != QLatin1String("QSQLITE3")) {
200
        throw FakeAkonadiServerException(QLatin1String("Unexpected driver specified. Expected QSQLITE3, got ") + dbConfig->driverName());
201
202
203
    }

    const QLatin1String initCon("initConnection");
204
205
206
207
208
    {
        QSqlDatabase db = QSqlDatabase::addDatabase(DbConfig::configuredDatabase()->driverName(), initCon);
        DbConfig::configuredDatabase()->apply(db);
        db.setDatabaseName(DbConfig::configuredDatabase()->databaseName());
        if (!db.isDriverAvailable(DbConfig::configuredDatabase()->driverName())) {
Laurent Montel's avatar
Laurent Montel committed
209
            throw FakeAkonadiServerException(QStringLiteral("SQL driver %s not available").arg(db.driverName()));
210
211
212
213
214
215
216
217
        }
        if (!db.isValid()) {
            throw FakeAkonadiServerException("Got invalid database");
        }
        if (db.open()) {
            qWarning() << "Database" << dbConfig->configuredDatabase()->databaseName() << "already exists, the test is not running in a clean environment!";
        }
        db.close();
218
    }
219

220
221
222
223
    QSqlDatabase::removeDatabase(initCon);

    dbConfig->setup();

224
    mDataStore = static_cast<FakeDataStore *>(FakeDataStore::self());
225
    mDataStore->setPopulateDb(mPopulateDb);
226
    if (!mDataStore->init()) {
227
        throw FakeAkonadiServerException("Failed to initialize datastore");
228
229
    }

230
    mTracer = std::make_unique<Tracer>();
231
    mCollectionStats = std::make_unique<CollectionStatistics>();
232
    mCacheCleaner = std::make_unique<CacheCleaner>();
233
    if (!mDisableItemRetrievalManager) {
234
        mItemRetrieval = std::make_unique<FakeItemRetrievalManager>();
235
    }
236
    mAgentSearchManager = std::make_unique<SearchTaskManager>();
237

238
239
240
241
242
243
244
    mDebugInterface = std::make_unique<DebugInterface>(*mTracer.get());
    mResourceManager = std::make_unique<ResourceManager>(*mTracer.get());
    mPreprocessorManager = std::make_unique<PreprocessorManager>(*mTracer.get());
    mPreprocessorManager->setEnabled(false);
    mIntervalCheck = std::make_unique<FakeIntervalCheck>(*mItemRetrieval.get());
    mSearchManager = std::make_unique<FakeSearchManager>(*mAgentSearchManager.get());
    mStorageJanitor = std::make_unique<StorageJanitor>(*this);
245

246
247
248
    qDebug() << "==== Fake Akonadi Server started ====";
}

249
bool FakeAkonadiServer::quit()
250
251
252
{
    qDebug() << "==== Fake Akonadi Server shutting down ====";

253
    // Stop listening for connections
254
255
256
    if (mCmdServer) {
        mCmdServer->close();
    }
257

258
    if (!qEnvironmentVariableIsSet("AKONADI_TEST_NOCLEANUP")) {
259
260
        bool ok = QDir(basePath()).removeRecursively();
        qDebug() << "Cleaned up" << basePath() << "success=" << ok;
261
262
263
    } else {
        qDebug() << "Skipping clean up of" << basePath();
    }
264

265
266
267
    mConnection.reset();
    mClient.reset();

268
    mStorageJanitor.reset();
269
270
    mSearchManager.reset();
    mIntervalCheck.reset();
271
272
273
274
275
276
277
278
279
    mPreprocessorManager.reset();
    mResourceManager.reset();
    mDebugInterface.reset();

    mAgentSearchManager.reset();
    mItemRetrieval.reset();
    mCacheCleaner.reset();
    mCollectionStats.reset();
    mTracer.reset();
280
281
282
283

    if (mDataStore) {
        mDataStore->close();
    }
284
285
286
287
288

    qDebug() << "==== Fake Akonadi Server shut down ====";
    return true;
}

289
void FakeAkonadiServer::setScenarios(const TestScenario::List &scenarios)
290
{
291
    mClient->setScenarios(scenarios);
292
293
}

294
void FakeAkonadiServer::newCmdConnection(quintptr socketDescriptor)
295
{
296
    mConnection = std::make_unique<FakeConnection>(socketDescriptor, *this);
297

David Faure's avatar
David Faure committed
298
    // Connection is its own thread, so we have to make sure we get collector
299
    // from DataStore of the Connection's thread, not ours
300
    NotificationCollector *collector = nullptr;
301
    QMetaObject::invokeMethod(mConnection.get(), "notificationCollector", Qt::BlockingQueuedConnection,
302
303
                              Q_RETURN_ARG(Akonadi::Server::NotificationCollector*, collector));
    mNtfCollector = dynamic_cast<InspectableNotificationCollector*>(collector);
304
    Q_ASSERT(mNtfCollector);
305
    mNotificationSpy.reset(new QSignalSpy(mNtfCollector, &Server::InspectableNotificationCollector::notifySignal));
306
    Q_ASSERT(mNotificationSpy->isValid());
307
308
}

309
310
void FakeAkonadiServer::runTest()
{
311
312
    mCmdServer = std::make_unique<AkLocalServer>();
    connect(mCmdServer.get(), static_cast<void(AkLocalServer::*)(quintptr)>(&AkLocalServer::newConnection),
313
            this, &FakeAkonadiServer::newCmdConnection);
314
    QVERIFY(mCmdServer->listen(socketFile()));
315

316
    QEventLoop serverLoop;
317
318
319
320
321
322
323
324
325
326
327
    connect(mClient.get(), &QThread::finished,
            this, [this, &serverLoop]() {
                disconnect(mClient.get(), &QThread::finished, this, nullptr);
                // Flush any pending notifications and wait for them
                // before shutting down the event loop
                if (mNtfCollector->dispatchNotifications()) {
                    mNotificationSpy->wait();
                }

                serverLoop.quit();
            });
328
329
330
331
332
333

    // Start the client: the client will connect to the server and will
    // start playing the scenario
    mClient->start();

    // Wait until the client disconnects, i.e. until the scenario is completed.
334
    serverLoop.exec();
335

336
    mCmdServer->close();
337
338
}

339
QSharedPointer<QSignalSpy> FakeAkonadiServer::notificationSpy() const
340
341
342
{
    return mNotificationSpy;
}
343
344
345
346
347

void FakeAkonadiServer::setPopulateDb(bool populate)
{
    mPopulateDb = populate;
}