Source code

Revision control

Copy as Markdown

// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "mockserver.h"
#include "tags.h"
#include <QtTest/qtest.h>
#include <grpcpp/alarm.h>
#include <grpcpp/server_builder.h>
#include <cstring>
using namespace std::chrono_literals;
TagProcessor::TagProcessor(MockServer *server)
: mServer(server)
{
QVERIFY(mServer);
QVERIFY(mServer->cq());
mThread = std::thread(&TagProcessor::processLoop, this);
}
TagProcessor::~TagProcessor()
{
QVERIFY(waitForTagCompletion(5s));
mRunning.store(false);
wakeCQ();
if (mThread.joinable())
mThread.join();
drainTags();
}
void TagProcessor::processLoop()
{
while (mRunning.load(std::memory_order_acquire)) {
mServer->processTag();
}
}
void TagProcessor::registerTag(AbstractTag *tag)
{
std::scoped_lock lock(mMutex);
const auto it = mActiveTags.insert(tag);
QVERIFY(it.second);
}
void TagProcessor::unregisterTag(AbstractTag *tag)
{
std::scoped_lock lock(mMutex);
const auto erased = mActiveTags.erase(tag);
QCOMPARE_EQ(erased, 1u);
if (mActiveTags.empty())
mCv.notify_all();
}
size_t TagProcessor::activeTagCount() const noexcept
{
std::scoped_lock lock(mMutex);
return mActiveTags.size();
}
bool TagProcessor::waitForTagCompletion(std::chrono::milliseconds deadline) noexcept
{
std::unique_lock lock(mMutex);
return mCv.wait_for(lock, deadline, [this] { return mActiveTags.empty(); });
}
void TagProcessor::wakeCQ()
{
auto *alarm = new grpc::Alarm();
auto *tag = new DeleteTag<grpc::Alarm>(alarm, this);
alarm->Set(mServer->cq(), gpr_now(GPR_CLOCK_REALTIME), tag);
}
void TagProcessor::drainTags()
{
QVERIFY(!mRunning);
while (mServer->processTag(50))
;
}
MockServer::MockServer() = default;
MockServer::~MockServer()
{
stop();
};
bool MockServer::start(std::vector<ListeningPort> ports, std::vector<grpc::Service *> services,
const grpc::ChannelArguments &channelArgs)
{
if (!transitionState(State::Stopped, State::Starting))
return false;
grpc::ServerBuilder builder;
for (auto &p : ports)
builder.AddListeningPort(p.addressUri, p.creds, &p.selectedPort);
for (auto *s : services)
builder.RegisterService(s);
const grpc_channel_args cArgs = channelArgs.c_channel_args();
for (size_t i = 0; i < cArgs.num_args; ++i) {
const grpc_arg &a = cArgs.args[i];
// Skip client-only args that are not applicable to a server builder.
if (std::strcmp(a.key, GRPC_ARG_PRIMARY_USER_AGENT_STRING) == 0)
continue;
if (a.type == GRPC_ARG_INTEGER)
builder.AddChannelArgument(a.key, a.value.integer);
else if (a.type == GRPC_ARG_STRING)
builder.AddChannelArgument(a.key, a.value.string);
}
mCQ = builder.AddCompletionQueue();
mServer = builder.BuildAndStart();
if (!mServer || !mCQ) {
mState = State::Stopped;
return false;
}
mState = State::Started;
return true;
}
bool MockServer::stop()
{
State currentState = mState.load();
if (currentState != State::Started && currentState != State::ShuttingDown)
return currentState == State::Stopped;
mState = State::ShuttingDown;
mServer->Shutdown(std::chrono::system_clock::now() + std::chrono::milliseconds(500));
mCQ->Shutdown();
void *drainTag;
bool drainOk;
while (mCQ->Next(&drainTag, &drainOk)) {
if (drainTag)
static_cast<AbstractTag*>(drainTag)->process(drainOk);
}
mServer.reset();
mCQ.reset();
mState = State::Stopped;
return true;
}
bool MockServer::processTag(int timeoutMs)
{
State currentState = mState.load();
if (currentState != State::Started)
return false;
void *rawTag = nullptr;
bool ok = false;
const auto status = timeoutMs < 0
? mCQ->AsyncNext(&rawTag, &ok, gpr_inf_future(GPR_CLOCK_REALTIME))
: mCQ->AsyncNext(&rawTag, &ok,
std::chrono::system_clock::now() + std::chrono::milliseconds(timeoutMs));
if (rawTag && status == grpc::CompletionQueue::NextStatus::GOT_EVENT) {
static_cast<AbstractTag *>(rawTag)->process(ok);
return true;
}
return false;
}
MockServer &MockServer::step(int timeoutMs)
{
mFutures.emplace_back(std::async(std::launch::async,
[this, timeoutMs] { return processTag(timeoutMs); }));
return *this;
}
bool MockServer::waitForAllSteps()
{
for (auto &f : mFutures) {
if (!f.get())
return false;
}
mFutures.clear();
return true;
}
void MockServer::startRpcTag(AbstractRpcTag *tag)
{
QVERIFY(tag);
tag->start(mCQ.get());
}
bool MockServer::transitionState(State from, State to)
{
State expected = from;
return mState.compare_exchange_strong(expected, to);
}