starpc 0.52.1 → 0.52.2

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.
Files changed (91) hide show
  1. package/README.md +37 -8
  2. package/dist/srpc/client-rpc.d.ts +1 -0
  3. package/dist/srpc/client-rpc.js +9 -0
  4. package/dist/srpc/client-rpc.test.d.ts +1 -0
  5. package/dist/srpc/client-rpc.test.js +50 -0
  6. package/dist/srpc/common-rpc.d.ts +5 -0
  7. package/dist/srpc/common-rpc.js +37 -8
  8. package/dist/srpc/errors.d.ts +2 -0
  9. package/dist/srpc/errors.js +14 -0
  10. package/dist/srpc/index.d.ts +1 -1
  11. package/dist/srpc/index.js +1 -1
  12. package/echo/Cargo.toml +8 -0
  13. package/echo/echo_e2e_test.cpp +542 -985
  14. package/echo/echo_srpc.pb.cpp +3 -5
  15. package/echo/echo_srpc.pb.go +6 -9
  16. package/echo/echo_srpc.pb.hpp +23 -3
  17. package/echo/echo_srpc.pb.rs +61 -26
  18. package/echo/echo_srpc.py +4 -1
  19. package/echo/half_close_test.rs +178 -0
  20. package/echo/integration_server.rs +14 -21
  21. package/echo/main.rs +28 -48
  22. package/go.mod +1 -1
  23. package/go.sum +2 -18
  24. package/integration/cross-language/cpp-client.cpp +446 -402
  25. package/integration/cross-language/cpp-pipe-server.cpp +139 -0
  26. package/integration/cross-language/cpp-pipe_test.go +114 -0
  27. package/integration/cross-language/cpp-server.cpp +222 -186
  28. package/mock/mock_srpc.pb.cpp +1 -1
  29. package/mock/mock_srpc.pb.hpp +1 -1
  30. package/mock/mock_srpc.pb.rs +10 -9
  31. package/package.json +1 -1
  32. package/srpc/CMakeLists.txt +3 -58
  33. package/srpc/README-cpp.md +69 -0
  34. package/srpc/accept.go +10 -4
  35. package/srpc/accept_test.go +25 -0
  36. package/srpc/build.rs +129 -34
  37. package/srpc/client-half-close_test.go +152 -0
  38. package/srpc/client-invoker.go +32 -42
  39. package/srpc/client-invoker_test.go +53 -0
  40. package/srpc/client-rpc.cpp +65 -80
  41. package/srpc/client-rpc.go +32 -33
  42. package/srpc/client-rpc.hpp +52 -38
  43. package/srpc/client-rpc.test.ts +67 -0
  44. package/srpc/client-rpc.ts +10 -0
  45. package/srpc/client-rpc_test.go +76 -0
  46. package/srpc/client.cpp +99 -87
  47. package/srpc/client.hpp +46 -35
  48. package/srpc/client.rs +78 -38
  49. package/srpc/client_half_close_test.cc +331 -0
  50. package/srpc/client_invoker.rs +75 -0
  51. package/srpc/common-rpc.cpp +186 -146
  52. package/srpc/common-rpc.go +5 -4
  53. package/srpc/common-rpc.hpp +119 -89
  54. package/srpc/common-rpc.ts +43 -8
  55. package/srpc/common-rpc_test.go +23 -0
  56. package/srpc/error.rs +19 -2
  57. package/srpc/errors.hpp +80 -50
  58. package/srpc/errors.ts +16 -0
  59. package/srpc/handler.hpp +9 -7
  60. package/srpc/handler.rs +7 -45
  61. package/srpc/index.ts +2 -0
  62. package/srpc/invoker.hpp +82 -55
  63. package/srpc/invoker.rs +14 -52
  64. package/srpc/lib.rs +2 -0
  65. package/srpc/message.hpp +17 -12
  66. package/srpc/msg-stream.go +1 -0
  67. package/srpc/msg-stream.hpp +108 -61
  68. package/srpc/mux.cpp +84 -84
  69. package/srpc/mux.hpp +48 -38
  70. package/srpc/mux.rs +4 -4
  71. package/srpc/net.go +12 -3
  72. package/srpc/packet.cpp +60 -58
  73. package/srpc/packet.hpp +35 -20
  74. package/srpc/packet.rs +8 -27
  75. package/srpc/rpc.rs +332 -115
  76. package/srpc/rpc_lifecycle_test.cc +333 -0
  77. package/srpc/server-cancel_test.go +39 -0
  78. package/srpc/server-rpc.cpp +83 -73
  79. package/srpc/server-rpc.go +21 -19
  80. package/srpc/server-rpc.hpp +56 -37
  81. package/srpc/server.rs +76 -131
  82. package/srpc/starpc.hpp +4 -2
  83. package/srpc/stream.go +4 -0
  84. package/srpc/stream.hpp +95 -34
  85. package/srpc/stream.rs +7 -2
  86. package/srpc/testing.rs +11 -66
  87. package/srpc/transport.rs +21 -71
  88. package/srpc/websocket.rs +106 -57
  89. package/srpc/writer.hpp +46 -32
  90. package/srpc/yamux.rs +82 -26
  91. package/srpc/tests/integration_test.rs +0 -495
