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.
@@ -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
+ }