Skip to main content

bytesbuf_io/testing/
null.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4use std::convert::Infallible;
5
6use bytesbuf::mem::testing::TransparentMemory;
7use bytesbuf::mem::{HasMemory, Memory, MemoryShared, OpaqueMemory};
8use bytesbuf::{BytesBuf, BytesView};
9
10use crate::{Read, Write};
11
12/// A [`Read`] and [`Write`] that does nothing.
13///
14/// Any data written to it is discarded and it never returns any data when read from.
15/// Intended for simple tests and examples only.
16#[derive(Debug)]
17pub struct Null {
18    memory: OpaqueMemory,
19}
20
21impl Null {
22    /// Starts building a new `NullStream`.
23    #[must_use]
24    pub fn builder() -> NullBuilder {
25        NullBuilder {
26            memory: OpaqueMemory::new(TransparentMemory::new()),
27        }
28    }
29
30    /// Creates a new `NullStream` with the default configuration.
31    #[must_use]
32    pub fn new() -> Self {
33        Self::builder().build()
34    }
35
36    /// Reads 0 bytes into the provided buffer, returning it as-is.
37    ///
38    /// # Errors
39    ///
40    /// This call never fails.
41    #[cfg_attr(test, mutants::skip)] // This does nothing, pointless to mutate.
42    #[expect(
43        clippy::needless_pass_by_ref_mut,
44        clippy::unused_async,
45        reason = "API compatibility between trait and inherent fn"
46    )]
47    pub async fn read_at_most_into(&mut self, _len: usize, into: BytesBuf) -> Result<(usize, BytesBuf), Infallible> {
48        Ok((0, into))
49    }
50
51    /// Reads 0 bytes into the provided buffer, returning it as-is.
52    ///
53    /// # Errors
54    ///
55    /// This call never fails.
56    #[cfg_attr(test, mutants::skip)] // This does nothing, pointless to mutate.
57    #[expect(
58        clippy::needless_pass_by_ref_mut,
59        clippy::unused_async,
60        reason = "API compatibility between trait and inherent fn"
61    )]
62    pub async fn read_more_into(&mut self, into: BytesBuf) -> Result<(usize, BytesBuf), Infallible> {
63        Ok((0, into))
64    }
65
66    /// Reads 0 bytes.
67    ///
68    /// # Errors
69    ///
70    /// This call never fails.
71    #[cfg_attr(test, mutants::skip)] // This does nothing, pointless to mutate.
72    #[expect(
73        clippy::needless_pass_by_ref_mut,
74        clippy::unused_async,
75        reason = "API compatibility between trait and inherent fn"
76    )]
77    pub async fn read_any(&mut self) -> Result<BytesBuf, Infallible> {
78        Ok(BytesBuf::default())
79    }
80
81    /// "Writes" the provided data, discarding it.
82    ///
83    /// # Errors
84    ///
85    /// This call never fails.
86    #[cfg_attr(test, mutants::skip)] // This does nothing, pointless to mutate.
87    #[expect(
88        clippy::needless_pass_by_ref_mut,
89        clippy::unused_async,
90        reason = "API compatibility between trait and inherent fn"
91    )]
92    pub async fn write(&mut self, _sequence: BytesView) -> Result<(), Infallible> {
93        Ok(())
94    }
95
96    /// Returns the memory provider that was configured in the builder.
97    #[must_use]
98    pub fn memory(&self) -> impl MemoryShared {
99        self.memory.clone()
100    }
101
102    /// Reserves at least `min_bytes` bytes of memory capacity.
103    ///
104    /// Returns an empty [`BytesBuf`] that can be used to fill the reserved memory with data.
105    ///
106    /// The memory provider may provide more memory than requested.
107    ///
108    /// The memory reservation request will always be fulfilled, obtaining more memory from the
109    /// operating system if necessary.
110    ///
111    /// # Zero-sized reservations
112    ///
113    /// Reserving zero bytes of memory is a valid operation and will return a [`BytesBuf`]
114    /// with zero or more bytes of capacity.
115    ///
116    /// # Panics
117    ///
118    /// May panic if the operating system runs out of memory.
119    #[must_use]
120    pub fn reserve(&self, min_bytes: usize) -> BytesBuf {
121        self.memory.reserve(min_bytes)
122    }
123}
124
125impl Default for Null {
126    fn default() -> Self {
127        Self::new()
128    }
129}
130
131#[cfg_attr(coverage_nightly, coverage(off))] // Trivial forwarder.
132impl Read for Null {
133    type Error = Infallible;
134
135    #[cfg_attr(test, mutants::skip)] // Trivial forwarder.
136    async fn read_at_most_into(&mut self, len: usize, into: BytesBuf) -> Result<(usize, BytesBuf), Infallible> {
137        self.read_at_most_into(len, into).await
138    }
139
140    #[cfg_attr(test, mutants::skip)] // Trivial forwarder.
141    async fn read_more_into(&mut self, into: BytesBuf) -> Result<(usize, BytesBuf), Infallible> {
142        self.read_more_into(into).await
143    }
144
145    #[cfg_attr(test, mutants::skip)] // Trivial forwarder.
146    async fn read_any(&mut self) -> Result<BytesBuf, Infallible> {
147        self.read_any().await
148    }
149}
150
151#[cfg_attr(coverage_nightly, coverage(off))] // Trivial forwarder.
152impl Write for Null {
153    type Error = Infallible;
154
155    #[cfg_attr(test, mutants::skip)] // Trivial forwarder.
156    async fn write(&mut self, _sequence: BytesView) -> Result<(), Infallible> {
157        Ok(())
158    }
159}
160
161#[cfg_attr(coverage_nightly, coverage(off))] // Trivial forwarder.
162impl HasMemory for Null {
163    #[cfg_attr(test, mutants::skip)] // Trivial forwarder.
164    fn memory(&self) -> impl MemoryShared {
165        self.memory()
166    }
167}
168
169#[cfg_attr(coverage_nightly, coverage(off))] // Trivial forwarder.
170impl Memory for Null {
171    #[cfg_attr(test, mutants::skip)] // Trivial forwarder.
172    fn reserve(&self, min_bytes: usize) -> BytesBuf {
173        self.reserve(min_bytes)
174    }
175}
176
177/// Creates an instance of [`Null`].
178///
179/// Access through [`Null::builder()`][Null::builder].
180#[derive(Debug)]
181pub struct NullBuilder {
182    memory: OpaqueMemory,
183}
184
185impl NullBuilder {
186    /// The memory provider to use in memory-related stream operations.
187    ///
188    /// The null stream never reserves memory, so the only purpose of this is to allow the user
189    /// of the null stream to call `memory()` and `reserve()` via the `HasMemory` and `Memory`
190    /// traits that every stream implements.
191    ///
192    /// Optional. Defaults to using the Rust global allocator.
193    #[must_use]
194    pub fn memory(mut self, memory: OpaqueMemory) -> Self {
195        self.memory = memory;
196        self
197    }
198
199    /// Builds the `NullStream` with the provided configuration.
200    #[must_use]
201    pub fn build(self) -> Null {
202        Null { memory: self.memory }
203    }
204}
205
206#[cfg(test)]
207#[cfg_attr(coverage_nightly, coverage(off))]
208mod tests {
209    use std::sync::Arc;
210    use std::sync::atomic::{AtomicBool, Ordering};
211
212    use bytesbuf::mem::CallbackMemory;
213    use testing_aids::execute_or_terminate_process;
214
215    use super::*;
216
217    #[test]
218    fn smoke_test() {
219        execute_or_terminate_process(|| {
220            futures::executor::block_on(async {
221                let mut s = Null::new();
222
223                let buffer = s.reserve(1000);
224                assert!(buffer.remaining_capacity() >= 1000);
225
226                let (bytes_read, buffer) = s.read_at_most_into(100, buffer).await.unwrap();
227                assert_eq!(bytes_read, 0);
228                assert_eq!(buffer.len(), 0);
229
230                let (bytes_read, buffer) = s.read_more_into(buffer).await.unwrap();
231                assert_eq!(bytes_read, 0);
232                assert_eq!(buffer.len(), 0);
233
234                let mut buffer = s.read_any().await.unwrap();
235                assert_eq!(buffer.len(), 0);
236
237                s.write(buffer.consume_all()).await.unwrap();
238            });
239        });
240    }
241
242    #[test]
243    fn default_returns_working_instance() {
244        execute_or_terminate_process(|| {
245            futures::executor::block_on(async {
246                let mut s = Null::default();
247
248                // Verify it can reserve memory
249                let buffer = s.reserve(100);
250                assert!(buffer.remaining_capacity() >= 100);
251
252                // Verify read operations work
253                let (bytes_read, _) = s.read_at_most_into(10, BytesBuf::new()).await.unwrap();
254                assert_eq!(bytes_read, 0);
255
256                // Verify write operations work
257                let empty_view = BytesView::default();
258                s.write(empty_view).await.unwrap();
259            });
260        });
261    }
262
263    #[test]
264    fn memory_returns_configured_provider() {
265        let callback_called = Arc::new(AtomicBool::new(false));
266
267        let custom_memory = OpaqueMemory::new(CallbackMemory::new({
268            let callback_called = Arc::clone(&callback_called);
269            move |min_bytes| {
270                callback_called.store(true, Ordering::SeqCst);
271                TransparentMemory::new().reserve(min_bytes)
272            }
273        }));
274
275        let null_stream = Null::builder().memory(custom_memory).build();
276
277        // Get memory from stream and use it
278        let stream_memory = null_stream.memory();
279        let _buf = stream_memory.reserve(10);
280
281        assert!(
282            callback_called.load(Ordering::SeqCst),
283            "Custom memory callback should have been called"
284        );
285    }
286}