@@ -1,15 +1,19 @@
1
1
  //go:build deps_only
2
2
 
3
- // Echo end-to-end test for starpc C++ implementation.
4
- // Tests unary and streaming RPC patterns.
3
+ /*
4
+ * Echo end-to-end test for the starpc C++ implementation: unary, streaming,
5
+ * and nested RpcStream calls over an in-memory transport.
6
+ */
5
7
 
6
- #include <atomic>
7
- #include <cassert>
8
+ #include <condition_variable>
8
9
  #include <iostream>
9
10
  #include <memory>
10
11
  #include <mutex>
11
12
  #include <queue>
13
+ #include <string>
12
14
  #include <thread>
15
+ #include <tuple>
16
+ #include <utility>
13
17
 
14
18
  #include "echo/echo_srpc.pb.hpp"
15
19
  #include "rpcstream/rpcstream.hpp"
@@ -18,1031 +22,584 @@
18
22
 
19
23
  namespace {
20
24
 
21
- const char *kTestBody = "hello world via starpc C++ e2e test";
25
+ const char *test_body = "hello world via starpc C++ e2e test";
22
26
 
23
- // InMemoryTransport provides an in-memory packet transport for testing.
24
- // Simulates a bidirectional connection between client and server.
27
+ /*
28
+ * InMemoryTransport is a bidirectional packet connection between a test
29
+ * client and server. Each Endpoint queues packets and wakes its readers on
30
+ * send or close.
31
+ */
25
32
  class InMemoryTransport {
26
33
  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
- }
34
+ struct Endpoint {
35
+ std::mutex mtx;
36
+ std::condition_variable cv;
37
+ std::queue<std::string> packets;
38
+ bool closed = false;
39
+ };
40
+
41
+ InMemoryTransport()
42
+ : client_endpoint(std::make_shared<Endpoint>()),
43
+ server_endpoint(std::make_shared<Endpoint>())
44
+ {
45
+ }
46
+
47
+ /* ClientToServer returns the endpoint the client writes to. */
48
+ std::shared_ptr<Endpoint> ClientToServer()
49
+ {
50
+ return server_endpoint;
51
+ }
52
+ /* ServerToClient returns the endpoint the server writes to. */
53
+ std::shared_ptr<Endpoint> ServerToClient()
54
+ {
55
+ return client_endpoint;
56
+ }
57
+ /* ClientReader returns the endpoint the client reads from. */
58
+ std::shared_ptr<Endpoint> ClientReader()
59
+ {
60
+ return client_endpoint;
61
+ }
62
+ /* ServerReader returns the endpoint the server reads from. */
63
+ std::shared_ptr<Endpoint> ServerReader()
64
+ {
65
+ return server_endpoint;
66
+ }
67
+
68
+ /* Send queues data on ep and wakes its readers. */
69
+ static void Send(std::shared_ptr<Endpoint> ep, const std::string &data)
70
+ {
71
+ std::lock_guard<std::mutex> lock(ep->mtx);
72
+ if (!ep->closed) {
73
+ ep->packets.push(data);
74
+ ep->cv.notify_all();
75
+ }
76
+ }
77
+
78
+ /*
79
+ * Recv waits for one packet and returns false once ep is closed and
80
+ * drained.
81
+ */
82
+ static bool Recv(std::shared_ptr<Endpoint> ep, std::string *out)
83
+ {
84
+ std::unique_lock<std::mutex> lock(ep->mtx);
85
+ ep->cv.wait(lock, [&ep]() {
86
+ return !ep->packets.empty() || ep->closed;
87
+ });
88
+ if (ep->packets.empty())
89
+ return false;
90
+ *out = std::move(ep->packets.front());
91
+ ep->packets.pop();
92
+ return true;
93
+ }
94
+
95
+ /* Close ends the endpoint and wakes its readers. */
96
+ static void Close(std::shared_ptr<Endpoint> ep)
97
+ {
98
+ std::lock_guard<std::mutex> lock(ep->mtx);
99
+ ep->closed = true;
100
+ ep->cv.notify_all();
101
+ }
76
102
 
77
103
  private:
78
- std::shared_ptr<Endpoint> client_endpoint_;
79
- std::shared_ptr<Endpoint> server_endpoint_;
104
+ std::shared_ptr<Endpoint> client_endpoint;
105
+ std::shared_ptr<Endpoint> server_endpoint;
80
106
  };
