fakeclient.cpp 7.4 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/*
 * Copyright (C) 2014  Daniel Vrátil <dvratil@redhat.com>
 *
 * 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.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * 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
 *
 */

#include "fakeclient.h"
#include "fakeakonadiserver.h"
22

23
#include <private/protocol_exception_p.h>
24
#include <private/protocol_p.h>
25
#include <private/datastream_p_p.h>
26
27
28
29
30

#include <QTest>
#include <QMutexLocker>
#include <QLocalSocket>

31
#define CLIENT_COMPARE(actual, expected, ...)\
32
33
do {\
    if (!QTest::qCompare(actual, expected, #actual, #expected, __FILE__, __LINE__)) {\
34
35
        mSocket->disconnectFromServer();\
        return __VA_ARGS__;\
36
37
38
    }\
} while (0)

39
#define CLIENT_VERIFY(statement, ...)\
40
41
do {\
    if (!QTest::qVerify((statement), #statement, "", __FILE__, __LINE__)) {\
42
43
        mSocket->disconnectFromServer();\
        return __VA_ARGS__;\
44
45
    }\
} while (0)
46

47
using namespace Akonadi;
48
49
using namespace Akonadi::Server;

50
FakeClient::FakeClient(QObject *parent)
51
    : QThread(parent)
52
    , mMutex(QMutex::Recursive)
53
54
55
56
57
58
59
60
{
    moveToThread(this);
}

FakeClient::~FakeClient()
{
}

61
void FakeClient::setScenarios(const TestScenario::List &scenarios)
62
{
63
    mScenarios = scenarios;
64
65
66
67
68
}

bool FakeClient::isScenarioDone() const
{
    QMutexLocker locker(&mMutex);
69
    return mScenarios.isEmpty();
70
71
}

72
bool FakeClient::dataAvailable()
73
74
75
{
    QMutexLocker locker(&mMutex);

76
    CLIENT_VERIFY(!mScenarios.isEmpty(), false);
77
78
79

    readServerPart();
    writeClientPart();
80
81

    return true;
82
83
84
85
}

void FakeClient::readServerPart()
{
86
87
    while (!mScenarios.isEmpty() && (mScenarios.at(0).action == TestScenario::ServerCmd
            || mScenarios.at(0).action == TestScenario::Ignore)) {
88
        TestScenario scenario = mScenarios.takeFirst();
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
        if (scenario.action == TestScenario::Ignore) {
            const int count = scenario.data.toInt();

            // Read and throw away all "count" responses. Useful for scenarios
            // with thousands of responses
            qint64 tag;
            for (int i = 0; i < count; ++i) {
                mStream >> tag;
                Protocol::deserialize(mStream.device());
            }
        } else {
            QDataStream expectedStream(scenario.data);
            qint64 expectedTag, actualTag;

            expectedStream >> expectedTag;
104
            const auto expectedCommand = Protocol::deserialize(expectedStream.device());
105
106
107
            try {
                while ((size_t)mSocket->bytesAvailable() < sizeof(qint64)) {
                    Protocol::DataStream::waitForData(mSocket, 5000);
108
                }
109
110
111
112
            } catch (const ProtocolException &e) {
                qDebug() << "ProtocolException:" << e.what();
                qDebug() << "Expected response:" << Protocol::debugString(expectedCommand);
                CLIENT_VERIFY(false);
113
            }
114

115
116
117
            mStream >> actualTag;
            CLIENT_COMPARE(actualTag, expectedTag);

118
119
120
121
122
123
124
125
            Protocol::CommandPtr actualCommand;
            try {
                actualCommand = Protocol::deserialize(mStream.device());
            } catch (const ProtocolException &e) {
                qDebug() << "Protocol exception:" << e.what();
                qDebug() << "Expected response:" << Protocol::debugString(expectedCommand);
                CLIENT_VERIFY(false);
            }
126

127
128
129
            if (actualCommand->type() != expectedCommand->type()) {
                qDebug() << "Actual command:  " << Protocol::debugString(actualCommand);
                qDebug() << "Expected Command:" << Protocol::debugString(expectedCommand);
130
            }
131
132
133
134
135
            CLIENT_COMPARE(actualCommand->type(), expectedCommand->type());
            CLIENT_COMPARE(actualCommand->isResponse(), expectedCommand->isResponse());
            if (*actualCommand != *expectedCommand) {
                qDebug() << "Actual command:  " << Protocol::debugString(actualCommand);
                qDebug() << "Expected Command:" << Protocol::debugString(expectedCommand);
136
            }
137

138
            CLIENT_COMPARE(*actualCommand, *expectedCommand);
139
        }
140
141
    }

142
    if (!mScenarios.isEmpty()) {
143
144
145
        CLIENT_VERIFY(mScenarios.at(0).action == TestScenario::ClientCmd
                        || mScenarios.at(0).action == TestScenario::Wait
                        || mScenarios.at(0).action ==TestScenario::Quit);
146
147
    } else {
        // Server replied and there's nothing else to send, then quit
148
        mSocket->disconnectFromServer();
149
150
151
152
153
    }
}

void FakeClient::writeClientPart()
{
154
155
156
    while (!mScenarios.isEmpty() && (mScenarios.at(0).action == TestScenario::ClientCmd
            || mScenarios.at(0).action == TestScenario::Wait)) {
        const TestScenario rule = mScenarios.takeFirst();
157

158
159
       if (rule.action == TestScenario::ClientCmd) {
            mSocket->write(rule.data);
160
            CLIENT_VERIFY(mSocket->waitForBytesWritten());
161
        } else {
162
            const int timeout = rule.data.toInt();
163
164
165
166
            QTest::qWait(timeout);
        }
    }

167
    if (!mScenarios.isEmpty() && mScenarios.at(0).action == TestScenario::Quit) {
168
169
170
        mSocket->close();
    }

171
    if (!mScenarios.isEmpty()) {
172
173
        CLIENT_VERIFY(mScenarios.at(0).action == TestScenario::ServerCmd
                        || mScenarios.at(0).action == TestScenario::Ignore);
174
175
176
177
178
179
180
    }
}

void FakeClient::run()
{
    mSocket = new QLocalSocket();
    mSocket->connectToServer(FakeAkonadiServer::socketFile());
181
    connect(mSocket, &QLocalSocket::disconnected, this, &FakeClient::connectionLost);
182
#if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0))
183
    connect(mSocket, QOverload<QLocalSocket::LocalSocketError>::of(&QLocalSocket::error),
184
185
186
#else
    connect(mSocket, &QLocalSocket::errorOccurred,
#endif
Daniel Vrátil's avatar
Daniel Vrátil committed
187
            this, [this]() {
188
189
190
191
                qWarning() << "Client socket error: " << mSocket->errorString();
                connectionLost();
                QVERIFY(false);
            });
192
    if (!mSocket->waitForConnected()) {
193
        qFatal("Failed to connect to FakeAkonadiServer");
194
195
        QVERIFY(false);
        return;
196
    }
197
    mStream.setDevice(mSocket);
198

199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
    Q_FOREVER {
        if (mSocket->state() != QLocalSocket::ConnectedState) {
            connectionLost();
            break;
        }

        {
            QEventLoop loop;
            connect(mSocket, &QLocalSocket::readyRead, &loop, &QEventLoop::quit);
            connect(mSocket, &QLocalSocket::disconnected, &loop, &QEventLoop::quit);
            loop.exec();
        }

        while (mSocket->bytesAvailable() > 0) {
            if (mSocket->state() != QLocalSocket::ConnectedState) {
                connectionLost();
                break;
            }

            if(!dataAvailable()) {
                break;
            }
        }
    }
223

Laurent Montel's avatar
Laurent Montel committed
224
    mStream.setDevice(nullptr);
225
226
    mSocket->close();
    delete mSocket;
Laurent Montel's avatar
Laurent Montel committed
227
    mSocket = nullptr;
228
229
230
231
232
}

void FakeClient::connectionLost()
{
    // Otherwise this is an error on server-side, we expected more talking
233
    CLIENT_VERIFY(isScenarioDone());
234
}