starpc 0.41.2 → 0.43.1
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.
- package/README.md +101 -20
- package/dist/mock/mock.pb.d.ts +1 -1
- package/dist/mock/mock.pb.js +4 -5
- package/dist/mock/mock_srpc.pb.d.ts +3 -3
- package/dist/mock/mock_srpc.pb.js +5 -5
- package/echo/Cargo.toml +21 -0
- package/echo/build.rs +15 -0
- package/echo/echo.pb.cc +405 -0
- package/echo/echo.pb.go +9 -27
- package/echo/echo.pb.h +364 -0
- package/echo/echo_srpc.pb.go +1 -1
- package/echo/gen/mod.rs +3 -0
- package/echo/main.rs +162 -0
- package/go.mod +18 -10
- package/go.sum +28 -18
- package/mock/mock.pb.cc +394 -0
- package/mock/mock.pb.go +9 -27
- package/mock/mock.pb.h +366 -0
- package/mock/mock.pb.ts +11 -13
- package/mock/mock_srpc.pb.go +1 -1
- package/mock/mock_srpc.pb.ts +12 -9
- package/package.json +27 -25
- package/srpc/Cargo.toml +26 -0
- package/srpc/build.rs +15 -0
- package/srpc/client.rs +356 -0
- package/srpc/codec.rs +225 -0
- package/srpc/error.rs +177 -0
- package/srpc/handler.rs +163 -0
- package/srpc/invoker.rs +192 -0
- package/srpc/lib.rs +107 -0
- package/srpc/message.rs +9 -0
- package/srpc/mux.rs +353 -0
- package/srpc/packet.rs +334 -0
- package/srpc/proto/mod.rs +10 -0
- package/srpc/rpc.rs +777 -0
- package/srpc/rpcproto.pb.cc +1381 -0
- package/srpc/rpcproto.pb.go +75 -183
- package/srpc/rpcproto.pb.h +1451 -0
- package/srpc/server.rs +337 -0
- package/srpc/stream.rs +304 -0
- package/srpc/testing.rs +290 -0
- package/srpc/tests/integration_test.rs +495 -0
- package/srpc/transport.rs +218 -0
- package/Makefile +0 -154
package/srpc/testing.rs
ADDED
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
//! Testing utilities for starpc.
|
|
2
|
+
//!
|
|
3
|
+
//! This module provides in-memory transports and helpers for testing
|
|
4
|
+
//! starpc services without actual network connections.
|
|
5
|
+
|
|
6
|
+
use async_trait::async_trait;
|
|
7
|
+
use std::sync::Arc;
|
|
8
|
+
use tokio::io::{duplex, DuplexStream};
|
|
9
|
+
use tokio::sync::Mutex;
|
|
10
|
+
|
|
11
|
+
use crate::client::{OpenStream, PacketReceiver};
|
|
12
|
+
use crate::error::{Error, Result};
|
|
13
|
+
use crate::rpc::PacketWriter;
|
|
14
|
+
use crate::transport::create_packet_channel;
|
|
15
|
+
|
|
16
|
+
/// Creates a pair of connected in-memory streams.
|
|
17
|
+
///
|
|
18
|
+
/// Returns (client_stream, server_stream) that can be used for testing
|
|
19
|
+
/// client-server communication.
|
|
20
|
+
///
|
|
21
|
+
/// # Arguments
|
|
22
|
+
/// * `buffer_size` - Size of the internal buffer for each direction
|
|
23
|
+
///
|
|
24
|
+
/// # Example
|
|
25
|
+
///
|
|
26
|
+
/// ```ignore
|
|
27
|
+
/// let (client_stream, server_stream) = create_pipe(64 * 1024);
|
|
28
|
+
///
|
|
29
|
+
/// // Use client_stream with a client
|
|
30
|
+
/// // Use server_stream with a server
|
|
31
|
+
/// ```
|
|
32
|
+
pub fn create_pipe(buffer_size: usize) -> (DuplexStream, DuplexStream) {
|
|
33
|
+
duplex(buffer_size)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/// Creates a pair of connected in-memory streams with default buffer size.
|
|
37
|
+
pub fn create_pipe_default() -> (DuplexStream, DuplexStream) {
|
|
38
|
+
create_pipe(64 * 1024)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/// In-memory stream opener for testing.
|
|
42
|
+
///
|
|
43
|
+
/// This opener can hold multiple streams and returns them one at a time
|
|
44
|
+
/// on each call to `open_stream`.
|
|
45
|
+
///
|
|
46
|
+
/// # Example
|
|
47
|
+
///
|
|
48
|
+
/// ```ignore
|
|
49
|
+
/// let (client_stream, server_stream) = create_pipe_default();
|
|
50
|
+
///
|
|
51
|
+
/// let opener = InMemoryOpener::new(vec![client_stream]);
|
|
52
|
+
/// let client = SrpcClient::new(opener);
|
|
53
|
+
///
|
|
54
|
+
/// // The client will use client_stream for its RPC
|
|
55
|
+
/// ```
|
|
56
|
+
pub struct InMemoryOpener {
|
|
57
|
+
streams: Arc<Mutex<Vec<DuplexStream>>>,
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
impl InMemoryOpener {
|
|
61
|
+
/// Creates a new in-memory opener with the given streams.
|
|
62
|
+
///
|
|
63
|
+
/// Streams are consumed in LIFO order (last added is used first).
|
|
64
|
+
pub fn new(streams: Vec<DuplexStream>) -> Self {
|
|
65
|
+
Self {
|
|
66
|
+
streams: Arc::new(Mutex::new(streams)),
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/// Creates a new in-memory opener with a single stream.
|
|
71
|
+
pub fn single(stream: DuplexStream) -> Self {
|
|
72
|
+
Self::new(vec![stream])
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/// Adds a stream to the opener.
|
|
76
|
+
pub async fn add_stream(&self, stream: DuplexStream) {
|
|
77
|
+
self.streams.lock().await.push(stream);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
#[async_trait]
|
|
82
|
+
impl OpenStream for InMemoryOpener {
|
|
83
|
+
async fn open_stream(&self) -> Result<(Arc<dyn PacketWriter>, PacketReceiver)> {
|
|
84
|
+
let mut streams = self.streams.lock().await;
|
|
85
|
+
let stream = streams.pop().ok_or(Error::StreamClosed)?;
|
|
86
|
+
|
|
87
|
+
let (read_half, write_half) = tokio::io::split(stream);
|
|
88
|
+
Ok(create_packet_channel(read_half, write_half))
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/// Single-use in-memory opener.
|
|
93
|
+
///
|
|
94
|
+
/// A simpler version of `InMemoryOpener` that holds exactly one stream.
|
|
95
|
+
pub struct SingleInMemoryOpener {
|
|
96
|
+
stream: Mutex<Option<DuplexStream>>,
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
impl SingleInMemoryOpener {
|
|
100
|
+
/// Creates a new single in-memory opener.
|
|
101
|
+
pub fn new(stream: DuplexStream) -> Self {
|
|
102
|
+
Self {
|
|
103
|
+
stream: Mutex::new(Some(stream)),
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
#[async_trait]
|
|
109
|
+
impl OpenStream for SingleInMemoryOpener {
|
|
110
|
+
async fn open_stream(&self) -> Result<(Arc<dyn PacketWriter>, PacketReceiver)> {
|
|
111
|
+
let stream = self
|
|
112
|
+
.stream
|
|
113
|
+
.lock()
|
|
114
|
+
.await
|
|
115
|
+
.take()
|
|
116
|
+
.ok_or(Error::StreamClosed)?;
|
|
117
|
+
|
|
118
|
+
let (read_half, write_half) = tokio::io::split(stream);
|
|
119
|
+
Ok(create_packet_channel(read_half, write_half))
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/// Creates a connected client-server test setup.
|
|
124
|
+
///
|
|
125
|
+
/// Returns (client_opener, server_stream) where:
|
|
126
|
+
/// - `client_opener` can be used to create a `SrpcClient`
|
|
127
|
+
/// - `server_stream` can be passed to `Server::handle_stream`
|
|
128
|
+
///
|
|
129
|
+
/// # Example
|
|
130
|
+
///
|
|
131
|
+
/// ```ignore
|
|
132
|
+
/// let (opener, server_stream) = create_test_pair();
|
|
133
|
+
///
|
|
134
|
+
/// let client = SrpcClient::new(opener);
|
|
135
|
+
/// let server = Server::new(mux);
|
|
136
|
+
///
|
|
137
|
+
/// // Spawn server
|
|
138
|
+
/// tokio::spawn(async move {
|
|
139
|
+
/// server.handle_stream(server_stream).await
|
|
140
|
+
/// });
|
|
141
|
+
///
|
|
142
|
+
/// // Use client
|
|
143
|
+
/// let response = client.exec_call("svc", "method", &request).await?;
|
|
144
|
+
/// ```
|
|
145
|
+
pub fn create_test_pair() -> (SingleInMemoryOpener, DuplexStream) {
|
|
146
|
+
let (client_stream, server_stream) = create_pipe_default();
|
|
147
|
+
(SingleInMemoryOpener::new(client_stream), server_stream)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
#[cfg(test)]
|
|
151
|
+
mod tests {
|
|
152
|
+
use super::*;
|
|
153
|
+
use crate::handler::Handler;
|
|
154
|
+
use crate::invoker::Invoker;
|
|
155
|
+
use crate::mux::Mux;
|
|
156
|
+
use crate::server::Server;
|
|
157
|
+
use crate::stream::Stream;
|
|
158
|
+
use crate::{Client, SrpcClient};
|
|
159
|
+
|
|
160
|
+
struct EchoHandler;
|
|
161
|
+
|
|
162
|
+
#[async_trait]
|
|
163
|
+
impl Invoker for EchoHandler {
|
|
164
|
+
async fn invoke_method(
|
|
165
|
+
&self,
|
|
166
|
+
_service_id: &str,
|
|
167
|
+
method_id: &str,
|
|
168
|
+
stream: Box<dyn Stream>,
|
|
169
|
+
) -> (bool, Result<()>) {
|
|
170
|
+
if method_id != "Echo" {
|
|
171
|
+
return (false, Err(Error::Unimplemented));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Read request.
|
|
175
|
+
let request = match stream.recv_bytes().await {
|
|
176
|
+
Ok(b) => b,
|
|
177
|
+
Err(e) => return (true, Err(e)),
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
// Echo it back.
|
|
181
|
+
if let Err(e) = stream.send_bytes(request).await {
|
|
182
|
+
return (true, Err(e));
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
(true, Ok(()))
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
impl Handler for EchoHandler {
|
|
190
|
+
fn service_id(&self) -> &'static str {
|
|
191
|
+
"test.Echo"
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
fn method_ids(&self) -> &'static [&'static str] {
|
|
195
|
+
&["Echo"]
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
#[tokio::test]
|
|
200
|
+
async fn test_in_memory_echo() {
|
|
201
|
+
// Create a pipe.
|
|
202
|
+
let (client_stream, server_stream) = create_pipe_default();
|
|
203
|
+
|
|
204
|
+
// Set up the server.
|
|
205
|
+
let mux = Arc::new(Mux::new());
|
|
206
|
+
mux.register(Arc::new(EchoHandler)).unwrap();
|
|
207
|
+
let server = Server::with_arc(mux);
|
|
208
|
+
|
|
209
|
+
// Spawn server handler.
|
|
210
|
+
let server_handle = tokio::spawn(async move {
|
|
211
|
+
let _ = server.handle_stream(server_stream).await;
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
// Create client.
|
|
215
|
+
let opener = SingleInMemoryOpener::new(client_stream);
|
|
216
|
+
let client = SrpcClient::new(opener);
|
|
217
|
+
|
|
218
|
+
// Make the call.
|
|
219
|
+
let stream = client
|
|
220
|
+
.new_stream("test.Echo", "Echo", Some(b"hello"))
|
|
221
|
+
.await
|
|
222
|
+
.unwrap();
|
|
223
|
+
|
|
224
|
+
// Close send side to indicate we're done sending.
|
|
225
|
+
stream.close_send().await.unwrap();
|
|
226
|
+
|
|
227
|
+
// Read response with a timeout to handle potential races.
|
|
228
|
+
let response = tokio::time::timeout(
|
|
229
|
+
std::time::Duration::from_secs(5),
|
|
230
|
+
stream.recv_bytes(),
|
|
231
|
+
)
|
|
232
|
+
.await
|
|
233
|
+
.expect("timeout")
|
|
234
|
+
.expect("recv_bytes failed");
|
|
235
|
+
assert_eq!(&response[..], b"hello");
|
|
236
|
+
|
|
237
|
+
// Wait for server to complete.
|
|
238
|
+
let _ = tokio::time::timeout(std::time::Duration::from_secs(1), server_handle).await;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
#[tokio::test]
|
|
242
|
+
async fn test_create_test_pair() {
|
|
243
|
+
let (opener, server_stream) = create_test_pair();
|
|
244
|
+
|
|
245
|
+
// Set up the server.
|
|
246
|
+
let mux = Arc::new(Mux::new());
|
|
247
|
+
mux.register(Arc::new(EchoHandler)).unwrap();
|
|
248
|
+
let server = Server::with_arc(mux);
|
|
249
|
+
|
|
250
|
+
// Spawn server handler.
|
|
251
|
+
let server_handle = tokio::spawn(async move {
|
|
252
|
+
let _ = server.handle_stream(server_stream).await;
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
// Create client.
|
|
256
|
+
let client = SrpcClient::new(opener);
|
|
257
|
+
|
|
258
|
+
// Make the call using raw bytes.
|
|
259
|
+
let stream = client
|
|
260
|
+
.new_stream("test.Echo", "Echo", Some(b"test data"))
|
|
261
|
+
.await
|
|
262
|
+
.unwrap();
|
|
263
|
+
|
|
264
|
+
stream.close_send().await.unwrap();
|
|
265
|
+
|
|
266
|
+
let response = stream.recv_bytes().await.unwrap();
|
|
267
|
+
assert_eq!(&response[..], b"test data");
|
|
268
|
+
|
|
269
|
+
server_handle.abort();
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
#[tokio::test]
|
|
273
|
+
async fn test_multi_stream_opener() {
|
|
274
|
+
let (stream1, _) = create_pipe_default();
|
|
275
|
+
let (stream2, _) = create_pipe_default();
|
|
276
|
+
|
|
277
|
+
let opener = InMemoryOpener::new(vec![stream1, stream2]);
|
|
278
|
+
|
|
279
|
+
// Should succeed twice (LIFO order)
|
|
280
|
+
let result1 = opener.open_stream().await;
|
|
281
|
+
assert!(result1.is_ok());
|
|
282
|
+
|
|
283
|
+
let result2 = opener.open_stream().await;
|
|
284
|
+
assert!(result2.is_ok());
|
|
285
|
+
|
|
286
|
+
// Third should fail
|
|
287
|
+
let result3 = opener.open_stream().await;
|
|
288
|
+
assert!(matches!(result3, Err(Error::StreamClosed)));
|
|
289
|
+
}
|
|
290
|
+
}
|