81
107
 
82
- // InMemoryPacketWriter writes packets to an InMemoryTransport endpoint.
108
+ /* InMemoryPacketWriter writes packets to one transport endpoint. */
83
109
  class InMemoryPacketWriter : public starpc::PacketWriter {
84
110
  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
- }
111
+ explicit InMemoryPacketWriter(
112
+ std::shared_ptr<InMemoryTransport::Endpoint> ep)
113
+ : endpoint(ep)
114
+ {
115
+ }
116
+
117
+ starpc::Error WritePacket(const srpc::Packet &pkt) override
118
+ {
119
+ std::string data;
120
+ if (!pkt.SerializeToString(&data))
121
+ return starpc::Error::InvalidMessage;
122
+ InMemoryTransport::Send(endpoint, data);
123
+ return starpc::Error::OK;
124
+ }
125
+
126
+ starpc::Error Close() override
127
+ {
128
+ InMemoryTransport::Close(endpoint);
129
+ return starpc::Error::OK;
130
+ }
101
131
 
102
132
  private:
103
- std::shared_ptr<InMemoryTransport::Endpoint> endpoint_;
133
+ std::shared_ptr<InMemoryTransport::Endpoint> endpoint;
104
134
  };
105
135
 
106
- // RpcStreamAdapter adapts generated stream classes to implement
107
- // rpcstream::RpcStream
136
+ /*
137
+ * RpcStreamAdapter exposes a generated server stream as an
138
+ * rpcstream::RpcStream. The stream is borrowed and must outlive the adapter.
139
+ */
108
140
  class RpcStreamAdapter : public rpcstream::RpcStream {
109
141
  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; }
142
+ explicit RpcStreamAdapter(echo::SRPCEchoer_RpcStreamStream *strm)
143
+ : strm(strm)
144
+ {
145
+ }
146
+
147
+ starpc::Error Send(const rpcstream::RpcStreamPacket &msg) override
148
+ {
149
+ return strm->Send(msg);
150
+ }
151
+ starpc::Error Recv(rpcstream::RpcStreamPacket *msg) override
152
+ {
153
+ return strm->Recv(msg);
154
+ }
155
+ starpc::Error CloseSend() override
156
+ {
157
+ return starpc::Error::OK;
158
+ }
159
+ starpc::Error Close() override
160
+ {
161
+ return starpc::Error::OK;
162
+ }
121
163
 
122
164
  private:
123
- echo::SRPCEchoer_RpcStreamStream *strm_;
165
+ echo::SRPCEchoer_RpcStreamStream *strm;
124
166
  };
125
167
 
