19#include "gtest/gtest.h"
36using namespace std::chrono_literals;
43bool waitFor(
const std::function<
bool()> &cond,
44 std::chrono::milliseconds timeout = 10s) {
45 auto deadline = std::chrono::steady_clock::now() + timeout;
47 if (std::chrono::steady_clock::now() > deadline)
49 std::this_thread::sleep_for(1ms);
54class CosimRpcTest :
public ::testing::Test {
56 void SetUp()
override {
58 oldCwd = std::filesystem::current_path();
59 tmpDir = std::filesystem::temp_directory_path() /
60 (
"esi_cosim_rpc_test_" +
62 std::chrono::steady_clock::now().time_since_epoch().count()));
63 std::filesystem::create_directories(tmpDir);
64 std::filesystem::current_path(tmpDir);
66 ctxt = Context::withLogger<NullLogger>();
67 server = std::make_unique<RpcServer>(*ctxt);
70 for (
int attempt = 0;; ++attempt) {
74 }
catch (
const std::runtime_error &) {
79 toServer = &server->registerReadPort(
"in",
"i32");
81 lateToServer = &server->registerReadPort(
"late",
"i32");
82 toClient = &server->registerWritePort(
"out",
"i32");
86 server->setManifest(0, {1, 2, 3, 4});
89 void TearDown()
override {
94 std::filesystem::current_path(oldCwd);
96 std::filesystem::remove_all(tmpDir, ec);
99 void connectClient() {
100 client = std::make_unique<RpcClient>(ctxt->getLogger(),
"127.0.0.1",
106 uint32_t consumeOne() {
return consumeFrom(*toServer); }
108 std::filesystem::path oldCwd, tmpDir;
109 std::unique_ptr<Context> ctxt;
110 std::unique_ptr<RpcServer> server;
111 ReadChannelPort *toServer =
nullptr;
112 ReadChannelPort *lateToServer =
nullptr;
113 WriteChannelPort *toClient =
nullptr;
114 std::unique_ptr<RpcClient> client;
119 uint32_t consumeFrom(ReadChannelPort &port) {
120 std::future<MessageData> f = port.
readAsync();
121 if (f.wait_for(10s) != std::future_status::ready) {
122 ADD_FAILURE() <<
"timed out waiting for a message";
125 MessageData m = f.get();
126 server->messageConsumed(port);
127 return *m.
as<uint32_t>();
131TEST_F(CosimRpcTest, ToServerTryWriteBackpressure) {
133 client->setToServerMaxInFlight(
"in", 4);
135 for (uint32_t i = 0; i < 4; ++i)
136 EXPECT_TRUE(client->tryWriteToServer(
"in", {msg(i)}));
138 EXPECT_FALSE(client->tryWriteToServer(
"in", {msg(4)}));
141 EXPECT_EQ(consumeOne(), 0u);
143 waitFor([&] {
return client->tryWriteToServer(
"in", {msg(4)}); }));
144 EXPECT_FALSE(client->tryWriteToServer(
"in", {msg(5)}));
146 for (uint32_t i = 1; i <= 4; ++i)
147 EXPECT_EQ(consumeOne(), i);
150 ASSERT_TRUE(waitFor([&] {
151 return client->tryWriteToServer(
152 "in", {msg(10), msg(11), msg(12), msg(13), msg(14)});
154 EXPECT_FALSE(client->tryWriteToServer(
"in", {msg(15)}));
155 for (uint32_t i = 10; i <= 14; ++i)
156 EXPECT_EQ(consumeOne(), i);
159 client->setToServerMaxInFlight(
"in", 0);
160 for (uint32_t i = 0; i < 100; ++i)
161 EXPECT_TRUE(client->tryWriteToServer(
"in", {msg(i)}));
162 for (uint32_t i = 0; i < 100; ++i)
163 EXPECT_EQ(consumeOne(), i);
166TEST_F(CosimRpcTest, ToServerForcedWriteIgnoresLimit) {
168 client->setToServerMaxInFlight(
"in", 1);
169 EXPECT_TRUE(client->tryWriteToServer(
"in", {msg(0)}));
170 EXPECT_FALSE(client->tryWriteToServer(
"in", {msg(1)}));
173 EXPECT_TRUE(client->tryWriteToServer(
"in", {msg(1)},
true));
174 EXPECT_EQ(consumeOne(), 0u);
175 std::this_thread::sleep_for(50ms);
176 EXPECT_FALSE(client->tryWriteToServer(
"in", {msg(2)}));
177 EXPECT_EQ(consumeOne(), 1u);
179 waitFor([&] {
return client->tryWriteToServer(
"in", {msg(2)}); }));
180 EXPECT_EQ(consumeOne(), 2u);
183TEST_F(CosimRpcTest, ToServerBlockingWrite) {
185 client->setToServerMaxInFlight(
"in", 2);
186 client->writeToServer(
"in", msg(0));
187 client->writeToServer(
"in", msg(1));
189 std::atomic<bool> done =
false;
190 std::thread writer([&] {
191 client->writeToServer(
"in", msg(2));
194 std::this_thread::sleep_for(100ms);
195 EXPECT_FALSE(done.load()) <<
"write() should block at the in-flight limit";
197 EXPECT_EQ(consumeOne(), 0u);
198 EXPECT_TRUE(waitFor([&] {
return done.load(); }));
200 EXPECT_EQ(consumeOne(), 1u);
201 EXPECT_EQ(consumeOne(), 2u);
204TEST_F(CosimRpcTest, ToServerBlockedWriterThrowsOnDisconnect) {
210 client->setToServerMaxInFlight(
"late", 1);
211 client->writeToServer(
"late", msg(0));
213 std::promise<bool> threw;
214 std::thread writer([&] {
216 client->writeToServer(
"late", msg(1));
217 threw.set_value(
false);
218 }
catch (
const std::exception &) {
219 threw.set_value(
true);
222 std::this_thread::sleep_for(50ms);
225 auto f = threw.get_future();
226 ASSERT_EQ(f.wait_for(10s), std::future_status::ready);
227 EXPECT_TRUE(f.get());
231TEST_F(CosimRpcTest, ToClientBackpressure) {
235 while (toClient->
tryWrite(msg(next)))
236 ASSERT_LT(++next, 1000u) <<
"server never applied backpressure";
240 std::atomic<bool> accepting =
true;
241 std::mutex receivedMutex;
242 std::vector<uint32_t> received;
243 auto conn = client->connectClientReceiver(
245 [&](std::unique_ptr<SegmentedMessageData> &data) {
248 std::lock_guard<std::mutex> lock(receivedMutex);
249 received.push_back(*
data->toMessageData().as<uint32_t>());
253 auto numReceived = [&] {
254 std::lock_guard<std::mutex> lock(receivedMutex);
255 return received.size();
259 ASSERT_TRUE(waitFor([&] {
return numReceived() == next; }));
260 ASSERT_TRUE(waitFor([&] {
return toClient->
tryWrite(msg(next)); }));
262 ASSERT_TRUE(waitFor([&] {
return numReceived() == next; }));
267 uint32_t admitted = 0;
268 while (toClient->
tryWrite(msg(next))) {
270 ASSERT_LE(++admitted, 4u);
272 std::this_thread::sleep_for(100ms);
273 EXPECT_FALSE(toClient->
tryWrite(msg(next)));
277 ASSERT_TRUE(waitFor([&] {
return toClient->
tryWrite(msg(next)); }));
279 ASSERT_TRUE(waitFor([&] {
return numReceived() == next; }));
280 std::lock_guard<std::mutex> lock(receivedMutex);
281 for (uint32_t i = 0; i < next; ++i)
282 EXPECT_EQ(received[i], i);
285TEST_F(CosimRpcTest, ToClientUnsubscribeResetsInFlight) {
288 std::atomic<bool> seen =
false;
289 auto conn = client->connectClientReceiver(
291 [&](std::unique_ptr<SegmentedMessageData> &) {
296 EXPECT_TRUE(toClient->
tryWrite(msg(0)));
297 EXPECT_FALSE(toClient->
tryWrite(msg(1)));
300 ASSERT_TRUE(waitFor([&] {
return seen.load(); }));
304 uint32_t admitted = 0;
305 ASSERT_TRUE(waitFor([&] {
return toClient->
tryWrite(msg(0)); }));
307 while (toClient->
tryWrite(msg(admitted)))
312TEST_F(CosimRpcTest, UnconnectedServerPortBackpressures) {
316 client->setToServerMaxInFlight(
"late", 4);
317 for (uint32_t i = 0; i < 4; ++i)
318 EXPECT_TRUE(client->tryWriteToServer(
"late", {msg(i)}));
319 std::this_thread::sleep_for(50ms);
320 EXPECT_FALSE(client->tryWriteToServer(
"late", {msg(4)}));
324 for (uint32_t i = 0; i < 4; ++i)
325 EXPECT_EQ(consumeFrom(*lateToServer), i);
327 waitFor([&] {
return client->tryWriteToServer(
"late", {msg(4)}); }));
328 EXPECT_EQ(consumeFrom(*lateToServer), 4u);
331TEST_F(CosimRpcTest, ServerPortDisconnectReturnsInFlight) {
334 client->setToServerMaxInFlight(
"late", 3);
335 for (uint32_t i = 0; i < 3; ++i)
336 EXPECT_TRUE(client->tryWriteToServer(
"late", {msg(i)}));
340 client->writeToServer(
"in", msg(99));
341 EXPECT_EQ(consumeOne(), 99u);
346 waitFor([&] {
return client->tryWriteToServer(
"late", {msg(10)}); }));
350 EXPECT_EQ(consumeFrom(*lateToServer), 10u);
353TEST_F(CosimRpcTest, RefusedFramesDontSkewDisconnectCredits) {
355 client->setToServerMaxInFlight(
"late", 1);
358 std::atomic<int> offered{0};
363 EXPECT_TRUE(client->tryWriteToServer(
"late", {msg(1)}));
365 waitFor([&] {
return client->tryWriteToServer(
"late", {msg(2)}); }));
366 ASSERT_TRUE(waitFor([&] {
return offered.load() == 2; }));
372 waitFor([&] {
return client->tryWriteToServer(
"late", {msg(3)}); }));
373 client->writeToServer(
"in", msg(99));
374 EXPECT_EQ(consumeOne(), 99u);
375 EXPECT_FALSE(client->tryWriteToServer(
"late", {msg(4)}));
380 waitFor([&] {
return client->tryWriteToServer(
"late", {msg(5)}); }));
383TEST_F(CosimRpcTest, DepartedClientsCreditsNotGivenToNext) {
386 client->writeToServer(
"late", msg(1));
387 client->writeToServer(
"in", msg(99));
388 EXPECT_EQ(consumeOne(), 99u);
393 client->setToServerMaxInFlight(
"late", 1);
394 EXPECT_TRUE(client->tryWriteToServer(
"late", {msg(2)}));
395 client->writeToServer(
"in", msg(98));
396 EXPECT_EQ(consumeOne(), 98u);
400 EXPECT_EQ(consumeFrom(*lateToServer), 1u);
401 std::this_thread::sleep_for(100ms);
402 EXPECT_FALSE(client->tryWriteToServer(
"late", {msg(3)}));
405 EXPECT_EQ(consumeFrom(*lateToServer), 2u);
407 waitFor([&] {
return client->tryWriteToServer(
"late", {msg(3)}); }));
408 EXPECT_EQ(consumeFrom(*lateToServer), 3u);
411TEST_F(CosimRpcTest, UnconnectedServerPortHoldsFrames) {
415 for (uint32_t i = 0; i < 4; ++i)
416 client->writeToServer(
"late", msg(i));
419 client->writeToServer(
"in", msg(99));
420 EXPECT_EQ(consumeOne(), 99u);
423 for (uint32_t i = 0; i < 4; ++i)
424 EXPECT_EQ(consumeFrom(*lateToServer), i);
428 client->writeToServer(
"late", msg(10));
429 client->writeToServer(
"in", msg(98));
430 EXPECT_EQ(consumeOne(), 98u);
432 EXPECT_EQ(consumeFrom(*lateToServer), 10u);
435TEST_F(CosimRpcTest, RejectedClientLeavesActiveOneConnected) {
439 auto received = std::make_shared<std::promise<uint32_t>>();
440 std::future<uint32_t> f = received->get_future();
441 auto conn = client->connectClientReceiver(
442 "out", [received](std::unique_ptr<SegmentedMessageData> &data) {
443 received->set_value(*
data->toMessageData().as<uint32_t>());
451 std::this_thread::sleep_for(100ms);
454 toClient->
write(msg(7));
455 ASSERT_EQ(f.wait_for(10s), std::future_status::ready);
456 EXPECT_EQ(f.get(), 7u);
459TEST_F(CosimRpcTest, RefusedParkedFrameDoesNotBlockTheRest) {
461 for (uint32_t i = 0; i < 3; ++i)
462 client->writeToServer(
"late", msg(i));
463 client->writeToServer(
"in", msg(99));
464 EXPECT_EQ(consumeOne(), 99u);
467 std::mutex receivedMutex;
468 std::vector<uint32_t> received;
469 bool refused =
false;
471 std::lock_guard<std::mutex> lock(receivedMutex);
476 received.push_back(*
data.as<uint32_t>());
482 std::lock_guard<std::mutex> lock(receivedMutex);
483 EXPECT_EQ(received, (std::vector<uint32_t>{1, 2}));
489TEST_F(CosimRpcTest, SecondReceiverRejected) {
493 auto received = std::make_shared<std::promise<uint32_t>>();
494 std::future<uint32_t> f = received->get_future();
495 auto conn = client->connectClientReceiver(
496 "out", [received](std::unique_ptr<SegmentedMessageData> &data) {
497 received->set_value(*
data->toMessageData().as<uint32_t>());
501 client->connectClientReceiver(
502 "out", [](std::unique_ptr<SegmentedMessageData> &) { return true; }),
506 toClient->
write(msg(5));
507 ASSERT_EQ(f.wait_for(10s), std::future_status::ready);
508 EXPECT_EQ(f.get(), 5u);
511TEST_F(CosimRpcTest, ToClientBlockingWrite) {
513 std::atomic<bool> accepting =
false;
514 std::atomic<int> received = 0;
515 auto conn = client->connectClientReceiver(
517 [&](std::unique_ptr<SegmentedMessageData> &) {
524 toClient->
write(msg(0));
525 toClient->
write(msg(1));
528 std::atomic<bool> written =
false;
529 std::thread writer([&] {
530 toClient->
write(msg(2));
533 std::this_thread::sleep_for(100ms);
534 EXPECT_FALSE(written);
536 ASSERT_TRUE(waitFor([&] {
return written.load(); }));
538 ASSERT_TRUE(waitFor([&] {
return received == 3; }));
541TEST_F(CosimRpcTest, ToClientBlockedWriteThrowsOnDisconnect) {
544 while (toClient->
tryWrite(msg(next)))
546 std::promise<bool> threw;
547 std::thread writer([&] {
549 toClient->
write(msg(next));
550 threw.set_value(
false);
551 }
catch (
const std::exception &) {
552 threw.set_value(
true);
555 std::this_thread::sleep_for(50ms);
557 auto f = threw.get_future();
558 ASSERT_EQ(f.wait_for(10s), std::future_status::ready);
559 EXPECT_TRUE(f.get());
static std::unique_ptr< RpcServer > server
static constexpr unsigned DefaultToClientMaxInFlight
Default maximum number of frames in flight (queued in the simulator or sent but not yet consumed by t...
A concrete flat message backed by a single vector of bytes.
const T * as() const
Cast to a type.
static MessageData from(T &t)
Cast from a type to its raw bytes.
virtual void connect(ReadCallback callback, const ConnectOptions &options={})
virtual std::future< MessageData > readAsync()
Asynchronous polling read.
virtual void disconnect() override
Disconnect the channel.
virtual void disconnect() override
void write(const MessageData &data)
A very basic blocking write API.
bool tryWrite(const MessageData &data)
A basic non-blocking write API.
A client for the cosim RPC server.
TODO: make this a proper backend (as much as possible).