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 "fakeclient.h"
0009 
0010 #include "private/datastream_p_p.h"
0011 #include "private/protocol_exception_p.h"
0012 #include "private/protocol_p.h"
0013 
0014 #include <QBuffer>
0015 #include <QLocalSocket>
0016 #include <QMutexLocker>
0017 #include <QTest>
0018 
0019 #define CLIENT_COMPARE(actual, expected, ...)                                                                                                                  \
0020     do {                                                                                                                                                       \
0021         if (!QTest::qCompare(actual, expected, #actual, #expected, __FILE__, __LINE__)) {                                                                      \
0022             mSocket->disconnectFromServer();                                                                                                                   \
0023             return __VA_ARGS__;                                                                                                                                \
0024         }                                                                                                                                                      \
0025     } while (0)
0026 
0027 #define CLIENT_VERIFY(statement, ...)                                                                                                                          \
0028     do {                                                                                                                                                       \
0029         if (!QTest::qVerify((statement), #statement, "", __FILE__, __LINE__)) {                                                                                \
0030             mSocket->disconnectFromServer();                                                                                                                   \
0031             return __VA_ARGS__;                                                                                                                                \
0032         }                                                                                                                                                      \
0033     } while (0)
0034 
0035 using namespace Akonadi;
0036 using namespace Akonadi::Server;
0037 
0038 FakeClient::FakeClient(QObject *parent)
0039     : QThread(parent)
0040     , mMutex()
0041 {
0042     moveToThread(this);
0043 }
0044 
0045 FakeClient::~FakeClient()
0046 {
0047 }
0048 
0049 void FakeClient::setScenarios(const TestScenario::List &scenarios)
0050 {
0051     mScenarios = scenarios;
0052 }
0053 
0054 bool FakeClient::isScenarioDone() const
0055 {
0056     QMutexLocker locker(&mMutex);
0057     return mScenarios.isEmpty();
0058 }
0059 
0060 bool FakeClient::dataAvailable()
0061 {
0062     QMutexLocker locker(&mMutex);
0063 
0064     CLIENT_VERIFY(!mScenarios.isEmpty(), false);
0065 
0066     readServerPart();
0067     writeClientPart();
0068 
0069     return true;
0070 }
0071 
0072 void FakeClient::readServerPart()
0073 {
0074     while (!mScenarios.isEmpty() && (mScenarios.at(0).action == TestScenario::ServerCmd || mScenarios.at(0).action == TestScenario::Ignore)) {
0075         TestScenario scenario = mScenarios.takeFirst();
0076         if (scenario.action == TestScenario::Ignore) {
0077             const int count = scenario.data.toInt();
0078 
0079             // Read and throw away all "count" responses. Useful for scenarios
0080             // with thousands of responses
0081             qint64 tag;
0082             for (int i = 0; i < count; ++i) {
0083                 mStream >> tag;
0084                 Protocol::deserialize(mStream.device());
0085             }
0086         } else {
0087             QBuffer buffer(&scenario.data);
0088             buffer.open(QIODevice::ReadOnly);
0089             Protocol::DataStream expectedStream(&buffer);
0090             qint64 expectedTag;
0091             qint64 actualTag;
0092 
0093             expectedStream >> expectedTag;
0094             const auto expectedCommand = Protocol::deserialize(expectedStream.device());
0095             try {
0096                 while (static_cast<size_t>(mSocket->bytesAvailable()) < sizeof(qint64)) {
0097                     Protocol::DataStream::waitForData(mSocket, 5000);
0098                 }
0099             } catch (const ProtocolException &e) {
0100                 qDebug() << "ProtocolException:" << e.what();
0101                 qDebug() << "Expected response:" << Protocol::debugString(expectedCommand);
0102                 CLIENT_VERIFY(false);
0103             }
0104 
0105             mStream >> actualTag;
0106             CLIENT_COMPARE(actualTag, expectedTag);
0107 
0108             Protocol::CommandPtr actualCommand;
0109             try {
0110                 actualCommand = Protocol::deserialize(mStream.device());
0111             } catch (const ProtocolException &e) {
0112                 qDebug() << "Protocol exception:" << e.what();
0113                 qDebug() << "Expected response:" << Protocol::debugString(expectedCommand);
0114                 CLIENT_VERIFY(false);
0115             }
0116 
0117             if (actualCommand->type() != expectedCommand->type()) {
0118                 qDebug() << "Actual command:  " << Protocol::debugString(actualCommand);
0119                 qDebug() << "Expected Command:" << Protocol::debugString(expectedCommand);
0120             }
0121             CLIENT_COMPARE(actualCommand->type(), expectedCommand->type());
0122             CLIENT_COMPARE(actualCommand->isResponse(), expectedCommand->isResponse());
0123             if (*actualCommand != *expectedCommand) {
0124                 qDebug() << "Actual command:  " << Protocol::debugString(actualCommand);
0125                 qDebug() << "Expected Command:" << Protocol::debugString(expectedCommand);
0126             }
0127 
0128             CLIENT_COMPARE(*actualCommand, *expectedCommand);
0129         }
0130     }
0131 
0132     if (!mScenarios.isEmpty()) {
0133         CLIENT_VERIFY(mScenarios.at(0).action == TestScenario::ClientCmd || mScenarios.at(0).action == TestScenario::Wait
0134                       || mScenarios.at(0).action == TestScenario::Quit);
0135     } else {
0136         // Server replied and there's nothing else to send, then quit
0137         mSocket->disconnectFromServer();
0138     }
0139 }
0140 
0141 void FakeClient::writeClientPart()
0142 {
0143     while (!mScenarios.isEmpty() && (mScenarios.at(0).action == TestScenario::ClientCmd || mScenarios.at(0).action == TestScenario::Wait)) {
0144         const TestScenario rule = mScenarios.takeFirst();
0145 
0146         if (rule.action == TestScenario::ClientCmd) {
0147             mSocket->write(rule.data);
0148             CLIENT_VERIFY(mSocket->waitForBytesWritten());
0149         } else {
0150             const int timeout = rule.data.toInt();
0151             QTest::qWait(timeout);
0152         }
0153     }
0154 
0155     if (!mScenarios.isEmpty() && mScenarios.at(0).action == TestScenario::Quit) {
0156         mSocket->close();
0157     }
0158 
0159     if (!mScenarios.isEmpty()) {
0160         CLIENT_VERIFY(mScenarios.at(0).action == TestScenario::ServerCmd || mScenarios.at(0).action == TestScenario::Ignore);
0161     }
0162 }
0163 
0164 void FakeClient::start()
0165 {
0166     QThread::start();
0167     QMetaObject::invokeMethod(this, &FakeClient::do_connectToServer, Qt::QueuedConnection);
0168 }
0169 
0170 void FakeClient::startScenario()
0171 {
0172     QMetaObject::invokeMethod(this, &FakeClient::do_startScenario, Qt::QueuedConnection);
0173 }
0174 
0175 void FakeClient::do_connectToServer()
0176 {
0177     mSocket = new QLocalSocket();
0178     mSocket->connectToServer(FakeAkonadiServer::socketFile());
0179     connect(mSocket, &QLocalSocket::disconnected, this, &FakeClient::connectionLost);
0180     connect(mSocket, &QLocalSocket::errorOccurred, this, [this]() {
0181         qWarning() << "Client socket error: " << mSocket->errorString();
0182         connectionLost();
0183         QVERIFY(false);
0184     });
0185     if (!mSocket->waitForConnected()) {
0186         qFatal("Failed to connect to FakeAkonadiServer");
0187         QVERIFY(false);
0188         return;
0189     }
0190     mStream.setDevice(mSocket);
0191 }
0192 
0193 void FakeClient::do_startScenario()
0194 {
0195     for (;;) {
0196         if (mSocket->state() != QLocalSocket::ConnectedState) {
0197             connectionLost();
0198             break;
0199         }
0200 
0201         if (mSocket->bytesAvailable() == 0) {
0202             QEventLoop loop;
0203             connect(mSocket, &QLocalSocket::readyRead, &loop, &QEventLoop::quit);
0204             connect(mSocket, &QLocalSocket::disconnected, &loop, &QEventLoop::quit);
0205             loop.exec();
0206         }
0207 
0208         while (mSocket->bytesAvailable() > 0) {
0209             if (mSocket->state() != QLocalSocket::ConnectedState) {
0210                 connectionLost();
0211                 break;
0212             }
0213 
0214             if (!dataAvailable()) {
0215                 break;
0216             }
0217         }
0218     }
0219 
0220     mStream.setDevice(nullptr);
0221     mSocket->close();
0222     delete mSocket;
0223     mSocket = nullptr;
0224 
0225     // Quit the thread
0226     quit();
0227 }
0228 
0229 void FakeClient::connectionLost()
0230 {
0231     // Otherwise this is an error on server-side, we expected more talking
0232     CLIENT_VERIFY(isScenarioDone());
0233 }
0234 
0235 #include "moc_fakeclient.cpp"