126
- // EchoServerImpl implements the echo server.
168
+ /* EchoServerImpl implements the echo service under test. */
127
169
  class EchoServerImpl : public echo::SRPCEchoerServer {
128
170
  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
- auto adapter = std::make_shared<RpcStreamAdapter>(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
- }
171
+ /* SetRpcStreamMux selects the mux nested RpcStream calls resolve to. */
172
+ void SetRpcStreamMux(starpc::Mux *mux)
173
+ {
174
+ rpc_stream_mux = mux;
175
+ }
176
+
177
+ starpc::Error Echo(const echo::EchoMsg &req,
178
+ echo::EchoMsg *resp) override
179
+ {
180
+ resp->set_body(req.body());
181
+ return starpc::Error::OK;
182
+ }
183
+
184
+ starpc::Error
185
+ EchoServerStream(const echo::EchoMsg &req,
186
+ echo::SRPCEchoer_EchoServerStreamStream *strm) override
187
+ {
188
+ /* Send 5 copies of the message */
189
+ for (int i = 0; i < 5; i++) {
190
+ echo::EchoMsg msg;
191
+ msg.set_body(req.body());
192
+ starpc::Error err = strm->Send(msg);
193
+ if (err != starpc::Error::OK) {
194
+ return err;
195
+ }
196
+ }
197
+ return starpc::Error::OK;
198
+ }
199
+
200
+ starpc::Error
201
+ EchoClientStream(echo::SRPCEchoer_EchoClientStreamStream *strm,
202
+ echo::EchoMsg *resp) override
203
+ {
204
+ /* Receive first message and return it */
205
+ echo::EchoMsg msg;
206
+ starpc::Error err = strm->Recv(&msg);
207
+ if (err != starpc::Error::OK) {
208
+ return err;
209
+ }
210
+ resp->set_body(msg.body());
211
+ return starpc::Error::OK;
212
+ }
213
+
214
+ starpc::Error
215
+ EchoBidiStream(echo::SRPCEchoer_EchoBidiStreamStream *strm) override
216
+ {
217
+ /* Echo back all received messages */
218
+ while (true) {
219
+ echo::EchoMsg msg;
220
+ starpc::Error err = strm->Recv(&msg);
221
+ if (err == starpc::Error::EOF_) {
222
+ break;
223
+ }
224
+ if (err != starpc::Error::OK) {
225
+ return err;
226
+ }
227
+ err = strm->Send(msg);
228
+ if (err != starpc::Error::OK) {
229
+ return err;
230
+ }
231
+ }
232
+ return starpc::Error::OK;
233
+ }
234
+
235
+ starpc::Error RpcStream(echo::SRPCEchoer_RpcStreamStream *strm) override
236
+ {
237
+ auto adapter = std::make_shared<RpcStreamAdapter>(strm);
238
+ return rpcstream::HandleRpcStream(
239
+ adapter, [this](const std::string &component_id) {
240
+ if (!rpc_stream_mux) {
241
+ return std::make_tuple(
242
+ static_cast<starpc::Invoker *>(
243
+ nullptr),
244
+ std::function<void()>(),
245
+ starpc::Error::Unimplemented);
246
+ }
247
+ return std::make_tuple(
248
+ static_cast<starpc::Invoker *>(
249
+ rpc_stream_mux),
250
+ std::function<void()>(),
251
+ starpc::Error::OK);
252
+ });
253
+ }
254
+
255
+ starpc::Error DoNothing(const google::protobuf::Empty &req,
256
+ google::protobuf::Empty *resp) override
257
+ {
258
+ return starpc::Error::OK;
259
+ }
204
260
 
205
261
  private:
206
- starpc::Mux *rpc_stream_mux_ = nullptr;
262
+ /* rpc_stream_mux is borrowed; the caller owns it. */
263
+ starpc::Mux *rpc_stream_mux = nullptr;
207
264
  };
208
265
 
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
- }
266
+ /* RunServer serves packets from the transport until it closes. */
267
+ void RunServer(InMemoryTransport *transport, starpc::Mux *mux)
268
+ {
269
+ auto reader = transport->ServerReader();
270
+ auto writer = std::make_unique<InMemoryPacketWriter>(
271
+ transport->ServerToClient());
272
+ auto server_rpc = starpc::NewServerRPC(mux, writer.get());
273
+
274
+ std::string data;
275
+ while (InMemoryTransport::Recv(reader, &data)) {
276
+ starpc::Error err = server_rpc->HandlePacketData(data);
277
+ if (err != starpc::Error::OK &&
278
+ err != starpc::Error::Completed) {
279
+ std::cerr
280
+ << "Server error: " << starpc::ErrorString(err)
281
+ << std::endl;
282
+ break;
283
+ }
284
+ }
228
285
  }
