starpc 0.44.0 → 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.
@@ -1,4 +1,4 @@
1
- //go:build deps_only
1
+ // go:build deps_only
2
2
 
3
3
  // Echo end-to-end test for starpc C++ implementation.
4
4
  // Tests unary and streaming RPC patterns.
@@ -12,17 +12,18 @@
12
12
  #include <thread>
13
13
 
14
14
  #include "echo/echo_srpc.pb.hpp"
15
+ #include "rpcstream/rpcstream.hpp"
15
16
  #include "srpc/rpcproto.pb.h"
16
17
  #include "srpc/starpc.hpp"
17
18
 
18
19
  namespace {
19
20
 
20
- const char* kTestBody = "hello world via starpc C++ e2e test";
21
+ const char *kTestBody = "hello world via starpc C++ e2e test";
21
22
 
22
23
  // InMemoryTransport provides an in-memory packet transport for testing.
23
24
  // Simulates a bidirectional connection between client and server.
24
25
  class InMemoryTransport {
25
- public:
26
+ public:
26
27
  struct Endpoint {
27
28
  std::mutex mtx;
28
29
  std::condition_variable cv;
@@ -30,8 +31,9 @@ class InMemoryTransport {
30
31
  bool closed = false;
31
32
  };
32
33
 
33
- InMemoryTransport() : client_endpoint_(std::make_shared<Endpoint>()),
34
- server_endpoint_(std::make_shared<Endpoint>()) {}
34
+ InMemoryTransport()
35
+ : client_endpoint_(std::make_shared<Endpoint>()),
36
+ server_endpoint_(std::make_shared<Endpoint>()) {}
35
37
 
36
38
  // Get writer for client to send to server
37
39
  std::shared_ptr<Endpoint> ClientToServer() { return server_endpoint_; }
@@ -42,7 +44,7 @@ class InMemoryTransport {
42
44
  // Get reader for server (reads from client)
43
45
  std::shared_ptr<Endpoint> ServerReader() { return server_endpoint_; }
44
46
 
45
- static void Send(std::shared_ptr<Endpoint> ep, const std::string& data) {
47
+ static void Send(std::shared_ptr<Endpoint> ep, const std::string &data) {
46
48
  std::lock_guard<std::mutex> lock(ep->mtx);
47
49
  if (!ep->closed) {
48
50
  ep->packets.push(data);
@@ -50,15 +52,16 @@ class InMemoryTransport {
50
52
  }
51
53
  }
52
54
 
53
- static bool Recv(std::shared_ptr<Endpoint> ep, std::string* out, int timeout_ms = 5000) {
55
+ static bool Recv(std::shared_ptr<Endpoint> ep, std::string *out,
56
+ int timeout_ms = 5000) {
54
57
  std::unique_lock<std::mutex> lock(ep->mtx);
55
58
  if (!ep->cv.wait_for(lock, std::chrono::milliseconds(timeout_ms), [&ep]() {
56
59
  return !ep->packets.empty() || ep->closed;
57
60
  })) {
58
- return false; // timeout
61
+ return false; // timeout
59
62
  }
60
63
  if (ep->packets.empty()) {
61
- return false; // closed
64
+ return false; // closed
62
65
  }
63
66
  *out = ep->packets.front();
64
67
  ep->packets.pop();
@@ -71,18 +74,18 @@ class InMemoryTransport {
71
74
  ep->cv.notify_all();
72
75
  }
73
76
 
74
- private:
77
+ private:
75
78
  std::shared_ptr<Endpoint> client_endpoint_;
76
79
  std::shared_ptr<Endpoint> server_endpoint_;
77
80
  };
78
81
 
79
82
  // InMemoryPacketWriter writes packets to an InMemoryTransport endpoint.
80
83
  class InMemoryPacketWriter : public starpc::PacketWriter {
81
- public:
84
+ public:
82
85
  explicit InMemoryPacketWriter(std::shared_ptr<InMemoryTransport::Endpoint> ep)
83
86
  : endpoint_(ep) {}
84
87
 
85
- starpc::Error WritePacket(const srpc::Packet& pkt) override {
88
+ starpc::Error WritePacket(const srpc::Packet &pkt) override {
86
89
  std::string data;
87
90
  if (!pkt.SerializeToString(&data)) {
88
91
  return starpc::Error::InvalidMessage;
@@ -96,20 +99,43 @@ class InMemoryPacketWriter : public starpc::PacketWriter {
96
99
  return starpc::Error::OK;
97
100
  }
98
101
 
99
- private:
102
+ private:
100
103
  std::shared_ptr<InMemoryTransport::Endpoint> endpoint_;
101
104
  };
102
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
+
103
126
  // EchoServerImpl implements the echo server.
104
127
  class EchoServerImpl : public echo::SRPCEchoerServer {
105
- public:
106
- starpc::Error Echo(const echo::EchoMsg& req, echo::EchoMsg* resp) override {
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 {
107
132
  resp->set_body(req.body());
108
133
  return starpc::Error::OK;
109
134
  }
110
135
 
111
- starpc::Error EchoServerStream(const echo::EchoMsg& req,
112
- echo::SRPCEchoer_EchoServerStreamStream* strm) override {
136
+ starpc::Error
137
+ EchoServerStream(const echo::EchoMsg &req,
138
+ echo::SRPCEchoer_EchoServerStreamStream *strm) override {
113
139
  // Send 5 copies of the message
114
140
  for (int i = 0; i < 5; i++) {
115
141
  echo::EchoMsg msg;
@@ -122,8 +148,8 @@ class EchoServerImpl : public echo::SRPCEchoerServer {
122
148
  return starpc::Error::OK;
123
149
  }
124
150
 
125
- starpc::Error EchoClientStream(echo::SRPCEchoer_EchoClientStreamStream* strm,
126
- echo::EchoMsg* resp) override {
151
+ starpc::Error EchoClientStream(echo::SRPCEchoer_EchoClientStreamStream *strm,
152
+ echo::EchoMsg *resp) override {
127
153
  // Receive first message and return it
128
154
  echo::EchoMsg msg;
129
155
  starpc::Error err = strm->Recv(&msg);
@@ -134,7 +160,8 @@ class EchoServerImpl : public echo::SRPCEchoerServer {
134
160
  return starpc::Error::OK;
135
161
  }
136
162
 
137
- starpc::Error EchoBidiStream(echo::SRPCEchoer_EchoBidiStreamStream* strm) override {
163
+ starpc::Error
164
+ EchoBidiStream(echo::SRPCEchoer_EchoBidiStreamStream *strm) override {
138
165
  // Echo back all received messages
139
166
  while (true) {
140
167
  echo::EchoMsg msg;
@@ -153,19 +180,34 @@ class EchoServerImpl : public echo::SRPCEchoerServer {
153
180
  return starpc::Error::OK;
154
181
  }
155
182
 
156
- starpc::Error RpcStream(echo::SRPCEchoer_RpcStreamStream* strm) override {
157
- // Simple echo for RpcStream - not used in tests
158
- return starpc::Error::Unimplemented;
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
+ });
159
197
  }
160
198
 
161
- starpc::Error DoNothing(const google::protobuf::Empty& req, google::protobuf::Empty* resp) override {
199
+ starpc::Error DoNothing(const google::protobuf::Empty &req,
200
+ google::protobuf::Empty *resp) override {
162
201
  // Just return OK
163
202
  return starpc::Error::OK;
164
203
  }
204
+
205
+ private:
206
+ starpc::Mux *rpc_stream_mux_ = nullptr;
165
207
  };
166
208
 
167
209
  // RunServer runs the server-side packet handling loop.
168
- void RunServer(InMemoryTransport* transport, starpc::Mux* mux) {
210
+ void RunServer(InMemoryTransport *transport, starpc::Mux *mux) {
169
211
  auto reader = transport->ServerReader();
170
212
  auto writer_ep = transport->ServerToClient();
171
213
  auto writer = std::make_unique<InMemoryPacketWriter>(writer_ep);
@@ -196,18 +238,19 @@ bool TestUnary() {
196
238
  EchoServerImpl server_impl;
197
239
  auto [handler, reg_err] = echo::SRPCRegisterEchoer(mux.get(), &server_impl);
198
240
  if (reg_err != starpc::Error::OK) {
199
- std::cerr << "FAILED: Registration error: " << starpc::ErrorString(reg_err) << std::endl;
241
+ std::cerr << "FAILED: Registration error: " << starpc::ErrorString(reg_err)
242
+ << std::endl;
200
243
  return false;
201
244
  }
202
245
 
203
246
  // Start server thread
204
- std::thread server_thread([&transport, &mux]() {
205
- RunServer(&transport, mux.get());
206
- });
247
+ std::thread server_thread(
248
+ [&transport, &mux]() { RunServer(&transport, mux.get()); });
207
249
 
208
250
  // Setup client
209
251
  auto client_rpc = starpc::NewClientRPC("echo.Echoer", "Echo");
210
- auto writer = std::make_unique<InMemoryPacketWriter>(transport.ClientToServer());
252
+ auto writer =
253
+ std::make_unique<InMemoryPacketWriter>(transport.ClientToServer());
211
254
 
212
255
  // Start client receive thread
213
256
  auto client_reader = transport.ClientReader();
@@ -233,7 +276,8 @@ bool TestUnary() {
233
276
 
234
277
  starpc::Error err = client_rpc->Start(writer.get(), true, req_data);
235
278
  if (err != starpc::Error::OK) {
236
- std::cerr << "FAILED: Start error: " << starpc::ErrorString(err) << std::endl;
279
+ std::cerr << "FAILED: Start error: " << starpc::ErrorString(err)
280
+ << std::endl;
237
281
  return false;
238
282
  }
239
283
 
@@ -241,7 +285,8 @@ bool TestUnary() {
241
285
  std::string resp_data;
242
286
  err = client_rpc->ReadOne(&resp_data);
243
287
  if (err != starpc::Error::OK) {
244
- std::cerr << "FAILED: ReadOne error: " << starpc::ErrorString(err) << std::endl;
288
+ std::cerr << "FAILED: ReadOne error: " << starpc::ErrorString(err)
289
+ << std::endl;
245
290
  return false;
246
291
  }
247
292
 
@@ -252,7 +297,430 @@ bool TestUnary() {
252
297
  }
253
298
 
254
299
  if (resp.body() != kTestBody) {
255
- std::cerr << "FAILED: Expected '" << kTestBody << "' got '" << resp.body() << "'" << std::endl;
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;
256
724
  return false;
257
725
  }
258
726
 
@@ -268,7 +736,267 @@ bool TestUnary() {
268
736
  return true;
269
737
  }
270
738
 
271
- } // namespace
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
272
1000
 
273
1001
  int main() {
274
1002
  std::cout << "=== starpc C++ E2E Tests ===" << std::endl;
@@ -282,8 +1010,39 @@ int main() {
282
1010
  failed++;
283
1011
  }
284
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
+
285
1043
  std::cout << std::endl;
286
- std::cout << "Results: " << passed << " passed, " << failed << " failed" << std::endl;
1044
+ std::cout << "Results: " << passed << " passed, " << failed << " failed"
1045
+ << std::endl;
287
1046
 
288
1047
  return failed > 0 ? 1 : 0;
289
1048
  }