mirror of https://github.com/qt/qtgrpc.git
306 lines
10 KiB
C++
306 lines
10 KiB
C++
// Copyright (C) 2022 The Qt Company Ltd.
|
|
// Copyright (C) 2019 Alexey Edelev <semlanik@gmail.com>
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
|
|
|
|
#include <grpcclienttestbase.h>
|
|
|
|
#include <QtCore/QThread>
|
|
#include <QtCore/QTimer>
|
|
|
|
#include <QtGrpc/QGrpcCallOptions>
|
|
#include <QtGrpc/QGrpcMetadata>
|
|
|
|
#include <QtTest/QSignalSpy>
|
|
#include <QtTest/QTest>
|
|
|
|
#include <memory>
|
|
|
|
#include <message_latency_defs.h>
|
|
#include <server_proc_runner.h>
|
|
#include <testservice_client.grpc.qpb.h>
|
|
|
|
using namespace Qt::Literals::StringLiterals;
|
|
using namespace qtgrpc::tests;
|
|
|
|
class QtGrpcClientUnaryCallTest : public GrpcClientTestBase
|
|
{
|
|
Q_OBJECT
|
|
public:
|
|
QtGrpcClientUnaryCallTest()
|
|
: GrpcClientTestBase(GrpcClientTestBase::Channels{
|
|
GrpcClientTestBase::Channel::Qt,
|
|
GrpcClientTestBase::Channel::Json,
|
|
#if !defined(Q_OS_DARWIN) && !defined(Q_OS_WIN32)
|
|
GrpcClientTestBase::Channel::Ssl,
|
|
#endif
|
|
})
|
|
{
|
|
}
|
|
|
|
private Q_SLOTS:
|
|
void asyncWithSubscribe();
|
|
void asyncWithLambda();
|
|
void immediateCancel();
|
|
void deferredCancel();
|
|
void asyncClientStatusMessage();
|
|
void asyncStatusMessage();
|
|
void inThread();
|
|
void asyncInThread();
|
|
void metadata();
|
|
void deadline_data();
|
|
void deadline();
|
|
};
|
|
|
|
void QtGrpcClientUnaryCallTest::asyncWithSubscribe()
|
|
{
|
|
SimpleStringMessage request;
|
|
request.setTestFieldString("Hello Qt!");
|
|
std::optional<SimpleStringMessage> result;
|
|
|
|
bool waitForReply = false;
|
|
std::shared_ptr<QGrpcCallReply> reply = client()->testMethod(request);
|
|
reply->subscribe(this, [reply, &result, &waitForReply] {
|
|
result = reply->read<SimpleStringMessage>();
|
|
waitForReply = true;
|
|
});
|
|
|
|
QTRY_COMPARE_EQ_WITH_TIMEOUT(waitForReply, true, MessageLatency);
|
|
QVERIFY(result.has_value());
|
|
QCOMPARE_EQ(result->testFieldString(), "Hello Qt!");
|
|
}
|
|
|
|
void QtGrpcClientUnaryCallTest::asyncWithLambda()
|
|
{
|
|
std::optional<SimpleStringMessage> result = SimpleStringMessage();
|
|
SimpleStringMessage request;
|
|
request.setTestFieldString("Hello Qt!");
|
|
bool waitForReply = false;
|
|
client()->testMethod(request, this,
|
|
[&result, &waitForReply](std::shared_ptr<QGrpcCallReply> reply) {
|
|
result = reply->read<SimpleStringMessage>();
|
|
waitForReply = true;
|
|
});
|
|
|
|
QTRY_COMPARE_EQ_WITH_TIMEOUT(waitForReply, true, MessageLatency);
|
|
QVERIFY(result.has_value());
|
|
QCOMPARE_EQ(result->testFieldString(), "Hello Qt!");
|
|
}
|
|
|
|
void QtGrpcClientUnaryCallTest::immediateCancel()
|
|
{
|
|
SimpleStringMessage request;
|
|
request.setTestFieldString("sleep");
|
|
|
|
std::shared_ptr<QGrpcCallReply> reply = client()->testMethod(request);
|
|
|
|
std::optional<SimpleStringMessage> result = SimpleStringMessage();
|
|
result->setTestFieldString("Result not changed by echo");
|
|
QObject::connect(reply.get(), &QGrpcCallReply::finished, this,
|
|
[&result, reply] { result = reply->read<SimpleStringMessage>(); });
|
|
|
|
QSignalSpy replyErrorSpy(reply.get(), &QGrpcCallReply::errorOccurred);
|
|
QSignalSpy replyFinishedSpy(reply.get(), &QGrpcCallReply::finished);
|
|
QSignalSpy clientErrorSpy(client().get(), &TestService::Client::errorOccurred);
|
|
|
|
QVERIFY(replyErrorSpy.isValid());
|
|
QVERIFY(replyFinishedSpy.isValid());
|
|
QVERIFY(clientErrorSpy.isValid());
|
|
|
|
reply->cancel();
|
|
|
|
QTRY_COMPARE_EQ_WITH_TIMEOUT(replyErrorSpy.count(), 1, FailTimeout);
|
|
QTRY_COMPARE_EQ_WITH_TIMEOUT(clientErrorSpy.count(), 1, FailTimeout);
|
|
QTRY_COMPARE_EQ_WITH_TIMEOUT(replyFinishedSpy.count(), 0, FailTimeout);
|
|
|
|
QCOMPARE_EQ(result->testFieldString(), "Result not changed by echo");
|
|
QCOMPARE_EQ(qvariant_cast<QGrpcStatus>(clientErrorSpy.at(0).first()).code(),
|
|
QGrpcStatus::Cancelled);
|
|
}
|
|
|
|
void QtGrpcClientUnaryCallTest::deferredCancel()
|
|
{
|
|
SimpleStringMessage request;
|
|
request.setTestFieldString("sleep");
|
|
|
|
std::optional<SimpleStringMessage> result = SimpleStringMessage();
|
|
result->setTestFieldString("Result not changed by echo");
|
|
std::shared_ptr<QGrpcCallReply> reply = client()->testMethod(request);
|
|
|
|
QObject::connect(reply.get(), &QGrpcCallReply::finished, this, [reply, &result] {
|
|
QVERIFY(false);
|
|
result = reply->read<SimpleStringMessage>();
|
|
});
|
|
|
|
QSignalSpy replyErrorSpy(reply.get(), &QGrpcCallReply::errorOccurred);
|
|
QVERIFY(replyErrorSpy.isValid());
|
|
|
|
QTimer::singleShot(MessageLatencyThreshold, reply.get(), &QGrpcCallReply::cancel);
|
|
|
|
QTRY_COMPARE_EQ_WITH_TIMEOUT(replyErrorSpy.count(), 1, FailTimeout);
|
|
QCOMPARE_EQ(result->testFieldString(), "Result not changed by echo");
|
|
}
|
|
|
|
void QtGrpcClientUnaryCallTest::asyncClientStatusMessage()
|
|
{
|
|
SimpleStringMessage request;
|
|
request.setTestFieldString("Some status message");
|
|
|
|
QSignalSpy clientErrorSpy(client().get(), &TestService::Client::errorOccurred);
|
|
QVERIFY(clientErrorSpy.isValid());
|
|
|
|
client()->testMethodStatusMessage(request);
|
|
|
|
QTRY_COMPARE_EQ_WITH_TIMEOUT(clientErrorSpy.count(), 1, FailTimeout);
|
|
|
|
QCOMPARE(qvariant_cast<QGrpcStatus>(clientErrorSpy.at(0).first()).message(),
|
|
request.testFieldString());
|
|
}
|
|
|
|
void QtGrpcClientUnaryCallTest::asyncStatusMessage()
|
|
{
|
|
SimpleStringMessage request;
|
|
request.setTestFieldString("Some status message");
|
|
|
|
std::shared_ptr<QGrpcCallReply> reply = client()->testMethodStatusMessage(request);
|
|
|
|
QSignalSpy replyErrorSpy(reply.get(), &QGrpcCallReply::errorOccurred);
|
|
QVERIFY(replyErrorSpy.isValid());
|
|
|
|
QTRY_COMPARE_EQ_WITH_TIMEOUT(replyErrorSpy.count(), 1, FailTimeout);
|
|
QCOMPARE(qvariant_cast<QGrpcStatus>(replyErrorSpy.at(0).first()).message(),
|
|
request.testFieldString());
|
|
}
|
|
|
|
void QtGrpcClientUnaryCallTest::inThread()
|
|
{
|
|
SimpleStringMessage request;
|
|
|
|
request.setTestFieldString("Hello Qt from thread!");
|
|
|
|
QSignalSpy clientErrorSpy(client().get(), &TestService::Client::errorOccurred);
|
|
QVERIFY(clientErrorSpy.isValid());
|
|
|
|
std::shared_ptr<QGrpcCallReply> reply;
|
|
const std::unique_ptr<QThread> thread(QThread::create(
|
|
[&] { reply = client()->testMethod(request); }));
|
|
|
|
thread->start();
|
|
|
|
QTRY_COMPARE_EQ_WITH_TIMEOUT(clientErrorSpy.count(), 1, MessageLatencyWithThreshold);
|
|
QVERIFY(reply == nullptr);
|
|
QVERIFY(qvariant_cast<QGrpcStatus>(clientErrorSpy.at(0).first())
|
|
.message()
|
|
.startsWith("QGrpcClientBase::call is called from a different thread."));
|
|
}
|
|
|
|
void QtGrpcClientUnaryCallTest::asyncInThread()
|
|
{
|
|
SimpleStringMessage request;
|
|
request.setTestFieldString("Hello Qt from thread!");
|
|
|
|
QSignalSpy clientErrorSpy(client().get(), &TestService::Client::errorOccurred);
|
|
QVERIFY(clientErrorSpy.isValid());
|
|
|
|
std::optional<SimpleStringMessage> result = SimpleStringMessage();
|
|
const std::unique_ptr<QThread> thread(QThread::create([&] {
|
|
QEventLoop waiter;
|
|
std::shared_ptr<QGrpcCallReply> reply = client()->testMethod(request);
|
|
QObject::connect(reply.get(), &QGrpcCallReply::finished, &waiter,
|
|
[reply, &result, &waiter] {
|
|
result = reply->read<SimpleStringMessage>();
|
|
waiter.quit();
|
|
});
|
|
waiter.exec();
|
|
}));
|
|
|
|
thread->start();
|
|
QTRY_COMPARE_EQ_WITH_TIMEOUT(clientErrorSpy.count(), 1, FailTimeout);
|
|
QTRY_VERIFY(result.has_value());
|
|
QTRY_VERIFY(
|
|
qvariant_cast<QGrpcStatus>(clientErrorSpy.at(0).first())
|
|
.message()
|
|
.startsWith("QGrpcClientBase::call is called from a different thread."));
|
|
}
|
|
|
|
void QtGrpcClientUnaryCallTest::metadata()
|
|
{
|
|
QSignalSpy clientErrorSpy(client().get(), &TestService::Client::errorOccurred);
|
|
QVERIFY(clientErrorSpy.isValid());
|
|
|
|
QGrpcCallOptions opt;
|
|
opt.setMetadata({
|
|
{ "client_header", "1" },
|
|
{ "client_return_header", "valid_value" }
|
|
});
|
|
auto reply = client()->testMetadata({}, opt);
|
|
|
|
QGrpcMetadata metadata;
|
|
QEventLoop waiter;
|
|
reply->subscribe(this, [reply, &metadata, &waiter] {
|
|
metadata = reply->metadata();
|
|
waiter.quit();
|
|
});
|
|
|
|
waiter.exec();
|
|
|
|
int serverHeaderCount = 0;
|
|
QByteArray clientReturnHeader;
|
|
for (const auto &header : reply->metadata()) {
|
|
if (header.first == "server_header") {
|
|
QCOMPARE_EQ(QString::fromLatin1(header.second), QString::number(++serverHeaderCount));
|
|
} else if (header.first == "client_return_header") {
|
|
clientReturnHeader = header.second;
|
|
}
|
|
}
|
|
|
|
QCOMPARE_EQ(clientErrorSpy.count(), 0);
|
|
QCOMPARE_EQ(serverHeaderCount, 1);
|
|
QCOMPARE_EQ(clientReturnHeader, "valid_value"_ba);
|
|
}
|
|
|
|
void QtGrpcClientUnaryCallTest::deadline_data()
|
|
{
|
|
QTest::addColumn<QGrpcDuration>("timeout");
|
|
constexpr std::array<qreal, 4> messageLatencyFractions{ 0.7, 0.9, 1.0, 1.3 };
|
|
for (const auto &fraction : messageLatencyFractions)
|
|
QTest::newRow(QString("MessageLatency * %1").arg(fraction).toStdString().c_str())
|
|
<< QGrpcDuration(static_cast<int64_t>(MessageLatency * fraction));
|
|
}
|
|
|
|
void QtGrpcClientUnaryCallTest::deadline()
|
|
{
|
|
QFETCH(const QGrpcDuration, timeout);
|
|
|
|
QGrpcCallOptions opt;
|
|
opt.setDeadline(timeout);
|
|
|
|
SimpleStringMessage request;
|
|
request.setTestFieldString("sleep");
|
|
|
|
QSignalSpy clientErrorSpy(client().get(), &TestService::Client::errorOccurred);
|
|
QVERIFY(clientErrorSpy.isValid());
|
|
|
|
auto reply = client()->testMethod(request, opt);
|
|
QSignalSpy callFinishedSpy(reply.get(), &QGrpcCallReply::finished);
|
|
QVERIFY(callFinishedSpy.isValid());
|
|
|
|
if (timeout.count() < MessageLatency) {
|
|
QTRY_COMPARE_EQ_WITH_TIMEOUT(clientErrorSpy.count(), 1, FailTimeout);
|
|
const auto code = qvariant_cast<QGrpcStatus>(clientErrorSpy.at(0).first()).code();
|
|
// Really low timeout can trigger before service becomes available
|
|
QVERIFY(code == QGrpcStatus::StatusCode::Cancelled
|
|
|| code == QGrpcStatus::StatusCode::Unavailable);
|
|
} else if (timeout.count() >= MessageLatencyWithThreshold) {
|
|
QTRY_COMPARE_EQ_WITH_TIMEOUT(callFinishedSpy.count(), 1, MessageLatencyWithThreshold);
|
|
QCOMPARE(reply->read<SimpleStringMessage>()->testFieldString(), request.testFieldString());
|
|
} else {
|
|
// Because we're can't be sure about the result,
|
|
// cancel the call, that might affect other tests.
|
|
reply->cancel();
|
|
}
|
|
}
|
|
|
|
QTEST_MAIN(QtGrpcClientUnaryCallTest)
|
|
|
|
#include "tst_grpc_client_unarycall.moc"
|