229
286
 
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;
287
+ /*
288
+ * EchoServer serves one EchoServerImpl on a mux, and on a nested mux that
289
+ * RpcStream calls resolve to. It owns the handlers both muxes borrow.
290
+ */
291
+ struct EchoServer {
292
+ EchoServerImpl impl;
293
+ std::unique_ptr<echo::SRPCEchoerHandler> handler;
294
+ std::unique_ptr<echo::SRPCEchoerHandler> nested_handler;
295
+ std::unique_ptr<starpc::Mux> mux = starpc::NewMux();
296
+ std::unique_ptr<starpc::Mux> nested_mux = starpc::NewMux();
297
+
298
+ /* Register registers impl on both muxes and reports the first error. */
299
+ starpc::Error Register()
300
+ {
301
+ impl.SetRpcStreamMux(nested_mux.get());
302
+ starpc::Error err;
303
+ std::tie(handler, err) =
304
+ echo::SRPCRegisterEchoer(mux.get(), &impl);
305
+ if (err != starpc::Error::OK)
306
+ return err;
307
+ std::tie(nested_handler, err) =
308
+ echo::SRPCRegisterEchoer(nested_mux.get(), &impl);
309
+ return err;
310
+ }
311
+ };
312
+
313
+ /*
314
+ * TestCall serves a mux over an in-memory transport and opens one client call
315
+ * on it. Its receive thread feeds server packets to the call and ends the call
316
+ * with EOF when the transport closes. Destroying the TestCall closes the call
317
+ * and both endpoints and joins both threads, so every exit path releases them.
318
+ * The mux must outlive the TestCall.
319
+ */
320
+ class TestCall {
321
+ public:
322
+ TestCall(starpc::Mux *mux, const std::string &service,
323
+ const std::string &method)
324
+ : client_rpc(starpc::NewClientRPC(service, method)),
325
+ writer(std::make_unique<InMemoryPacketWriter>(
326
+ transport.ClientToServer()))
327
+ {
328
+ server_thread = std::thread(
329
+ [this, mux]() { RunServer(&transport, mux); });
330
+ recv_thread = std::thread([this]() { Receive(); });
331
+ }
332
+
333
+ TestCall(const TestCall &) = delete;
334
+ TestCall &operator=(const TestCall &) = delete;
335
+
336
+ ~TestCall()
337
+ {
338
+ client_rpc->Close();
339
+ InMemoryTransport::Close(transport.ServerReader());
340
+ InMemoryTransport::Close(transport.ClientReader());
341
+ recv_thread.join();
342
+ server_thread.join();
343
+ }
344
+
345
+ /* Start starts the call, sending data first when has_data is set. */
346
+ starpc::Error Start(bool has_data, const std::string &data)
347
+ {
348
+ return client_rpc->Start(writer.get(), has_data, data);
349
+ }
350
+
351
+ /* Rpc returns the client call. */
352
+ starpc::ClientRPC *Rpc()
353
+ {
354
+ return client_rpc.get();
355
+ }
356
+
357
+ private:
358
+ /* Receive feeds packets to the call until the transport closes. */
359
+ void Receive()
360
+ {
361
+ auto reader = transport.ClientReader();
362
+ std::string data;
363
+ while (InMemoryTransport::Recv(reader, &data)) {
364
+ if (client_rpc->HandlePacketData(data) !=
365
+ starpc::Error::OK)
366
+ break;
367
+ }
368
+ client_rpc->HandleStreamClose(starpc::Error::EOF_);
369
+ }
370
+
371
+ InMemoryTransport transport;
372
+ std::unique_ptr<starpc::ClientRPC> client_rpc;
373
+ std::unique_ptr<InMemoryPacketWriter> writer;
374
+ std::thread server_thread;
375
+ std::thread recv_thread;
376
+ };
377
+
378
+ /* Fail reports a failed step with its error and returns false. */
379
+ bool Fail(const std::string &step, starpc::Error err)
380
+ {
381
+ std::cerr << "FAILED: " << step << ": " << starpc::ErrorString(err)
382
+ << std::endl;
383
+ return false;
315
384
  }
316
385
 
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;
386
+ /* Fail reports a failed step and returns false. */
387
+ bool Fail(const std::string &step)
388
+ {
389
+ std::cerr << "FAILED: " << step << std::endl;
390
+ return false;
422
391
  }
423
392
 
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;
393
+ /* EchoRequest returns an encoded EchoMsg carrying test_body. */
394
+ std::string EchoRequest()
395
+ {
396
+ echo::EchoMsg req;
397
+ req.set_body(test_body);
398
+ return req.SerializeAsString();
538
399
  }
539
400
 
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;
401
+ /* ReadEcho reads one EchoMsg from rpc and checks that it carries test_body. */
402
+ bool ReadEcho(starpc::ClientRPC *rpc, const std::string &step)
403
+ {
404
+ std::string data;
405
+ starpc::Error err = rpc->ReadOne(&data);
406
+ if (err != starpc::Error::OK)
407
+ return Fail(step + ": read", err);
408
+
409
+ echo::EchoMsg resp;
410
+ if (!resp.ParseFromString(data))
411
+ return Fail(step + ": parse");
412
+ if (resp.body() != test_body)
413
+ return Fail(step + ": got '" + resp.body() + "'");
414
+ return true;
657
415
  }
