Files
llvm/lldb/unittests/Host/JSONTransportTest.cpp
John Harrison 8ae30a3fac [lldb] Adding A new Binding helper for JSONTransport. (#159160)
This adds a new Binding helper class to allow mapping of incoming and
outgoing requests / events to specific handlers.

This should make it easier to create new protocol implementations and
allow us to create a relay in the lldb-mcp binary.
2025-10-03 12:14:44 -07:00

797 lines
25 KiB
C++

//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "lldb/Host/JSONTransport.h"
#include "TestingSupport/Host/JSONTransportTestUtilities.h"
#include "TestingSupport/Host/PipeTestUtilities.h"
#include "TestingSupport/SubsystemRAII.h"
#include "lldb/Host/File.h"
#include "lldb/Host/MainLoop.h"
#include "lldb/Host/MainLoopBase.h"
#include "lldb/Utility/Log.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/Support/JSON.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/Testing/Support/Error.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include <chrono>
#include <cstddef>
#include <memory>
#include <optional>
#include <string>
#include <system_error>
using namespace llvm;
using namespace lldb_private;
using namespace lldb_private::transport;
using testing::_;
using testing::HasSubstr;
using testing::InSequence;
using testing::Ref;
namespace llvm::json {
static bool fromJSON(const Value &V, Value &T, Path P) {
T = V;
return true;
}
} // namespace llvm::json
namespace {
namespace test_protocol {
struct Req {
int id = 0;
std::string name;
std::optional<json::Value> params;
};
json::Value toJSON(const Req &T) {
return json::Object{{"name", T.name}, {"id", T.id}, {"params", T.params}};
}
bool fromJSON(const json::Value &V, Req &T, json::Path P) {
json::ObjectMapper O(V, P);
return O && O.map("name", T.name) && O.map("id", T.id) &&
O.map("params", T.params);
}
bool operator==(const Req &a, const Req &b) {
return a.name == b.name && a.id == b.id && a.params == b.params;
}
inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Req &V) {
OS << toJSON(V);
return OS;
}
void PrintTo(const Req &message, std::ostream *os) {
std::string O;
llvm::raw_string_ostream OS(O);
OS << message;
*os << O;
}
struct Resp {
int id = 0;
int errorCode = 0;
std::optional<json::Value> result;
};
json::Value toJSON(const Resp &T) {
return json::Object{
{"id", T.id}, {"errorCode", T.errorCode}, {"result", T.result}};
}
bool fromJSON(const json::Value &V, Resp &T, json::Path P) {
json::ObjectMapper O(V, P);
return O && O.map("id", T.id) && O.mapOptional("errorCode", T.errorCode) &&
O.map("result", T.result);
}
bool operator==(const Resp &a, const Resp &b) {
return a.id == b.id && a.errorCode == b.errorCode && a.result == b.result;
}
inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Resp &V) {
OS << toJSON(V);
return OS;
}
void PrintTo(const Resp &message, std::ostream *os) {
std::string O;
llvm::raw_string_ostream OS(O);
OS << message;
*os << O;
}
struct Evt {
std::string name;
std::optional<json::Value> params;
};
json::Value toJSON(const Evt &T) {
return json::Object{{"name", T.name}, {"params", T.params}};
}
bool fromJSON(const json::Value &V, Evt &T, json::Path P) {
json::ObjectMapper O(V, P);
return O && O.map("name", T.name) && O.map("params", T.params);
}
bool operator==(const Evt &a, const Evt &b) { return a.name == b.name; }
inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Evt &V) {
OS << toJSON(V);
return OS;
}
void PrintTo(const Evt &message, std::ostream *os) {
std::string O;
llvm::raw_string_ostream OS(O);
OS << message;
*os << O;
}
using Message = std::variant<Req, Resp, Evt>;
json::Value toJSON(const Message &msg) {
return std::visit([](const auto &msg) { return toJSON(msg); }, msg);
}
bool fromJSON(const json::Value &V, Message &msg, json::Path P) {
const json::Object *O = V.getAsObject();
if (!O) {
P.report("expected object");
return false;
}
if (O->find("id") == O->end()) {
Evt E;
if (!fromJSON(V, E, P))
return false;
msg = std::move(E);
return true;
}
if (O->get("name")) {
Req R;
if (!fromJSON(V, R, P))
return false;
msg = std::move(R);
return true;
}
Resp R;
if (!fromJSON(V, R, P))
return false;
msg = std::move(R);
return true;
}
struct MyFnParams {
int a = 0;
int b = 0;
};
json::Value toJSON(const MyFnParams &T) {
return json::Object{{"a", T.a}, {"b", T.b}};
}
bool fromJSON(const json::Value &V, MyFnParams &T, json::Path P) {
json::ObjectMapper O(V, P);
return O && O.map("a", T.a) && O.map("b", T.b);
}
struct MyFnResult {
int c = 0;
};
json::Value toJSON(const MyFnResult &T) { return json::Object{{"c", T.c}}; }
bool fromJSON(const json::Value &V, MyFnResult &T, json::Path P) {
json::ObjectMapper O(V, P);
return O && O.map("c", T.c);
}
struct ProtoDesc {
using Id = int;
using Req = Req;
using Resp = Resp;
using Evt = Evt;
static inline Id InitialId() { return 0; }
static inline Req Make(Id id, llvm::StringRef method,
std::optional<llvm::json::Value> params) {
return Req{id, method.str(), params};
}
static inline Evt Make(llvm::StringRef method,
std::optional<llvm::json::Value> params) {
return Evt{method.str(), params};
}
static inline Resp Make(Req req, llvm::Error error) {
Resp resp;
resp.id = req.id;
llvm::handleAllErrors(
std::move(error), [&](const llvm::ErrorInfoBase &err) {
std::error_code cerr = err.convertToErrorCode();
resp.errorCode =
cerr == llvm::inconvertibleErrorCode() ? 1 : cerr.value();
resp.result = err.message();
});
return resp;
}
static inline Resp Make(Req req, std::optional<llvm::json::Value> result) {
return Resp{req.id, 0, std::move(result)};
}
static inline Id KeyFor(Resp r) { return r.id; }
static inline std::string KeyFor(Req r) { return r.name; }
static inline std::string KeyFor(Evt e) { return e.name; }
static inline std::optional<llvm::json::Value> Extract(Req r) {
return r.params;
}
static inline llvm::Expected<llvm::json::Value> Extract(Resp r) {
if (r.errorCode != 0)
return llvm::createStringError(
std::error_code(r.errorCode, std::generic_category()),
r.result && r.result->getAsString() ? *r.result->getAsString()
: "no-message");
return r.result;
}
static inline std::optional<llvm::json::Value> Extract(Evt e) {
return e.params;
}
};
using Transport = TestTransport<ProtoDesc>;
using Binder = lldb_private::transport::Binder<ProtoDesc>;
using MessageHandler = MockMessageHandler<ProtoDesc>;
} // namespace test_protocol
template <typename T> class JSONTransportTest : public PipePairTest {
protected:
SubsystemRAII<FileSystem> subsystems;
test_protocol::MessageHandler message_handler;
std::unique_ptr<T> transport;
MainLoop loop;
void SetUp() override {
PipePairTest::SetUp();
transport = std::make_unique<T>(
std::make_shared<NativeFile>(input.GetReadFileDescriptor(),
File::eOpenOptionReadOnly,
NativeFile::Unowned),
std::make_shared<NativeFile>(output.GetWriteFileDescriptor(),
File::eOpenOptionWriteOnly,
NativeFile::Unowned));
}
/// Run the transport MainLoop and return any messages received.
Error
Run(bool close_input = true,
std::chrono::milliseconds timeout = std::chrono::milliseconds(5000)) {
if (close_input) {
input.CloseWriteFileDescriptor();
EXPECT_CALL(message_handler, OnClosed()).WillOnce([this]() {
loop.RequestTermination();
});
}
loop.AddCallback(
[](MainLoopBase &loop) {
loop.RequestTermination();
FAIL() << "timeout";
},
timeout);
auto handle = transport->RegisterMessageHandler(loop, message_handler);
if (!handle)
return handle.takeError();
return loop.Run().takeError();
}
template <typename... Ts> void Write(Ts... args) {
std::string message;
for (const auto &arg : {args...})
message += Encode(arg);
EXPECT_THAT_EXPECTED(input.Write(message.data(), message.size()),
Succeeded());
}
virtual std::string Encode(const json::Value &) = 0;
};
class TestHTTPDelimitedJSONTransport final
: public HTTPDelimitedJSONTransport<test_protocol::ProtoDesc> {
public:
using HTTPDelimitedJSONTransport::HTTPDelimitedJSONTransport;
void Log(llvm::StringRef message) override {
log_messages.emplace_back(message);
}
std::vector<std::string> log_messages;
};
class HTTPDelimitedJSONTransportTest
: public JSONTransportTest<TestHTTPDelimitedJSONTransport> {
public:
using JSONTransportTest::JSONTransportTest;
std::string Encode(const json::Value &V) override {
std::string msg;
raw_string_ostream OS(msg);
OS << formatv("{0}", V);
return formatv("Content-Length: {0}\r\nContent-type: "
"text/json\r\n\r\n{1}",
msg.size(), msg)
.str();
}
};
class TestJSONRPCTransport final
: public JSONRPCTransport<test_protocol::ProtoDesc> {
public:
using JSONRPCTransport::JSONRPCTransport;
void Log(llvm::StringRef message) override {
log_messages.emplace_back(message);
}
std::vector<std::string> log_messages;
};
class JSONRPCTransportTest : public JSONTransportTest<TestJSONRPCTransport> {
public:
using JSONTransportTest::JSONTransportTest;
std::string Encode(const json::Value &V) override {
std::string msg;
raw_string_ostream OS(msg);
OS << formatv("{0}\n", V);
return msg;
}
};
class TransportBinderTest : public testing::Test {
protected:
SubsystemRAII<FileSystem> subsystems;
std::unique_ptr<test_protocol::Transport> to_remote;
std::unique_ptr<test_protocol::Transport> from_remote;
std::unique_ptr<test_protocol::Binder> binder;
test_protocol::MessageHandler remote;
MainLoop loop;
void SetUp() override {
std::tie(to_remote, from_remote) = test_protocol::Transport::createPair();
binder = std::make_unique<test_protocol::Binder>(*to_remote);
auto binder_handle = to_remote->RegisterMessageHandler(loop, remote);
EXPECT_THAT_EXPECTED(binder_handle, Succeeded());
auto remote_handle = from_remote->RegisterMessageHandler(loop, *binder);
EXPECT_THAT_EXPECTED(remote_handle, Succeeded());
}
void Run() {
loop.AddPendingCallback([](auto &loop) { loop.RequestTermination(); });
EXPECT_THAT_ERROR(loop.Run().takeError(), Succeeded());
}
};
} // namespace
// Failing on Windows, see https://github.com/llvm/llvm-project/issues/153446.
#ifndef _WIN32
using namespace test_protocol;
TEST_F(HTTPDelimitedJSONTransportTest, MalformedRequests) {
std::string malformed_header =
"COnTent-LenGth: -1\r\nContent-Type: text/json\r\n\r\nnotjosn";
ASSERT_THAT_EXPECTED(
input.Write(malformed_header.data(), malformed_header.size()),
Succeeded());
EXPECT_CALL(message_handler, OnError(_)).WillOnce([](llvm::Error err) {
ASSERT_THAT_ERROR(std::move(err),
FailedWithMessage("invalid content length: -1"));
});
ASSERT_THAT_ERROR(Run(), Succeeded());
}
TEST_F(HTTPDelimitedJSONTransportTest, Read) {
Write(Req{6, "foo", std::nullopt});
EXPECT_CALL(message_handler, Received(Req{6, "foo", std::nullopt}));
ASSERT_THAT_ERROR(Run(), Succeeded());
}
TEST_F(HTTPDelimitedJSONTransportTest, ReadMultipleMessagesInSingleWrite) {
InSequence seq;
Write(
Message{
Req{6, "one", std::nullopt},
},
Message{
Evt{"two", std::nullopt},
},
Message{
Resp{2, 0, std::nullopt},
});
EXPECT_CALL(message_handler, Received(Req{6, "one", std::nullopt}));
EXPECT_CALL(message_handler, Received(Evt{"two", std::nullopt}));
EXPECT_CALL(message_handler, Received(Resp{2, 0, std::nullopt}));
ASSERT_THAT_ERROR(Run(), Succeeded());
}
TEST_F(HTTPDelimitedJSONTransportTest, ReadAcrossMultipleChunks) {
std::string long_str = std::string(
HTTPDelimitedJSONTransport<test_protocol::ProtoDesc>::kReadBufferSize * 2,
'x');
Write(Req{5, long_str, std::nullopt});
EXPECT_CALL(message_handler, Received(Req{5, long_str, std::nullopt}));
ASSERT_THAT_ERROR(Run(), Succeeded());
}
TEST_F(HTTPDelimitedJSONTransportTest, ReadPartialMessage) {
std::string message = Encode(Req{5, "foo", std::nullopt});
auto split_at = message.size() / 2;
std::string part1 = message.substr(0, split_at);
std::string part2 = message.substr(split_at);
EXPECT_CALL(message_handler, Received(Req{5, "foo", std::nullopt}));
ASSERT_THAT_EXPECTED(input.Write(part1.data(), part1.size()), Succeeded());
loop.AddPendingCallback(
[](MainLoopBase &loop) { loop.RequestTermination(); });
ASSERT_THAT_ERROR(Run(/*close_stdin=*/false), Succeeded());
ASSERT_THAT_EXPECTED(input.Write(part2.data(), part2.size()), Succeeded());
input.CloseWriteFileDescriptor();
ASSERT_THAT_ERROR(Run(), Succeeded());
}
TEST_F(HTTPDelimitedJSONTransportTest, ReadWithZeroByteWrites) {
std::string message = Encode(Req{6, "foo", std::nullopt});
auto split_at = message.size() / 2;
std::string part1 = message.substr(0, split_at);
std::string part2 = message.substr(split_at);
EXPECT_CALL(message_handler, Received(Req{6, "foo", std::nullopt}));
ASSERT_THAT_EXPECTED(input.Write(part1.data(), part1.size()), Succeeded());
// Run the main loop once for the initial read.
loop.AddPendingCallback(
[](MainLoopBase &loop) { loop.RequestTermination(); });
ASSERT_THAT_ERROR(Run(/*close_stdin=*/false), Succeeded());
// zero-byte write.
ASSERT_THAT_EXPECTED(input.Write(part1.data(), 0),
Succeeded()); // zero-byte write.
loop.AddPendingCallback(
[](MainLoopBase &loop) { loop.RequestTermination(); });
ASSERT_THAT_ERROR(Run(/*close_stdin=*/false), Succeeded());
// Write the remaining part of the message.
ASSERT_THAT_EXPECTED(input.Write(part2.data(), part2.size()), Succeeded());
ASSERT_THAT_ERROR(Run(), Succeeded());
}
TEST_F(HTTPDelimitedJSONTransportTest, ReadWithEOF) {
ASSERT_THAT_ERROR(Run(), Succeeded());
}
TEST_F(HTTPDelimitedJSONTransportTest, ReaderWithUnhandledData) {
std::string json = R"json({"str": "foo"})json";
std::string message =
formatv("Content-Length: {0}\r\nContent-type: text/json\r\n\r\n{1}",
json.size(), json)
.str();
EXPECT_CALL(message_handler, OnError(_)).WillOnce([](llvm::Error err) {
// The error should indicate that there are unhandled contents.
ASSERT_THAT_ERROR(std::move(err),
Failed<TransportUnhandledContentsError>());
});
// Write an incomplete message and close the handle.
ASSERT_THAT_EXPECTED(input.Write(message.data(), message.size() - 1),
Succeeded());
ASSERT_THAT_ERROR(Run(), Succeeded());
}
TEST_F(HTTPDelimitedJSONTransportTest, InvalidTransport) {
transport =
std::make_unique<TestHTTPDelimitedJSONTransport>(nullptr, nullptr);
ASSERT_THAT_ERROR(Run(/*close_input=*/false),
FailedWithMessage("IO object is not valid."));
}
TEST_F(HTTPDelimitedJSONTransportTest, Write) {
ASSERT_THAT_ERROR(transport->Send(Req{7, "foo", std::nullopt}), Succeeded());
ASSERT_THAT_ERROR(transport->Send(Resp{5, 0, "bar"}), Succeeded());
ASSERT_THAT_ERROR(transport->Send(Evt{"baz", std::nullopt}), Succeeded());
output.CloseWriteFileDescriptor();
char buf[1024];
Expected<size_t> bytes_read =
output.Read(buf, sizeof(buf), std::chrono::milliseconds(1));
ASSERT_THAT_EXPECTED(bytes_read, Succeeded());
ASSERT_EQ(StringRef(buf, *bytes_read),
StringRef("Content-Length: 35\r\n\r\n"
R"({"id":7,"name":"foo","params":null})"
"Content-Length: 37\r\n\r\n"
R"({"errorCode":0,"id":5,"result":"bar"})"
"Content-Length: 28\r\n\r\n"
R"({"name":"baz","params":null})"));
}
TEST_F(JSONRPCTransportTest, MalformedRequests) {
std::string malformed_header = "notjson\n";
ASSERT_THAT_EXPECTED(
input.Write(malformed_header.data(), malformed_header.size()),
Succeeded());
EXPECT_CALL(message_handler, OnError(_)).WillOnce([](llvm::Error err) {
ASSERT_THAT_ERROR(std::move(err),
FailedWithMessage(HasSubstr("Invalid JSON value")));
});
ASSERT_THAT_ERROR(Run(), Succeeded());
}
TEST_F(JSONRPCTransportTest, Read) {
Write(Message{Req{1, "foo", std::nullopt}});
EXPECT_CALL(message_handler, Received(Req{1, "foo", std::nullopt}));
ASSERT_THAT_ERROR(Run(), Succeeded());
}
TEST_F(JSONRPCTransportTest, ReadMultipleMessagesInSingleWrite) {
InSequence seq;
Write(Message{Req{1, "one", std::nullopt}}, Message{Evt{"two", std::nullopt}},
Message{Resp{3, 0, "three"}});
EXPECT_CALL(message_handler, Received(Req{1, "one", std::nullopt}));
EXPECT_CALL(message_handler, Received(Evt{"two", std::nullopt}));
EXPECT_CALL(message_handler, Received(Resp{3, 0, "three"}));
ASSERT_THAT_ERROR(Run(), Succeeded());
}
TEST_F(JSONRPCTransportTest, ReadAcrossMultipleChunks) {
// Use a string longer than the chunk size to ensure we split the message
// across the chunk boundary.
std::string long_str = std::string(
IOTransport<test_protocol::ProtoDesc>::kReadBufferSize * 2, 'x');
Write(Req{42, long_str, std::nullopt});
EXPECT_CALL(message_handler, Received(Req{42, long_str, std::nullopt}));
ASSERT_THAT_ERROR(Run(), Succeeded());
}
TEST_F(JSONRPCTransportTest, ReadPartialMessage) {
std::string message = R"({"id":42,"name":"foo","params":null})"
"\n";
std::string part1 = message.substr(0, 7);
std::string part2 = message.substr(7);
EXPECT_CALL(message_handler, Received(Req{42, "foo", std::nullopt}));
ASSERT_THAT_EXPECTED(input.Write(part1.data(), part1.size()), Succeeded());
loop.AddPendingCallback(
[](MainLoopBase &loop) { loop.RequestTermination(); });
ASSERT_THAT_ERROR(Run(/*close_input=*/false), Succeeded());
ASSERT_THAT_EXPECTED(input.Write(part2.data(), part2.size()), Succeeded());
input.CloseWriteFileDescriptor();
ASSERT_THAT_ERROR(Run(), Succeeded());
}
TEST_F(JSONRPCTransportTest, ReadWithEOF) {
ASSERT_THAT_ERROR(Run(), Succeeded());
}
TEST_F(JSONRPCTransportTest, ReaderWithUnhandledData) {
std::string message = R"json({"req": "foo")json";
// Write an incomplete message and close the handle.
ASSERT_THAT_EXPECTED(input.Write(message.data(), message.size() - 1),
Succeeded());
EXPECT_CALL(message_handler, OnError(_)).WillOnce([](llvm::Error err) {
ASSERT_THAT_ERROR(std::move(err),
Failed<TransportUnhandledContentsError>());
});
ASSERT_THAT_ERROR(Run(), Succeeded());
}
TEST_F(JSONRPCTransportTest, Write) {
ASSERT_THAT_ERROR(transport->Send(Req{11, "foo", std::nullopt}), Succeeded());
ASSERT_THAT_ERROR(transport->Send(Resp{14, 0, "bar"}), Succeeded());
ASSERT_THAT_ERROR(transport->Send(Evt{"baz", std::nullopt}), Succeeded());
output.CloseWriteFileDescriptor();
char buf[1024];
Expected<size_t> bytes_read =
output.Read(buf, sizeof(buf), std::chrono::milliseconds(1));
ASSERT_THAT_EXPECTED(bytes_read, Succeeded());
ASSERT_EQ(StringRef(buf, *bytes_read),
StringRef(R"({"id":11,"name":"foo","params":null})"
"\n"
R"({"errorCode":0,"id":14,"result":"bar"})"
"\n"
R"({"name":"baz","params":null})"
"\n"));
}
TEST_F(JSONRPCTransportTest, InvalidTransport) {
transport = std::make_unique<TestJSONRPCTransport>(nullptr, nullptr);
ASSERT_THAT_ERROR(Run(/*close_input=*/false),
FailedWithMessage("IO object is not valid."));
}
// Out-bound binding request handler.
TEST_F(TransportBinderTest, OutBoundRequests) {
OutgoingRequest<MyFnResult, MyFnParams> addFn =
binder->Bind<MyFnResult, MyFnParams>("add");
bool replied = false;
addFn(MyFnParams{1, 2}, [&](Expected<MyFnResult> result) {
EXPECT_THAT_EXPECTED(result, Succeeded());
EXPECT_EQ(result->c, 3);
replied = true;
});
EXPECT_CALL(remote, Received(Req{1, "add", MyFnParams{1, 2}}));
EXPECT_THAT_ERROR(from_remote->Send(Resp{1, 0, toJSON(MyFnResult{3})}),
Succeeded());
Run();
EXPECT_TRUE(replied);
}
TEST_F(TransportBinderTest, OutBoundRequestsVoidParams) {
OutgoingRequest<MyFnResult, void> voidParamFn =
binder->Bind<MyFnResult, void>("voidParam");
bool replied = false;
voidParamFn([&](Expected<MyFnResult> result) {
EXPECT_THAT_EXPECTED(result, Succeeded());
EXPECT_EQ(result->c, 3);
replied = true;
});
EXPECT_CALL(remote, Received(Req{1, "voidParam", std::nullopt}));
EXPECT_THAT_ERROR(from_remote->Send(Resp{1, 0, toJSON(MyFnResult{3})}),
Succeeded());
Run();
EXPECT_TRUE(replied);
}
TEST_F(TransportBinderTest, OutBoundRequestsVoidResult) {
OutgoingRequest<void, MyFnParams> voidResultFn =
binder->Bind<void, MyFnParams>("voidResult");
bool replied = false;
voidResultFn(MyFnParams{4, 5}, [&](llvm::Error error) {
EXPECT_THAT_ERROR(std::move(error), Succeeded());
replied = true;
});
EXPECT_CALL(remote, Received(Req{1, "voidResult", MyFnParams{4, 5}}));
EXPECT_THAT_ERROR(from_remote->Send(Resp{1, 0, std::nullopt}), Succeeded());
Run();
EXPECT_TRUE(replied);
}
TEST_F(TransportBinderTest, OutBoundRequestsVoidParamsAndVoidResult) {
OutgoingRequest<void, void> voidParamAndResultFn =
binder->Bind<void, void>("voidParamAndResult");
bool replied = false;
voidParamAndResultFn([&](llvm::Error error) {
EXPECT_THAT_ERROR(std::move(error), Succeeded());
replied = true;
});
EXPECT_CALL(remote, Received(Req{1, "voidParamAndResult", std::nullopt}));
EXPECT_THAT_ERROR(from_remote->Send(Resp{1, 0, std::nullopt}), Succeeded());
Run();
EXPECT_TRUE(replied);
}
// In-bound binding request handler.
TEST_F(TransportBinderTest, InBoundRequests) {
bool called = false;
binder->Bind<MyFnResult, MyFnParams>(
"add",
[&](const int captured_param,
const MyFnParams &params) -> Expected<MyFnResult> {
called = true;
return MyFnResult{params.a + params.b + captured_param};
},
2);
EXPECT_THAT_ERROR(from_remote->Send(Req{1, "add", MyFnParams{3, 4}}),
Succeeded());
EXPECT_CALL(remote, Received(Resp{1, 0, MyFnResult{9}}));
Run();
EXPECT_TRUE(called);
}
TEST_F(TransportBinderTest, InBoundRequestsVoidParams) {
bool called = false;
binder->Bind<MyFnResult, void>(
"voidParam",
[&](const int captured_param) -> Expected<MyFnResult> {
called = true;
return MyFnResult{captured_param};
},
2);
EXPECT_THAT_ERROR(from_remote->Send(Req{2, "voidParam", std::nullopt}),
Succeeded());
EXPECT_CALL(remote, Received(Resp{2, 0, MyFnResult{2}}));
Run();
EXPECT_TRUE(called);
}
TEST_F(TransportBinderTest, InBoundRequestsVoidResult) {
bool called = false;
binder->Bind<void, MyFnParams>(
"voidResult",
[&](const int captured_param, const MyFnParams &params) -> llvm::Error {
called = true;
EXPECT_EQ(captured_param, 2);
EXPECT_EQ(params.a, 3);
EXPECT_EQ(params.b, 4);
return llvm::Error::success();
},
2);
EXPECT_THAT_ERROR(from_remote->Send(Req{3, "voidResult", MyFnParams{3, 4}}),
Succeeded());
EXPECT_CALL(remote, Received(Resp{3, 0, std::nullopt}));
Run();
EXPECT_TRUE(called);
}
TEST_F(TransportBinderTest, InBoundRequestsVoidParamsAndResult) {
bool called = false;
binder->Bind<void, void>(
"voidParamAndResult",
[&](const int captured_param) -> llvm::Error {
called = true;
EXPECT_EQ(captured_param, 2);
return llvm::Error::success();
},
2);
EXPECT_THAT_ERROR(
from_remote->Send(Req{4, "voidParamAndResult", std::nullopt}),
Succeeded());
EXPECT_CALL(remote, Received(Resp{4, 0, std::nullopt}));
Run();
EXPECT_TRUE(called);
}
// Out-bound binding event handler.
TEST_F(TransportBinderTest, OutBoundEvents) {
OutgoingEvent<MyFnParams> emitEvent = binder->Bind<MyFnParams>("evt");
emitEvent(MyFnParams{1, 2});
EXPECT_CALL(remote, Received(Evt{"evt", MyFnParams{1, 2}}));
Run();
}
TEST_F(TransportBinderTest, OutBoundEventsVoidParams) {
OutgoingEvent<void> emitEvent = binder->Bind<void>("evt");
emitEvent();
EXPECT_CALL(remote, Received(Evt{"evt", std::nullopt}));
Run();
}
// In-bound binding event handler.
TEST_F(TransportBinderTest, InBoundEvents) {
bool called = false;
binder->Bind<MyFnParams>(
"evt",
[&](const int captured_arg, const MyFnParams &params) {
EXPECT_EQ(captured_arg, 42);
EXPECT_EQ(params.a, 3);
EXPECT_EQ(params.b, 4);
called = true;
},
42);
EXPECT_THAT_ERROR(from_remote->Send(Evt{"evt", MyFnParams{3, 4}}),
Succeeded());
Run();
EXPECT_TRUE(called);
}
TEST_F(TransportBinderTest, InBoundEventsVoidParams) {
bool called = false;
binder->Bind<void>(
"evt",
[&](const int captured_arg) {
EXPECT_EQ(captured_arg, 42);
called = true;
},
42);
EXPECT_THAT_ERROR(from_remote->Send(Evt{"evt", std::nullopt}), Succeeded());
Run();
EXPECT_TRUE(called);
}
#endif