starpc 0.43.1 → 0.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1048 @@
1
+ // go:build deps_only
2
+
3
+ // Echo end-to-end test for starpc C++ implementation.
4
+ // Tests unary and streaming RPC patterns.
5
+
6
+ #include <atomic>
7
+ #include <cassert>
8
+ #include <iostream>
9
+ #include <memory>
10
+ #include <mutex>
11
+ #include <queue>
12
+ #include <thread>
13
+
14
+ #include "echo/echo_srpc.pb.hpp"
15
+ #include "rpcstream/rpcstream.hpp"
16
+ #include "srpc/rpcproto.pb.h"
17
+ #include "srpc/starpc.hpp"
18
+
19
+ namespace {
20
+
21
+ const char *kTestBody = "hello world via starpc C++ e2e test";
22
+
23
+ // InMemoryTransport provides an in-memory packet transport for testing.
24
+ // Simulates a bidirectional connection between client and server.
25
+ class InMemoryTransport {
26
+ public:
27
+ struct Endpoint {
28
+ std::mutex mtx;
29
+ std::condition_variable cv;
30
+ std::queue<std::string> packets;
31
+ bool closed = false;
32
+ };
33
+
34
+ InMemoryTransport()
35
+ : client_endpoint_(std::make_shared<Endpoint>()),
36
+ server_endpoint_(std::make_shared<Endpoint>()) {}
37
+
38
+ // Get writer for client to send to server
39
+ std::shared_ptr<Endpoint> ClientToServer() { return server_endpoint_; }
40
+ // Get writer for server to send to client
41
+ std::shared_ptr<Endpoint> ServerToClient() { return client_endpoint_; }
42
+ // Get reader for client (reads from server)
43
+ std::shared_ptr<Endpoint> ClientReader() { return client_endpoint_; }
44
+ // Get reader for server (reads from client)
45
+ std::shared_ptr<Endpoint> ServerReader() { return server_endpoint_; }
46
+
47
+ static void Send(std::shared_ptr<Endpoint> ep, const std::string &data) {
48
+ std::lock_guard<std::mutex> lock(ep->mtx);
49
+ if (!ep->closed) {
50
+ ep->packets.push(data);
51
+ ep->cv.notify_all();
52
+ }
53
+ }
54
+
55
+ static bool Recv(std::shared_ptr<Endpoint> ep, std::string *out,
56
+ int timeout_ms = 5000) {
57
+ std::unique_lock<std::mutex> lock(ep->mtx);
58
+ if (!ep->cv.wait_for(lock, std::chrono::milliseconds(timeout_ms), [&ep]() {
59
+ return !ep->packets.empty() || ep->closed;
60
+ })) {
61
+ return false; // timeout
62
+ }
63
+ if (ep->packets.empty()) {
64
+ return false; // closed
65
+ }
66
+ *out = ep->packets.front();
67
+ ep->packets.pop();
68
+ return true;
69
+ }
70
+
71
+ static void Close(std::shared_ptr<Endpoint> ep) {
72
+ std::lock_guard<std::mutex> lock(ep->mtx);
73
+ ep->closed = true;
74
+ ep->cv.notify_all();
75
+ }
76
+
77
+ private:
78
+ std::shared_ptr<Endpoint> client_endpoint_;
79
+ std::shared_ptr<Endpoint> server_endpoint_;
80
+ };
81
+
82
+ // InMemoryPacketWriter writes packets to an InMemoryTransport endpoint.
83
+ class InMemoryPacketWriter : public starpc::PacketWriter {
84
+ public:
85
+ explicit InMemoryPacketWriter(std::shared_ptr<InMemoryTransport::Endpoint> ep)
86
+ : endpoint_(ep) {}
87
+
88
+ starpc::Error WritePacket(const srpc::Packet &pkt) override {
89
+ std::string data;
90
+ if (!pkt.SerializeToString(&data)) {
91
+ return starpc::Error::InvalidMessage;
92
+ }
93
+ InMemoryTransport::Send(endpoint_, data);
94
+ return starpc::Error::OK;
95
+ }
96
+
97
+ starpc::Error Close() override {
98
+ InMemoryTransport::Close(endpoint_);
99
+ return starpc::Error::OK;
100
+ }
101
+
102
+ private:
103
+ std::shared_ptr<InMemoryTransport::Endpoint> endpoint_;
104
+ };
105
+
106
+ // RpcStreamAdapter adapts generated stream classes to implement
107
+ // rpcstream::RpcStream
108
+ class RpcStreamAdapter : public rpcstream::RpcStream {
109
+ public:
110
+ explicit RpcStreamAdapter(echo::SRPCEchoer_RpcStreamStream *strm)
111
+ : strm_(strm) {}
112
+
113
+ starpc::Error Send(const rpcstream::RpcStreamPacket &msg) override {
114
+ return strm_->Send(msg);
115
+ }
116
+ starpc::Error Recv(rpcstream::RpcStreamPacket *msg) override {
117
+ return strm_->Recv(msg);
118
+ }
119
+ starpc::Error CloseSend() override { return starpc::Error::OK; }
120
+ starpc::Error Close() override { return starpc::Error::OK; }
121
+
122
+ private:
123
+ echo::SRPCEchoer_RpcStreamStream *strm_;
124
+ };
125
+
126
+ // EchoServerImpl implements the echo server.
127
+ class EchoServerImpl : public echo::SRPCEchoerServer {
128
+ public:
129
+ void SetRpcStreamMux(starpc::Mux *mux) { rpc_stream_mux_ = mux; }
130
+
131
+ starpc::Error Echo(const echo::EchoMsg &req, echo::EchoMsg *resp) override {
132
+ resp->set_body(req.body());
133
+ return starpc::Error::OK;
134
+ }
135
+
136
+ starpc::Error
137
+ EchoServerStream(const echo::EchoMsg &req,
138
+ echo::SRPCEchoer_EchoServerStreamStream *strm) override {
139
+ // Send 5 copies of the message
140
+ for (int i = 0; i < 5; i++) {
141
+ echo::EchoMsg msg;
142
+ msg.set_body(req.body());
143
+ starpc::Error err = strm->Send(msg);
144
+ if (err != starpc::Error::OK) {
145
+ return err;
146
+ }
147
+ }
148
+ return starpc::Error::OK;
149
+ }
150
+
151
+ starpc::Error EchoClientStream(echo::SRPCEchoer_EchoClientStreamStream *strm,
152
+ echo::EchoMsg *resp) override {
153
+ // Receive first message and return it
154
+ echo::EchoMsg msg;
155
+ starpc::Error err = strm->Recv(&msg);
156
+ if (err != starpc::Error::OK) {
157
+ return err;
158
+ }
159
+ resp->set_body(msg.body());
160
+ return starpc::Error::OK;
161
+ }
162
+
163
+ starpc::Error
164
+ EchoBidiStream(echo::SRPCEchoer_EchoBidiStreamStream *strm) override {
165
+ // Echo back all received messages
166
+ while (true) {
167
+ echo::EchoMsg msg;
168
+ starpc::Error err = strm->Recv(&msg);
169
+ if (err == starpc::Error::EOF_) {
170
+ break;
171
+ }
172
+ if (err != starpc::Error::OK) {
173
+ return err;
174
+ }
175
+ err = strm->Send(msg);
176
+ if (err != starpc::Error::OK) {
177
+ return err;
178
+ }
179
+ }
180
+ return starpc::Error::OK;
181
+ }
182
+
183
+ starpc::Error RpcStream(echo::SRPCEchoer_RpcStreamStream *strm) override {
184
+ // Wrap stream to implement rpcstream::RpcStream interface
185
+ RpcStreamAdapter adapter(strm);
186
+ return rpcstream::HandleRpcStream(
187
+ &adapter, [this](const std::string &component_id) {
188
+ if (!rpc_stream_mux_) {
189
+ return std::make_tuple(static_cast<starpc::Invoker *>(nullptr),
190
+ std::function<void()>(),
191
+ starpc::Error::Unimplemented);
192
+ }
193
+ return std::make_tuple(
194
+ static_cast<starpc::Invoker *>(rpc_stream_mux_),
195
+ std::function<void()>(), starpc::Error::OK);
196
+ });
197
+ }
198
+
199
+ starpc::Error DoNothing(const google::protobuf::Empty &req,
200
+ google::protobuf::Empty *resp) override {
201
+ // Just return OK
202
+ return starpc::Error::OK;
203
+ }
204
+
205
+ private:
206
+ starpc::Mux *rpc_stream_mux_ = nullptr;
207
+ };
208
+
209
+ // RunServer runs the server-side packet handling loop.
210
+ void RunServer(InMemoryTransport *transport, starpc::Mux *mux) {
211
+ auto reader = transport->ServerReader();
212
+ auto writer_ep = transport->ServerToClient();
213
+ auto writer = std::make_unique<InMemoryPacketWriter>(writer_ep);
214
+
215
+ auto server_rpc = starpc::NewServerRPC(mux, writer.get());
216
+
217
+ while (true) {
218
+ std::string data;
219
+ if (!InMemoryTransport::Recv(reader, &data)) {
220
+ break;
221
+ }
222
+ starpc::Error err = server_rpc->HandlePacketData(data);
223
+ if (err != starpc::Error::OK && err != starpc::Error::Completed) {
224
+ std::cerr << "Server error: " << starpc::ErrorString(err) << std::endl;
225
+ break;
226
+ }
227
+ }
228
+ }
229
+
230
+ // Test unary RPC
231
+ bool TestUnary() {
232
+ std::cout << "Testing Unary RPC... " << std::flush;
233
+
234
+ InMemoryTransport transport;
235
+
236
+ // Setup server
237
+ auto mux = starpc::NewMux();
238
+ EchoServerImpl server_impl;
239
+ auto [handler, reg_err] = echo::SRPCRegisterEchoer(mux.get(), &server_impl);
240
+ if (reg_err != starpc::Error::OK) {
241
+ std::cerr << "FAILED: Registration error: " << starpc::ErrorString(reg_err)
242
+ << std::endl;
243
+ return false;
244
+ }
245
+
246
+ // Start server thread
247
+ std::thread server_thread(
248
+ [&transport, &mux]() { RunServer(&transport, mux.get()); });
249
+
250
+ // Setup client
251
+ auto client_rpc = starpc::NewClientRPC("echo.Echoer", "Echo");
252
+ auto writer =
253
+ std::make_unique<InMemoryPacketWriter>(transport.ClientToServer());
254
+
255
+ // Start client receive thread
256
+ auto client_reader = transport.ClientReader();
257
+ std::thread client_recv_thread([&client_rpc, &client_reader]() {
258
+ while (true) {
259
+ std::string data;
260
+ if (!InMemoryTransport::Recv(client_reader, &data)) {
261
+ client_rpc->HandleStreamClose(starpc::Error::EOF_);
262
+ break;
263
+ }
264
+ starpc::Error err = client_rpc->HandlePacketData(data);
265
+ if (err != starpc::Error::OK) {
266
+ break;
267
+ }
268
+ }
269
+ });
270
+
271
+ // Send request
272
+ echo::EchoMsg req;
273
+ req.set_body(kTestBody);
274
+ std::string req_data;
275
+ req.SerializeToString(&req_data);
276
+
277
+ starpc::Error err = client_rpc->Start(writer.get(), true, req_data);
278
+ if (err != starpc::Error::OK) {
279
+ std::cerr << "FAILED: Start error: " << starpc::ErrorString(err)
280
+ << std::endl;
281
+ return false;
282
+ }
283
+
284
+ // Read response
285
+ std::string resp_data;
286
+ err = client_rpc->ReadOne(&resp_data);
287
+ if (err != starpc::Error::OK) {
288
+ std::cerr << "FAILED: ReadOne error: " << starpc::ErrorString(err)
289
+ << std::endl;
290
+ return false;
291
+ }
292
+
293
+ echo::EchoMsg resp;
294
+ if (!resp.ParseFromString(resp_data)) {
295
+ std::cerr << "FAILED: Parse response error" << std::endl;
296
+ return false;
297
+ }
298
+
299
+ if (resp.body() != kTestBody) {
300
+ std::cerr << "FAILED: Expected '" << kTestBody << "' got '" << resp.body()
301
+ << "'" << std::endl;
302
+ return false;
303
+ }
304
+
305
+ // Cleanup
306
+ client_rpc->Close();
307
+ writer->Close();
308
+ InMemoryTransport::Close(transport.ServerReader());
309
+
310
+ client_recv_thread.join();
311
+ server_thread.join();
312
+
313
+ std::cout << "PASSED" << std::endl;
314
+ return true;
315
+ }
316
+
317
+ // Test server streaming RPC
318
+ bool TestServerStream() {
319
+ std::cout << "Testing ServerStream RPC... " << std::flush;
320
+
321
+ InMemoryTransport transport;
322
+
323
+ // Setup server
324
+ auto mux = starpc::NewMux();
325
+ EchoServerImpl server_impl;
326
+ auto [handler, reg_err] = echo::SRPCRegisterEchoer(mux.get(), &server_impl);
327
+ if (reg_err != starpc::Error::OK) {
328
+ std::cerr << "FAILED: Registration error: " << starpc::ErrorString(reg_err)
329
+ << std::endl;
330
+ return false;
331
+ }
332
+
333
+ // Start server thread
334
+ std::thread server_thread(
335
+ [&transport, &mux]() { RunServer(&transport, mux.get()); });
336
+
337
+ // Setup client
338
+ auto client_rpc = starpc::NewClientRPC("echo.Echoer", "EchoServerStream");
339
+ auto writer =
340
+ std::make_unique<InMemoryPacketWriter>(transport.ClientToServer());
341
+
342
+ // Start client receive thread
343
+ auto client_reader = transport.ClientReader();
344
+ std::atomic<bool> client_done{false};
345
+ std::thread client_recv_thread([&client_rpc, &client_reader, &client_done]() {
346
+ while (!client_done.load()) {
347
+ std::string data;
348
+ if (!InMemoryTransport::Recv(client_reader, &data, 100)) {
349
+ continue;
350
+ }
351
+ starpc::Error err = client_rpc->HandlePacketData(data);
352
+ if (err != starpc::Error::OK) {
353
+ break;
354
+ }
355
+ }
356
+ });
357
+
358
+ // Send request
359
+ echo::EchoMsg req;
360
+ req.set_body(kTestBody);
361
+ std::string req_data;
362
+ req.SerializeToString(&req_data);
363
+
364
+ starpc::Error err = client_rpc->Start(writer.get(), true, req_data);
365
+ if (err != starpc::Error::OK) {
366
+ std::cerr << "FAILED: Start error: " << starpc::ErrorString(err)
367
+ << std::endl;
368
+ client_done.store(true);
369
+ client_recv_thread.join();
370
+ return false;
371
+ }
372
+
373
+ // Read 5 responses
374
+ int received = 0;
375
+ for (int i = 0; i < 5; i++) {
376
+ std::string resp_data;
377
+ err = client_rpc->ReadOne(&resp_data);
378
+ if (err != starpc::Error::OK) {
379
+ std::cerr << "FAILED: ReadOne error at message " << i << ": "
380
+ << starpc::ErrorString(err) << std::endl;
381
+ client_done.store(true);
382
+ client_recv_thread.join();
383
+ return false;
384
+ }
385
+
386
+ echo::EchoMsg resp;
387
+ if (!resp.ParseFromString(resp_data)) {
388
+ std::cerr << "FAILED: Parse response error at message " << i << std::endl;
389
+ client_done.store(true);
390
+ client_recv_thread.join();
391
+ return false;
392
+ }
393
+
394
+ if (resp.body() != kTestBody) {
395
+ std::cerr << "FAILED: Expected '" << kTestBody << "' got '" << resp.body()
396
+ << "'" << std::endl;
397
+ client_done.store(true);
398
+ client_recv_thread.join();
399
+ return false;
400
+ }
401
+ received++;
402
+ }
403
+
404
+ if (received != 5) {
405
+ std::cerr << "FAILED: Expected 5 messages, got " << received << std::endl;
406
+ client_done.store(true);
407
+ client_recv_thread.join();
408
+ return false;
409
+ }
410
+
411
+ // Cleanup
412
+ client_rpc->Close();
413
+ writer->Close();
414
+ client_done.store(true);
415
+ InMemoryTransport::Close(transport.ServerReader());
416
+
417
+ client_recv_thread.join();
418
+ server_thread.join();
419
+
420
+ std::cout << "PASSED" << std::endl;
421
+ return true;
422
+ }
423
+
424
+ // Test client streaming RPC
425
+ bool TestClientStream() {
426
+ std::cout << "Testing ClientStream RPC... " << std::flush;
427
+
428
+ InMemoryTransport transport;
429
+
430
+ // Setup server
431
+ auto mux = starpc::NewMux();
432
+ EchoServerImpl server_impl;
433
+ auto [handler, reg_err] = echo::SRPCRegisterEchoer(mux.get(), &server_impl);
434
+ if (reg_err != starpc::Error::OK) {
435
+ std::cerr << "FAILED: Registration error: " << starpc::ErrorString(reg_err)
436
+ << std::endl;
437
+ return false;
438
+ }
439
+
440
+ // Start server thread
441
+ std::thread server_thread(
442
+ [&transport, &mux]() { RunServer(&transport, mux.get()); });
443
+
444
+ // Setup client
445
+ auto client_rpc = starpc::NewClientRPC("echo.Echoer", "EchoClientStream");
446
+ auto writer =
447
+ std::make_unique<InMemoryPacketWriter>(transport.ClientToServer());
448
+
449
+ // Start client receive thread
450
+ auto client_reader = transport.ClientReader();
451
+ std::atomic<bool> client_done{false};
452
+ std::thread client_recv_thread([&client_rpc, &client_reader, &client_done]() {
453
+ while (!client_done.load()) {
454
+ std::string data;
455
+ if (!InMemoryTransport::Recv(client_reader, &data, 100)) {
456
+ continue;
457
+ }
458
+ starpc::Error err = client_rpc->HandlePacketData(data);
459
+ if (err != starpc::Error::OK) {
460
+ break;
461
+ }
462
+ }
463
+ });
464
+
465
+ // Send request (no initial data for streaming)
466
+ starpc::Error err = client_rpc->Start(writer.get(), false, "");
467
+ if (err != starpc::Error::OK) {
468
+ std::cerr << "FAILED: Start error: " << starpc::ErrorString(err)
469
+ << std::endl;
470
+ client_done.store(true);
471
+ client_recv_thread.join();
472
+ return false;
473
+ }
474
+
475
+ // Send first message using WriteCallData
476
+ echo::EchoMsg req;
477
+ req.set_body(kTestBody);
478
+ std::string req_data;
479
+ req.SerializeToString(&req_data);
480
+
481
+ err = client_rpc->WriteCallData(req_data, false, false, starpc::Error::OK);
482
+ if (err != starpc::Error::OK) {
483
+ std::cerr << "FAILED: WriteCallData error: " << starpc::ErrorString(err)
484
+ << std::endl;
485
+ client_done.store(true);
486
+ client_recv_thread.join();
487
+ return false;
488
+ }
489
+
490
+ // Close send side to indicate we're done sending
491
+ err = client_rpc->WriteCallData("", false, true, starpc::Error::OK);
492
+ if (err != starpc::Error::OK) {
493
+ std::cerr << "FAILED: WriteCallData (close) error: "
494
+ << starpc::ErrorString(err) << std::endl;
495
+ client_done.store(true);
496
+ client_recv_thread.join();
497
+ return false;
498
+ }
499
+
500
+ // Read response
501
+ std::string resp_data;
502
+ err = client_rpc->ReadOne(&resp_data);
503
+ if (err != starpc::Error::OK) {
504
+ std::cerr << "FAILED: ReadOne error: " << starpc::ErrorString(err)
505
+ << std::endl;
506
+ client_done.store(true);
507
+ client_recv_thread.join();
508
+ return false;
509
+ }
510
+
511
+ echo::EchoMsg resp;
512
+ if (!resp.ParseFromString(resp_data)) {
513
+ std::cerr << "FAILED: Parse response error" << std::endl;
514
+ client_done.store(true);
515
+ client_recv_thread.join();
516
+ return false;
517
+ }
518
+
519
+ if (resp.body() != kTestBody) {
520
+ std::cerr << "FAILED: Expected '" << kTestBody << "' got '" << resp.body()
521
+ << "'" << std::endl;
522
+ client_done.store(true);
523
+ client_recv_thread.join();
524
+ return false;
525
+ }
526
+
527
+ // Cleanup
528
+ client_rpc->Close();
529
+ writer->Close();
530
+ client_done.store(true);
531
+ InMemoryTransport::Close(transport.ServerReader());
532
+
533
+ client_recv_thread.join();
534
+ server_thread.join();
535
+
536
+ std::cout << "PASSED" << std::endl;
537
+ return true;
538
+ }
539
+
540
+ // Test bidirectional streaming RPC
541
+ bool TestBidiStream() {
542
+ std::cout << "Testing BidiStream RPC... " << std::flush;
543
+
544
+ InMemoryTransport transport;
545
+
546
+ // Setup server
547
+ auto mux = starpc::NewMux();
548
+ EchoServerImpl server_impl;
549
+ auto [handler, reg_err] = echo::SRPCRegisterEchoer(mux.get(), &server_impl);
550
+ if (reg_err != starpc::Error::OK) {
551
+ std::cerr << "FAILED: Registration error: " << starpc::ErrorString(reg_err)
552
+ << std::endl;
553
+ return false;
554
+ }
555
+
556
+ // Start server thread
557
+ std::thread server_thread(
558
+ [&transport, &mux]() { RunServer(&transport, mux.get()); });
559
+
560
+ // Setup client
561
+ auto client_rpc = starpc::NewClientRPC("echo.Echoer", "EchoBidiStream");
562
+ auto writer =
563
+ std::make_unique<InMemoryPacketWriter>(transport.ClientToServer());
564
+
565
+ // Start client receive thread
566
+ auto client_reader = transport.ClientReader();
567
+ std::atomic<bool> client_done{false};
568
+ std::thread client_recv_thread([&client_rpc, &client_reader, &client_done]() {
569
+ while (!client_done.load()) {
570
+ std::string data;
571
+ if (!InMemoryTransport::Recv(client_reader, &data, 100)) {
572
+ continue;
573
+ }
574
+ starpc::Error err = client_rpc->HandlePacketData(data);
575
+ if (err != starpc::Error::OK) {
576
+ break;
577
+ }
578
+ }
579
+ });
580
+
581
+ // Send request (no initial data for bidi streaming)
582
+ starpc::Error err = client_rpc->Start(writer.get(), false, "");
583
+ if (err != starpc::Error::OK) {
584
+ std::cerr << "FAILED: Start error: " << starpc::ErrorString(err)
585
+ << std::endl;
586
+ client_done.store(true);
587
+ client_recv_thread.join();
588
+ return false;
589
+ }
590
+
591
+ // Send 3 messages and receive 3 responses
592
+ for (int i = 0; i < 3; i++) {
593
+ // Send message
594
+ echo::EchoMsg req;
595
+ req.set_body(kTestBody);
596
+ std::string req_data;
597
+ req.SerializeToString(&req_data);
598
+
599
+ err = client_rpc->WriteCallData(req_data, false, false, starpc::Error::OK);
600
+ if (err != starpc::Error::OK) {
601
+ std::cerr << "FAILED: WriteCallData error at message " << i << ": "
602
+ << starpc::ErrorString(err) << std::endl;
603
+ client_done.store(true);
604
+ client_recv_thread.join();
605
+ return false;
606
+ }
607
+
608
+ // Receive echoed response
609
+ std::string resp_data;
610
+ err = client_rpc->ReadOne(&resp_data);
611
+ if (err != starpc::Error::OK) {
612
+ std::cerr << "FAILED: ReadOne error at message " << i << ": "
613
+ << starpc::ErrorString(err) << std::endl;
614
+ client_done.store(true);
615
+ client_recv_thread.join();
616
+ return false;
617
+ }
618
+
619
+ echo::EchoMsg resp;
620
+ if (!resp.ParseFromString(resp_data)) {
621
+ std::cerr << "FAILED: Parse response error at message " << i << std::endl;
622
+ client_done.store(true);
623
+ client_recv_thread.join();
624
+ return false;
625
+ }
626
+
627
+ if (resp.body() != kTestBody) {
628
+ std::cerr << "FAILED: Expected '" << kTestBody << "' got '" << resp.body()
629
+ << "'" << std::endl;
630
+ client_done.store(true);
631
+ client_recv_thread.join();
632
+ return false;
633
+ }
634
+ }
635
+
636
+ // Close send side
637
+ err = client_rpc->WriteCallData("", false, true, starpc::Error::OK);
638
+ if (err != starpc::Error::OK) {
639
+ std::cerr << "FAILED: WriteCallData (close) error: "
640
+ << starpc::ErrorString(err) << std::endl;
641
+ client_done.store(true);
642
+ client_recv_thread.join();
643
+ return false;
644
+ }
645
+
646
+ // Cleanup
647
+ client_rpc->Close();
648
+ writer->Close();
649
+ client_done.store(true);
650
+ InMemoryTransport::Close(transport.ServerReader());
651
+
652
+ client_recv_thread.join();
653
+ server_thread.join();
654
+
655
+ std::cout << "PASSED" << std::endl;
656
+ return true;
657
+ }
658
+
659
+ // Test DoNothing RPC
660
+ bool TestDoNothing() {
661
+ std::cout << "Testing DoNothing RPC... " << std::flush;
662
+
663
+ InMemoryTransport transport;
664
+
665
+ // Setup server
666
+ auto mux = starpc::NewMux();
667
+ EchoServerImpl server_impl;
668
+ auto [handler, reg_err] = echo::SRPCRegisterEchoer(mux.get(), &server_impl);
669
+ if (reg_err != starpc::Error::OK) {
670
+ std::cerr << "FAILED: Registration error: " << starpc::ErrorString(reg_err)
671
+ << std::endl;
672
+ return false;
673
+ }
674
+
675
+ // Start server thread
676
+ std::thread server_thread(
677
+ [&transport, &mux]() { RunServer(&transport, mux.get()); });
678
+
679
+ // Setup client
680
+ auto client_rpc = starpc::NewClientRPC("echo.Echoer", "DoNothing");
681
+ auto writer =
682
+ std::make_unique<InMemoryPacketWriter>(transport.ClientToServer());
683
+
684
+ // Start client receive thread
685
+ auto client_reader = transport.ClientReader();
686
+ std::thread client_recv_thread([&client_rpc, &client_reader]() {
687
+ while (true) {
688
+ std::string data;
689
+ if (!InMemoryTransport::Recv(client_reader, &data)) {
690
+ client_rpc->HandleStreamClose(starpc::Error::EOF_);
691
+ break;
692
+ }
693
+ starpc::Error err = client_rpc->HandlePacketData(data);
694
+ if (err != starpc::Error::OK) {
695
+ break;
696
+ }
697
+ }
698
+ });
699
+
700
+ // Send request with empty message
701
+ google::protobuf::Empty req;
702
+ std::string req_data;
703
+ req.SerializeToString(&req_data);
704
+
705
+ starpc::Error err = client_rpc->Start(writer.get(), true, req_data);
706
+ if (err != starpc::Error::OK) {
707
+ std::cerr << "FAILED: Start error: " << starpc::ErrorString(err)
708
+ << std::endl;
709
+ return false;
710
+ }
711
+
712
+ // Read response
713
+ std::string resp_data;
714
+ err = client_rpc->ReadOne(&resp_data);
715
+ if (err != starpc::Error::OK) {
716
+ std::cerr << "FAILED: ReadOne error: " << starpc::ErrorString(err)
717
+ << std::endl;
718
+ return false;
719
+ }
720
+
721
+ google::protobuf::Empty resp;
722
+ if (!resp.ParseFromString(resp_data)) {
723
+ std::cerr << "FAILED: Parse response error" << std::endl;
724
+ return false;
725
+ }
726
+
727
+ // Cleanup
728
+ client_rpc->Close();
729
+ writer->Close();
730
+ InMemoryTransport::Close(transport.ServerReader());
731
+
732
+ client_recv_thread.join();
733
+ server_thread.join();
734
+
735
+ std::cout << "PASSED" << std::endl;
736
+ return true;
737
+ }
738
+
739
+ // RpcStreamClientWrapper wraps a generated RpcStream client.
740
+ class RpcStreamClientWrapper : public rpcstream::RpcStream {
741
+ public:
742
+ explicit RpcStreamClientWrapper(echo::SRPCEchoer_RpcStreamClient *client)
743
+ : client_(client) {}
744
+
745
+ starpc::Error Send(const rpcstream::RpcStreamPacket &msg) override {
746
+ return client_->Send(msg);
747
+ }
748
+ starpc::Error Recv(rpcstream::RpcStreamPacket *msg) override {
749
+ return client_->Recv(msg);
750
+ }
751
+ starpc::Error CloseSend() override { return client_->CloseSend(); }
752
+ starpc::Error Close() override { return client_->Close(); }
753
+
754
+ private:
755
+ echo::SRPCEchoer_RpcStreamClient *client_;
756
+ };
757
+
758
+ // TestClientContext holds all resources for a test client with proper lifetime.
759
+ struct TestClientContext {
760
+ std::unique_ptr<starpc::ClientRPC> client_rpc;
761
+ std::unique_ptr<InMemoryPacketWriter> writer;
762
+ std::thread recv_thread;
763
+ std::atomic<bool> done{false};
764
+
765
+ ~TestClientContext() {
766
+ done.store(true);
767
+ if (recv_thread.joinable()) {
768
+ recv_thread.join();
769
+ }
770
+ }
771
+ };
772
+
773
+ // CreateTestClient creates a client with a joinable receive thread.
774
+ std::unique_ptr<TestClientContext>
775
+ CreateTestClient(InMemoryTransport &transport, const std::string &service,
776
+ const std::string &method) {
777
+ auto ctx = std::make_unique<TestClientContext>();
778
+ ctx->client_rpc = starpc::NewClientRPC(service, method);
779
+ ctx->writer =
780
+ std::make_unique<InMemoryPacketWriter>(transport.ClientToServer());
781
+
782
+ auto client_reader = transport.ClientReader();
783
+ auto *rpc = ctx->client_rpc.get();
784
+ ctx->recv_thread = std::thread([rpc, client_reader, &done = ctx->done]() {
785
+ while (!done.load()) {
786
+ std::string data;
787
+ if (!InMemoryTransport::Recv(client_reader, &data, 100)) {
788
+ if (done.load())
789
+ break;
790
+ continue;
791
+ }
792
+ starpc::Error err = rpc->HandlePacketData(data);
793
+ if (err != starpc::Error::OK) {
794
+ break;
795
+ }
796
+ }
797
+ rpc->HandleStreamClose(starpc::Error::EOF_);
798
+ });
799
+
800
+ return ctx;
801
+ }
802
+
803
+ // Test RpcStream RPC using OpenRpcStream, HandleRpcStream, and RpcStreamWriter.
804
+ // This test verifies:
805
+ // 1. Client opens an RpcStream to the server
806
+ // 2. Server handles init/ack via HandleRpcStream
807
+ // 3. Client sends Echo call through the RpcStream
808
+ // 4. Server forwards to nested mux and returns response
809
+ bool TestRpcStream() {
810
+ std::cout << "Testing RpcStream RPC... " << std::flush;
811
+
812
+ InMemoryTransport transport;
813
+
814
+ // Setup server with nested mux
815
+ auto mux = starpc::NewMux();
816
+ auto nested_mux = starpc::NewMux();
817
+ EchoServerImpl server_impl;
818
+ server_impl.SetRpcStreamMux(nested_mux.get());
819
+
820
+ // Register echo service on both muxes
821
+ auto [handler, reg_err] = echo::SRPCRegisterEchoer(mux.get(), &server_impl);
822
+ if (reg_err != starpc::Error::OK) {
823
+ std::cerr << "FAILED: Registration error: " << starpc::ErrorString(reg_err)
824
+ << std::endl;
825
+ return false;
826
+ }
827
+
828
+ auto [nested_handler, nested_reg_err] =
829
+ echo::SRPCRegisterEchoer(nested_mux.get(), &server_impl);
830
+ if (nested_reg_err != starpc::Error::OK) {
831
+ std::cerr << "FAILED: Nested registration error: "
832
+ << starpc::ErrorString(nested_reg_err) << std::endl;
833
+ return false;
834
+ }
835
+
836
+ // Start server thread
837
+ std::thread server_thread(
838
+ [&transport, &mux]() { RunServer(&transport, mux.get()); });
839
+
840
+ // Create client for RpcStream call
841
+ auto ctx = CreateTestClient(transport, "echo.Echoer", "RpcStream");
842
+
843
+ // Start the RpcStream call (no initial data for bidi stream)
844
+ starpc::Error err = ctx->client_rpc->Start(ctx->writer.get(), false, "");
845
+ if (err != starpc::Error::OK) {
846
+ std::cerr << "FAILED: Start error: " << starpc::ErrorString(err)
847
+ << std::endl;
848
+ ctx->done.store(true);
849
+ InMemoryTransport::Close(transport.ClientReader());
850
+ InMemoryTransport::Close(transport.ServerReader());
851
+ server_thread.join();
852
+ return false;
853
+ }
854
+
855
+ // Send init packet
856
+ rpcstream::RpcStreamPacket init_pkt;
857
+ init_pkt.mutable_init()->set_component_id("");
858
+ std::string init_data;
859
+ init_pkt.SerializeToString(&init_data);
860
+ err = ctx->client_rpc->WriteCallData(init_data, false, false,
861
+ starpc::Error::OK);
862
+ if (err != starpc::Error::OK) {
863
+ std::cerr << "FAILED: Send init error: " << starpc::ErrorString(err)
864
+ << std::endl;
865
+ ctx->done.store(true);
866
+ InMemoryTransport::Close(transport.ClientReader());
867
+ InMemoryTransport::Close(transport.ServerReader());
868
+ server_thread.join();
869
+ return false;
870
+ }
871
+
872
+ // Read ack packet
873
+ std::string ack_data;
874
+ err = ctx->client_rpc->ReadOne(&ack_data);
875
+ if (err != starpc::Error::OK) {
876
+ std::cerr << "FAILED: Read ack error: " << starpc::ErrorString(err)
877
+ << std::endl;
878
+ ctx->done.store(true);
879
+ InMemoryTransport::Close(transport.ClientReader());
880
+ InMemoryTransport::Close(transport.ServerReader());
881
+ server_thread.join();
882
+ return false;
883
+ }
884
+
885
+ rpcstream::RpcStreamPacket ack_pkt;
886
+ if (!ack_pkt.ParseFromString(ack_data) || !ack_pkt.has_ack()) {
887
+ std::cerr << "FAILED: Invalid ack packet" << std::endl;
888
+ ctx->done.store(true);
889
+ InMemoryTransport::Close(transport.ClientReader());
890
+ InMemoryTransport::Close(transport.ServerReader());
891
+ server_thread.join();
892
+ return false;
893
+ }
894
+
895
+ if (!ack_pkt.ack().error().empty()) {
896
+ std::cerr << "FAILED: Ack error: " << ack_pkt.ack().error() << std::endl;
897
+ ctx->done.store(true);
898
+ InMemoryTransport::Close(transport.ClientReader());
899
+ InMemoryTransport::Close(transport.ServerReader());
900
+ server_thread.join();
901
+ return false;
902
+ }
903
+
904
+ // Create CallStart packet for Echo call
905
+ echo::EchoMsg req;
906
+ req.set_body(kTestBody);
907
+ std::string req_data;
908
+ req.SerializeToString(&req_data);
909
+ auto call_start =
910
+ starpc::NewCallStartPacket("echo.Echoer", "Echo", req_data, true);
911
+
912
+ // Wrap in RpcStreamPacket and send
913
+ std::string call_start_data;
914
+ call_start->SerializeToString(&call_start_data);
915
+ rpcstream::RpcStreamPacket data_pkt;
916
+ data_pkt.set_data(call_start_data);
917
+ std::string pkt_data;
918
+ data_pkt.SerializeToString(&pkt_data);
919
+ err =
920
+ ctx->client_rpc->WriteCallData(pkt_data, false, false, starpc::Error::OK);
921
+ if (err != starpc::Error::OK) {
922
+ std::cerr << "FAILED: Send CallStart error: " << starpc::ErrorString(err)
923
+ << std::endl;
924
+ ctx->done.store(true);
925
+ InMemoryTransport::Close(transport.ClientReader());
926
+ InMemoryTransport::Close(transport.ServerReader());
927
+ server_thread.join();
928
+ return false;
929
+ }
930
+
931
+ // Read response (RpcStreamPacket containing srpc::Packet with CallData)
932
+ std::string resp_data;
933
+ err = ctx->client_rpc->ReadOne(&resp_data);
934
+ if (err != starpc::Error::OK) {
935
+ std::cerr << "FAILED: Read response error: " << starpc::ErrorString(err)
936
+ << std::endl;
937
+ ctx->done.store(true);
938
+ InMemoryTransport::Close(transport.ClientReader());
939
+ InMemoryTransport::Close(transport.ServerReader());
940
+ server_thread.join();
941
+ return false;
942
+ }
943
+
944
+ rpcstream::RpcStreamPacket resp_pkt;
945
+ if (!resp_pkt.ParseFromString(resp_data) || !resp_pkt.has_data()) {
946
+ std::cerr << "FAILED: Invalid response packet" << std::endl;
947
+ ctx->done.store(true);
948
+ InMemoryTransport::Close(transport.ClientReader());
949
+ InMemoryTransport::Close(transport.ServerReader());
950
+ server_thread.join();
951
+ return false;
952
+ }
953
+
954
+ srpc::Packet inner_pkt;
955
+ if (!inner_pkt.ParseFromString(resp_pkt.data()) ||
956
+ !inner_pkt.has_call_data()) {
957
+ std::cerr << "FAILED: Invalid inner packet" << std::endl;
958
+ ctx->done.store(true);
959
+ InMemoryTransport::Close(transport.ClientReader());
960
+ InMemoryTransport::Close(transport.ServerReader());
961
+ server_thread.join();
962
+ return false;
963
+ }
964
+
965
+ echo::EchoMsg resp;
966
+ if (!resp.ParseFromString(inner_pkt.call_data().data())) {
967
+ std::cerr << "FAILED: Parse EchoMsg error" << std::endl;
968
+ ctx->done.store(true);
969
+ InMemoryTransport::Close(transport.ClientReader());
970
+ InMemoryTransport::Close(transport.ServerReader());
971
+ server_thread.join();
972
+ return false;
973
+ }
974
+
975
+ if (resp.body() != kTestBody) {
976
+ std::cerr << "FAILED: Expected '" << kTestBody << "' got '" << resp.body()
977
+ << "'" << std::endl;
978
+ ctx->client_rpc->Close();
979
+ ctx->done.store(true);
980
+ InMemoryTransport::Close(transport.ClientReader());
981
+ InMemoryTransport::Close(transport.ServerReader());
982
+ server_thread.join();
983
+ return false;
984
+ }
985
+
986
+ // Close the RPC to signal server we're done
987
+ ctx->client_rpc->Close();
988
+
989
+ // Cleanup
990
+ ctx->done.store(true);
991
+ InMemoryTransport::Close(transport.ClientReader());
992
+ InMemoryTransport::Close(transport.ServerReader());
993
+ server_thread.join();
994
+
995
+ std::cout << "PASSED" << std::endl;
996
+ return true;
997
+ }
998
+
999
+ } // namespace
1000
+
1001
+ int main() {
1002
+ std::cout << "=== starpc C++ E2E Tests ===" << std::endl;
1003
+
1004
+ int passed = 0;
1005
+ int failed = 0;
1006
+
1007
+ if (TestUnary()) {
1008
+ passed++;
1009
+ } else {
1010
+ failed++;
1011
+ }
1012
+
1013
+ if (TestServerStream()) {
1014
+ passed++;
1015
+ } else {
1016
+ failed++;
1017
+ }
1018
+
1019
+ if (TestClientStream()) {
1020
+ passed++;
1021
+ } else {
1022
+ failed++;
1023
+ }
1024
+
1025
+ if (TestBidiStream()) {
1026
+ passed++;
1027
+ } else {
1028
+ failed++;
1029
+ }
1030
+
1031
+ if (TestDoNothing()) {
1032
+ passed++;
1033
+ } else {
1034
+ failed++;
1035
+ }
1036
+
1037
+ if (TestRpcStream()) {
1038
+ passed++;
1039
+ } else {
1040
+ failed++;
1041
+ }
1042
+
1043
+ std::cout << std::endl;
1044
+ std::cout << "Results: " << passed << " passed, " << failed << " failed"
1045
+ << std::endl;
1046
+
1047
+ return failed > 0 ? 1 : 0;
1048
+ }