658
416
 
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;
417
+ /* TestUnary pins one unary request/reply round trip. */
418
+ bool TestUnary(starpc::Mux *mux)
419
+ {
420
+ TestCall call(mux, "echo.Echoer", "Echo");
421
+ starpc::Error err = call.Start(true, EchoRequest());
422
+ if (err != starpc::Error::OK)
423
+ return Fail("start", err);
424
+ return ReadEcho(call.Rpc(), "reply");
737
425
  }
738
426
 
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(); }
427
+ /* TestServerStream pins five responses to one request. */
428
+ bool TestServerStream(starpc::Mux *mux)
429
+ {
430
+ TestCall call(mux, "echo.Echoer", "EchoServerStream");
431
+ starpc::Error err = call.Start(true, EchoRequest());
432
+ if (err != starpc::Error::OK)
433
+ return Fail("start", err);
434
+
435
+ for (int i = 0; i < 5; i++) {
436
+ if (!ReadEcho(call.Rpc(), "message " + std::to_string(i)))
437
+ return false;
438
+ }
439
+
440
+ std::string extra;
441
+ err = call.Rpc()->ReadOne(&extra);
442
+ if (err != starpc::Error::EOF_)
443
+ return Fail("end of stream", err);
444
+ return true;
445
+ }
753
446
 
754
- private:
755
- echo::SRPCEchoer_RpcStreamClient *client_;
756
- };
447
+ /* TestClientStream pins one reply to a streamed request. */
448
+ bool TestClientStream(starpc::Mux *mux)
449
+ {
450
+ TestCall call(mux, "echo.Echoer", "EchoClientStream");
451
+ starpc::Error err = call.Start(false, "");
452
+ if (err != starpc::Error::OK)
453
+ return Fail("start", err);
454
+
455
+ err = call.Rpc()->WriteCallData(EchoRequest(), false, false,
456
+ starpc::Error::OK);
457
+ if (err != starpc::Error::OK)
458
+ return Fail("send", err);
459
+ err = call.Rpc()->WriteCallData("", false, true, starpc::Error::OK);
460
+ if (err != starpc::Error::OK)
461
+ return Fail("close send", err);
462
+ return ReadEcho(call.Rpc(), "reply");
463
+ }
757
464
 
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
- };
465
+ /* TestBidiStream pins echo replies to each sent message. */
466
+ bool TestBidiStream(starpc::Mux *mux)
467
+ {
468
+ TestCall call(mux, "echo.Echoer", "EchoBidiStream");
469
+ starpc::Error err = call.Start(false, "");
470
+ if (err != starpc::Error::OK)
471
+ return Fail("start", err);
472
+
473
+ for (int i = 0; i < 3; i++) {
474
+ const std::string step = "message " + std::to_string(i);
475
+ err = call.Rpc()->WriteCallData(EchoRequest(), false, false,
476
+ starpc::Error::OK);
477
+ if (err != starpc::Error::OK)
478
+ return Fail(step + ": send", err);
479
+ if (!ReadEcho(call.Rpc(), step))
480
+ return false;
481
+ }
482
+
483
+ err = call.Rpc()->WriteCallData("", false, true, starpc::Error::OK);
484
+ if (err != starpc::Error::OK)
485
+ return Fail("close send", err);
486
+ return true;
487
+ }
772
488
 
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;
489
+ /* TestDoNothing pins an empty request and empty reply. */
490
+ bool TestDoNothing(starpc::Mux *mux)
491
+ {
492
+ TestCall call(mux, "echo.Echoer", "DoNothing");
493
+ starpc::Error err =
494
+ call.Start(true, google::protobuf::Empty().SerializeAsString());
495
+ if (err != starpc::Error::OK)
496
+ return Fail("start", err);
497
+
498
+ std::string data;
499
+ err = call.Rpc()->ReadOne(&data);
500
+ if (err != starpc::Error::OK)
501
+ return Fail("read", err);
502
+ google::protobuf::Empty resp;
503
+ if (!resp.ParseFromString(data))
504
+ return Fail("parse");
505
+ return true;
801
506
  }
