ZaStoGram_desktop/Telegram/SourceFiles/test/test_rpc_fixture.cpp
loop-uh 79cf2c99cd
All checks were successful
Desktop source guards / guards (push) Successful in 7s
Поправить пути mtproto в тестовой заглушке RPC из апстрима
test_rpc_fixture пришёл из апстрима со старыми путями mtproto/mtp_instance.h и mtproto/sender.h; в нашем дереве они лежат в mtproto/instance/.
2026-09-25 15:29:23 +03:00

459 lines
14 KiB
C++

/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "test/test_rpc_fixture.h"
#ifdef _DEBUG
#include "core/application.h"
#include "main/main_account.h"
#include "main/main_domain.h"
#include "mtproto/instance/sender.h"
#include "test/test_agent.h"
#include "test/test_log.h"
#include "test/test_runner.h"
#include <QtCore/QCoreApplication>
#include <QtCore/QThread>
namespace Test {
namespace {
[[nodiscard]] QString RouteName(ControlledRpcDelivery::Route route) {
switch (route) {
case ControlledRpcDelivery::Route::Rejected:
return u"rejected"_q;
case ControlledRpcDelivery::Route::Success:
return u"success"_q;
case ControlledRpcDelivery::Route::Error:
return u"error"_q;
case ControlledRpcDelivery::Route::Stale:
return u"stale"_q;
}
return u"unknown"_q;
}
[[nodiscard]] QString DeliveryDetails(const ControlledRpcDelivery &delivery) {
return u"route=%1 registeredBefore=%2 registeredAfter=%3 "
"invoked=%4 diagnosis=[%5]"_q.arg(
RouteName(delivery.route),
delivery.registeredBefore ? u"1"_q : u"0"_q,
delivery.registeredAfter ? u"1"_q : u"0"_q,
delivery.invokedProcessCallback ? u"1"_q : u"0"_q,
delivery.diagnosis);
}
[[nodiscard]] MTPmessages_Messages EmptyMessages() {
return MTP_messages_messages(
MTP_vector<MTPMessage>(0),
MTP_vector<MTPForumTopic>(0),
MTP_vector<MTPChat>(0),
MTP_vector<MTPUser>(0));
}
[[nodiscard]] MTPmessages_Messages NonemptyMessages() {
return MTP_messages_messages(
MTP_vector<MTPMessage>(
1,
MTP_messageEmpty(
MTP_flags(0),
MTP_int(1),
MTP_peerUser(MTP_long(1)))),
MTP_vector<MTPForumTopic>(0),
MTP_vector<MTPChat>(0),
MTP_vector<MTPUser>(0));
}
[[nodiscard]] mtpBuffer BareEmptyMessagesBuffer() {
auto buffer = mtpBuffer();
MTP_messages_messages(
MTP_vector<MTPMessage>(0),
MTP_vector<MTPForumTopic>(0),
MTP_vector<MTPChat>(0),
MTP_vector<MTPUser>(0)
).write(buffer);
return buffer;
}
[[nodiscard]] mtpBuffer TruncatedEmptyMessagesBuffer() {
auto buffer = mtpBuffer();
EmptyMessages().write(buffer);
if (buffer.size() > 1) {
buffer.resize(buffer.size() - 1);
}
return buffer;
}
[[nodiscard]] mtpBuffer TrailingEmptyMessagesBuffer() {
auto buffer = mtpBuffer();
EmptyMessages().write(buffer);
buffer.push_back(0);
return buffer;
}
[[nodiscard]] mtpBuffer IncompatibleBoolBuffer() {
auto buffer = mtpBuffer();
MTPBool(MTP_boolTrue()).write(buffer);
return buffer;
}
} // namespace
ControlledRpcDelivery DeliverControlledRpcError(
not_null<MTP::Instance*> instance,
mtpRequestId requestId,
int code,
const QString &type) {
auto delivery = ControlledRpcDelivery();
delivery.registeredBefore = instance->hasCallback(requestId);
auto response = MTP::Response();
response.requestId = requestId;
MTPRpcError(MTP_rpc_error(
MTP_int(code),
MTP_string(type)
)).write(response.reply);
delivery.diagnosis = DiagnoseControlledRpcResult<MTPRpcError>(
response.reply);
if (!delivery.diagnosis.isEmpty()) {
delivery.registeredAfter = instance->hasCallback(requestId);
return delivery;
}
instance->processCallback(response);
delivery.invokedProcessCallback = true;
delivery.route = ControlledRpcDelivery::Route::Error;
delivery.registeredAfter = instance->hasCallback(requestId);
return delivery;
}
void AppendControlledRpcSelfTest(not_null<Runner*> runner) {
struct State {
MTP::Instance *instance = nullptr;
std::unique_ptr<MTP::Sender> sender;
ControlledRpcDelivery bare;
ControlledRpcDelivery truncated;
ControlledRpcDelivery trailing;
ControlledRpcDelivery incompatible;
ControlledRpcDelivery emptySuccess;
ControlledRpcDelivery nonemptySuccess;
ControlledRpcDelivery error;
ControlledRpcDelivery stale;
mtpRequestId idEmpty = 0;
mtpRequestId idNonempty = 0;
mtpRequestId idError = 0;
mtpRequestId idStale = 0;
mtpTypeId emptyType = 0;
mtpTypeId nonemptyInnerType = 0;
int emptyMessageCount = -1;
int nonemptyMessageCount = -1;
int doneEmpty = 0;
int doneNonempty = 0;
int doneError = 0;
int doneStale = 0;
int failEmpty = 0;
int failNonempty = 0;
int failError = 0;
int failStale = 0;
int failCodeError = 0;
bool pendingBeforeCancel = false;
bool mainThread = false;
};
const auto state = new State();
runner->onFinish([=] {
state->sender = nullptr;
state->instance = nullptr;
});
runner->waitForSessionReady();
runner->add({
.name = u"controlled rpc self-test: reject malformed buffers then "
"deliver a valid empty boxed result"_q,
.run = [=] {
state->instance = &Core::App().domain().active().mtp();
state->sender = std::make_unique<MTP::Sender>(state->instance);
state->idEmpty = state->sender->request(
MTPmessages_GetMessages(MTP_vector<MTPInputMessage>(0))
).done([=](const MTPmessages_Messages &result) {
++state->doneEmpty;
state->emptyType = result.type();
result.match([&](const MTPDmessages_messages &data) {
state->emptyMessageCount = int(data.vmessages().v.size());
}, [&](const auto &) {
state->emptyMessageCount = -2;
});
}).fail([=] {
++state->failEmpty;
}).send();
state->bare = DeliverControlledRpcPrepared<MTPmessages_GetMessages>(
state->instance,
state->idEmpty,
BareEmptyMessagesBuffer());
state->truncated = DeliverControlledRpcPrepared<
MTPmessages_GetMessages>(
state->instance,
state->idEmpty,
TruncatedEmptyMessagesBuffer());
state->trailing = DeliverControlledRpcPrepared<
MTPmessages_GetMessages>(
state->instance,
state->idEmpty,
TrailingEmptyMessagesBuffer());
state->incompatible = DeliverControlledRpcPrepared<
MTPmessages_GetMessages>(
state->instance,
state->idEmpty,
IncompatibleBoolBuffer());
state->emptySuccess = DeliverControlledRpcSuccess<
MTPmessages_GetMessages>(
state->instance,
state->idEmpty,
EmptyMessages());
state->mainThread = (QThread::currentThread()
== QCoreApplication::instance()->thread());
},
.then = [=] {
const auto pending = state->bare.registeredBefore;
const auto reading = u"requestId=%1 mainThread=%2 done=%3 "
"fail=%4 emptyCount=%5 emptyType=0x%6"_q.arg(
QString::number(state->idEmpty),
state->mainThread ? u"1"_q : u"0"_q,
QString::number(state->doneEmpty),
QString::number(state->failEmpty),
QString::number(state->emptyMessageCount),
QString::number(uint32(state->emptyType), 16))
+ u" bare=[%1] truncated=[%2] trailing=[%3] "
"incompatible=[%4] success=[%5]"_q.arg(
DeliveryDetails(state->bare),
DeliveryDetails(state->truncated),
DeliveryDetails(state->trailing),
DeliveryDetails(state->incompatible),
DeliveryDetails(state->emptySuccess));
Check(
pending,
u"controlled rpc self-test: the empty getMessages request "
"was registered before any fixture delivery"_q,
reading);
if (!pending) {
return;
}
const auto rejected = [](const ControlledRpcDelivery &delivery) {
return (delivery.route == ControlledRpcDelivery::Route::Rejected)
&& !delivery.invokedProcessCallback
&& delivery.registeredAfter
&& !delivery.diagnosis.isEmpty();
};
Check(
rejected(state->bare)
&& state->bare.diagnosis.contains(
u"does not decode"_q),
u"controlled rpc self-test: a bare messages.messages write "
"is rejected before processCallback"_q,
reading);
Check(
rejected(state->truncated)
&& !state->truncated.diagnosis.contains(u"trailing"_q)
&& !state->truncated.diagnosis.contains(u"empty reply"_q),
u"controlled rpc self-test: a truncated boxed "
"messages.Messages buffer is rejected before "
"processCallback"_q,
reading);
Check(
rejected(state->trailing)
&& state->trailing.diagnosis.contains(u"trailing words"_q),
u"controlled rpc self-test: a boxed messages.Messages "
"buffer with trailing words is rejected before "
"processCallback"_q,
reading);
Check(
rejected(state->incompatible)
&& state->incompatible.diagnosis.contains(
u"does not decode"_q),
u"controlled rpc self-test: an unrelated boxed MTPBool "
"buffer is rejected before processCallback"_q,
reading);
Check(
(state->emptySuccess.route
== ControlledRpcDelivery::Route::Success)
&& state->emptySuccess.invokedProcessCallback
&& !state->emptySuccess.registeredAfter
&& (state->doneEmpty == 1)
&& (state->failEmpty == 0)
&& (state->emptyMessageCount == 0)
&& (state->emptyType == mtpc_messages_messages),
u"controlled rpc self-test: a valid empty boxed "
"messages.Messages reaches .done() once after the "
"rejected buffers left the parser registered"_q,
reading);
},
});
runner->add({
.name = u"controlled rpc self-test: a nonempty boxed "
"messages.Messages reaches .done() once"_q,
.run = [=] {
state->idNonempty = state->sender->request(
MTPmessages_GetMessages(MTP_vector<MTPInputMessage>(0))
).done([=](const MTPmessages_Messages &result) {
++state->doneNonempty;
result.match([&](const MTPDmessages_messages &data) {
state->nonemptyMessageCount = int(
data.vmessages().v.size());
if (!data.vmessages().v.isEmpty()) {
state->nonemptyInnerType
= data.vmessages().v.front().type();
}
}, [&](const auto &) {
state->nonemptyMessageCount = -2;
});
}).fail([=] {
++state->failNonempty;
}).send();
state->nonemptySuccess = DeliverControlledRpcSuccess<
MTPmessages_GetMessages>(
state->instance,
state->idNonempty,
NonemptyMessages());
},
.then = [=] {
const auto reading = u"requestId=%1 done=%2 fail=%3 count=%4 "
"innerType=0x%5 delivery=[%6]"_q.arg(
QString::number(state->idNonempty),
QString::number(state->doneNonempty),
QString::number(state->failNonempty),
QString::number(state->nonemptyMessageCount),
QString::number(uint32(state->nonemptyInnerType), 16),
DeliveryDetails(state->nonemptySuccess));
Check(
state->nonemptySuccess.registeredBefore,
u"controlled rpc self-test: the nonempty getMessages "
"request was registered before delivery"_q,
reading);
if (!state->nonemptySuccess.registeredBefore) {
return;
}
Check(
(state->nonemptySuccess.route
== ControlledRpcDelivery::Route::Success)
&& state->nonemptySuccess.invokedProcessCallback
&& !state->nonemptySuccess.registeredAfter
&& (state->doneNonempty == 1)
&& (state->failNonempty == 0)
&& (state->nonemptyMessageCount == 1)
&& (state->nonemptyInnerType == mtpc_messageEmpty),
u"controlled rpc self-test: a nonempty boxed "
"messages.Messages reaches .done() once with one "
"messageEmpty"_q,
reading);
},
});
runner->add({
.name = u"controlled rpc self-test: a boxed rpc_error reaches "
".fail()"_q,
.run = [=] {
state->idError = state->sender->request(
MTPmessages_GetMessages(MTP_vector<MTPInputMessage>(0))
).done([=] {
++state->doneError;
}).fail([=](const MTP::Error &error) {
++state->failError;
state->failCodeError = error.code();
}).send();
state->error = DeliverControlledRpcError(
state->instance,
state->idError,
400,
u"SELFTEST_FIXTURE_BAD_REQUEST"_q);
},
.then = [=] {
const auto reading = u"requestId=%1 done=%2 fail=%3 failCode=%4 "
"delivery=[%5]"_q.arg(
QString::number(state->idError),
QString::number(state->doneError),
QString::number(state->failError),
QString::number(state->failCodeError),
DeliveryDetails(state->error));
Check(
state->error.registeredBefore,
u"controlled rpc self-test: the error getMessages request "
"was registered before delivery"_q,
reading);
if (!state->error.registeredBefore) {
return;
}
Check(
(state->error.route == ControlledRpcDelivery::Route::Error)
&& state->error.invokedProcessCallback
&& !state->error.registeredAfter
&& (state->doneError == 0)
&& (state->failError == 1)
&& (state->failCodeError == 400),
u"controlled rpc self-test: a boxed rpc_error reaches "
".fail() with code 400 and unregisters the request"_q,
reading);
},
});
runner->add({
.name = u"controlled rpc self-test: a canceled request's stale "
"success invokes no done or fail callback"_q,
.run = [=] {
state->idStale = state->sender->request(
MTPmessages_GetMessages(MTP_vector<MTPInputMessage>(0))
).done([=] {
++state->doneStale;
}).fail([=] {
++state->failStale;
}).send();
state->pendingBeforeCancel
= state->instance->hasCallback(state->idStale);
state->sender->request(state->idStale).cancel();
state->stale = DeliverControlledRpcStale<MTPmessages_GetMessages>(
state->instance,
state->idStale,
EmptyMessages());
},
.then = [=] {
const auto reading = u"requestId=%1 pendingBeforeCancel=%2 "
"done=%3 fail=%4 delivery=[%5]"_q.arg(
QString::number(state->idStale),
state->pendingBeforeCancel ? u"1"_q : u"0"_q,
QString::number(state->doneStale),
QString::number(state->failStale),
DeliveryDetails(state->stale));
Check(
state->pendingBeforeCancel && !state->stale.registeredBefore,
u"controlled rpc self-test: cancel removed the parser "
"before stale delivery"_q,
reading);
Check(
(state->stale.route == ControlledRpcDelivery::Route::Stale)
&& state->stale.invokedProcessCallback
&& (state->doneStale == 0)
&& (state->failStale == 0)
&& !state->bare.invokedProcessCallback,
u"controlled rpc self-test: stale processCallback invokes "
"no done or fail callback and is distinct from a rejected "
"malformed buffer"_q,
reading);
},
});
runner->add({
.name = u"controlled rpc self-test: teardown"_q,
.run = [=] {
state->sender = nullptr;
state->instance = nullptr;
Note(u"controlled rpc self-test: onFinish also drops the "
"Sender if a later stage is skipped"_q);
},
});
}
} // namespace Test
#endif // _DEBUG