802
507
 
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;
508
+ /*
509
+ * TestRpcStream pins a nested call: the client opens an RpcStream, the server
510
+ * answers init, and the nested Echo call returns through the nested mux.
511
+ */
512
+ bool TestRpcStream(starpc::Mux *mux)
513
+ {
514
+ TestCall call(mux, "echo.Echoer", "RpcStream");
515
+ starpc::ClientRPC *rpc = call.Rpc();
516
+ starpc::Error err = call.Start(false, "");
517
+ if (err != starpc::Error::OK)
518
+ return Fail("start", err);
519
+
520
+ rpcstream::RpcStreamPacket init_pkt;
521
+ init_pkt.mutable_init()->set_component_id("");
522
+ err = rpc->WriteCallData(init_pkt.SerializeAsString(), false, false,
523
+ starpc::Error::OK);
524
+ if (err != starpc::Error::OK)
525
+ return Fail("send init", err);
526
+
527
+ std::string ack_data;
528
+ err = rpc->ReadOne(&ack_data);
529
+ if (err != starpc::Error::OK)
530
+ return Fail("read ack", err);
531
+ rpcstream::RpcStreamPacket ack_pkt;
532
+ if (!ack_pkt.ParseFromString(ack_data) || !ack_pkt.has_ack())
533
+ return Fail("invalid ack packet");
534
+ if (!ack_pkt.ack().error().empty())
535
+ return Fail("ack error: " + ack_pkt.ack().error());
536
+
537
+ /* The nested call starts inside an RpcStream data packet. */
538
+ auto call_start = starpc::NewCallStartPacket("echo.Echoer", "Echo",
539
+ EchoRequest(), true);
540
+ rpcstream::RpcStreamPacket data_pkt;
541
+ data_pkt.set_data(call_start->SerializeAsString());
542
+ err = rpc->WriteCallData(data_pkt.SerializeAsString(), false, false,
543
+ starpc::Error::OK);
544
+ if (err != starpc::Error::OK)
545
+ return Fail("send call start", err);
546
+
547
+ /* The reply is an RpcStream packet holding a CallData packet. */
548
+ std::string resp_data;
549
+ err = rpc->ReadOne(&resp_data);
550
+ if (err != starpc::Error::OK)
551
+ return Fail("read reply", err);
552
+ rpcstream::RpcStreamPacket resp_pkt;
553
+ if (!resp_pkt.ParseFromString(resp_data) || !resp_pkt.has_data())
554
+ return Fail("invalid reply packet");
555
+ srpc::Packet inner_pkt;
556
+ if (!inner_pkt.ParseFromString(resp_pkt.data()) ||
557
+ !inner_pkt.has_call_data())
558
+ return Fail("invalid inner packet");
559
+ echo::EchoMsg resp;
560
+ if (!resp.ParseFromString(inner_pkt.call_data().data()))
561
+ return Fail("parse reply");
562
+ if (resp.body() != test_body)
563
+ return Fail("got '" + resp.body() + "'");
564
+ return true;
997
565
  }
998
566
 
999
567
  } // namespace
1000
568
 
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;
569
+ int main()
570
+ {
571
+ std::cout << "=== starpc C++ E2E Tests ===" << std::endl;
572
+
573
+ EchoServer server;
574
+ starpc::Error err = server.Register();
575
+ if (err != starpc::Error::OK) {
576
+ Fail("register", err);
577
+ return 1;
578
+ }
579
+
580
+ const std::pair<const char *, bool (*)(starpc::Mux *)> tests[] = {
581
+ {"Unary", TestUnary},
582
+ {"ServerStream", TestServerStream},
583
+ {"ClientStream", TestClientStream},
584
+ {"BidiStream", TestBidiStream},
585
+ {"DoNothing", TestDoNothing},
586
+ {"RpcStream", TestRpcStream},
587
+ };
588
+
589
+ int passed = 0;
590
+ int failed = 0;
591
+ for (const auto &[name, test] : tests) {
592
+ std::cout << "Testing " << name << " RPC... " << std::flush;
593
+ if (test(server.mux.get())) {
594
+ std::cout << "PASSED" << std::endl;
595
+ passed++;
596
+ } else {
597
+ failed++;
598
+ }
599
+ }
600
+
601
+ std::cout << std::endl;
602
+ std::cout << "Results: " << passed << " passed, " << failed << " failed"
603
+ << std::endl;
604
+ return failed > 0 ? 1 : 0;
1048
605
  }