mock_embedded_io/
lib.rs

1//! Mock implementation of [`embedded-io`] and [`embedded-io-async`] traits.
2//!
3//! This is intended for testing higher level protocols or applications written on top of these
4//! traits.
5//!
6//! The main types of interest are:
7//! - [`Source`] : mock object implementing both blocking and async `Read` traits.
8//! - [`Sink`] : mock object implementing both blocking and async `Write` traits.
9//!
10//! These types can be constructed using the builder-style methods to return a desired sequence of
11//! return values and data. In the case of the `Sink`, the data written to it is stored for later
12//! inspection.
13//!
14//! ## Example
15//! ```rust
16//! # use mock_embedded_io::{Sink, Source, MockError};
17//! use embedded_io::{Read, Write};
18//!
19//! let data_bytes = "hello world!".as_bytes();
20//! let mut buf: [u8; 64] = [0; 64];
21//!
22//! let mut mock_source = Source::new()
23//!                           .data(data_bytes)
24//!                           .error(MockError(embedded_io::ErrorKind::BrokenPipe));
25//!
26//! let res = mock_source.read(&mut buf);
27//! assert!(res.is_ok_and(|n| &buf[0..n] == data_bytes));
28//!
29//! let res = mock_source.read(&mut buf);
30//! assert!(res.is_err_and(|e| e == MockError(embedded_io::ErrorKind::BrokenPipe)));
31//!
32//! let mut mock_sink = Sink::new()
33//!                         .accept_data(12)
34//!                         .error(MockError(embedded_io::ErrorKind::BrokenPipe));
35//!
36//! let res = mock_sink.write(data_bytes);
37//! assert!(res.is_ok_and(|n| n == data_bytes.len()));
38//!
39//! let res = mock_sink.write(data_bytes);
40//! assert!(res.is_err_and(|e| e == MockError(embedded_io::ErrorKind::BrokenPipe)));
41//!
42//! let written = mock_sink.into_inner_data();
43//! assert_eq!(written, data_bytes);
44//! ```
45//!
46//! [`embedded-io`]: https://docs.rs/embedded-io/latest/embedded_io/
47//! [`embedded-io-async`]: https://docs.rs/embedded-io-async/latest/embedded_io_async/
48#![deny(missing_docs)]
49
50use embedded_io::{Error, ErrorKind, ErrorType};
51use std::collections::VecDeque;
52
53/// Error type for the crate. This wraps an [`embedded_io::ErrorKind`].
54#[derive(Debug, Copy, Clone, PartialEq, Eq)]
55pub struct MockError(pub ErrorKind);
56
57impl Error for MockError {
58    fn kind(&self) -> embedded_io_async::ErrorKind {
59        self.0
60    }
61}
62
63/// A value to be yielded by the Source
64#[derive(Debug, Clone)]
65enum ReadItem {
66    /// Yield data to the caller
67    Data(Vec<u8>),
68
69    /// Return an error to the caller
70    Error(MockError),
71
72    /// Return a data length of zero to the caller
73    Closed,
74}
75
76/// A value to be yielded by the Sink
77#[derive(Debug, Clone)]
78enum WriteItem {
79    /// Accept data written by the caller up to the given length
80    AcceptData(usize),
81
82    /// Return an error to the caller
83    Error(MockError),
84
85    /// Close the connection by returning a written length of zero to the caller
86    Closed,
87}
88
89/// An owned handle to a [`Source`] or [`Sink`].
90///
91/// It's common to want an object which owns a type implementing `Read` or `Write`. But for testing
92/// purposes, the object under test can't consume the mock, as we might want to verify some
93/// expectations on it afterwards. To get around this, an `OwnedHandle` can be taken from a [`Source`]
94/// or [`Sink`], which also implements the underlying traits but only contains a
95/// mutable reference to the underlying mock.
96///
97/// Once the object under test is dropped, the mock object itself still exists and can be used to
98/// verify any expectations.
99///
100/// ### Example
101/// ```rust
102/// # use mock_embedded_io::Sink;
103/// use embedded_io::Write;
104///
105/// struct MySerialProtocol<T: embedded_io::Write> {
106///     serial: T,
107/// }
108///
109/// impl<T: embedded_io::Write> MySerialProtocol<T> {
110///     fn hello(&mut self) -> Result<(), T::Error> {
111///         self.serial.write_all("hello".as_bytes())
112///     }
113/// }
114///
115/// let mut mock_sink = Sink::new().accept_data(64);
116///
117/// // MySerialProtocol requires an owned `serial` type here
118/// let mut my_protocol = MySerialProtocol {
119///     serial: mock_sink.owned_handle(),
120/// };
121///
122/// let res = my_protocol.hello();
123/// assert!(res.is_ok());
124///
125/// // Because we constructed `my_protocol` with an `OwnedHandle`, we still have access to the
126/// // original `mock_sink` here to verify that the correct bytes were written.
127/// let written = mock_sink.into_inner_data();
128/// assert_eq!(written, "hello".as_bytes());
129/// ```
130#[derive(Debug)]
131pub struct OwnedHandle<'a, T> {
132    inner: &'a mut T,
133}
134
135/// A mock which can act as a data source.
136///
137/// An instance of the mock can be constructed using the builder-style methods. Each item added by
138/// the builder methods will be returned in-order when data is read from the `Source`.
139///
140/// Items can then be read from it using the [`embedded_io::Read`] or [`embedded_io_async::Read`]
141/// traits.
142///
143/// ### Blocking Example
144/// ```rust
145/// # use mock_embedded_io::{Source, MockError};
146/// use embedded_io::Read;
147///
148/// let data_bytes = "hello world!".as_bytes();
149/// let mut mock_source = Source::new()
150///                           .data(data_bytes)
151///                           .error(MockError(embedded_io::ErrorKind::BrokenPipe));
152///
153/// let mut buf: [u8; 64] = [0; 64];
154/// let res = mock_source.read(&mut buf);
155/// assert!(res.is_ok_and(|n| &buf[0..n] == data_bytes));
156///
157/// let res = mock_source.read(&mut buf);
158/// assert!(res.is_err_and(|e| e == MockError(embedded_io::ErrorKind::BrokenPipe)));
159/// ```
160///
161/// ### Async Example
162/// ```rust
163/// # use mock_embedded_io::{Source, MockError};
164/// # #[tokio::main]
165/// # async fn main() {
166/// use embedded_io_async::Read;
167///
168/// let data_bytes = "hello world!".as_bytes();
169/// let mut mock_source = Source::new()
170///                           .data(data_bytes)
171///                           .error(MockError(embedded_io::ErrorKind::BrokenPipe));
172///
173/// let mut buf: [u8; 64] = [0; 64];
174/// let res = mock_source.read(&mut buf).await;
175/// assert!(res.is_ok_and(|n| &buf[0..n] == data_bytes));
176///
177/// let res = mock_source.read(&mut buf).await;
178/// assert!(res.is_err_and(|e| e == MockError(embedded_io::ErrorKind::BrokenPipe)));
179/// # }
180/// ```
181///
182/// [`embedded_io::Read`]: https://docs.rs/embedded-io/latest/embedded_io/trait.Read.html
183/// [`embedded_io_async::Read`]: https://docs.rs/embedded-io-async/latest/embedded_io_async/trait.Read.html
184#[derive(Debug, Default)]
185pub struct Source {
186    /// A queue of items to return to the caller
187    queue: VecDeque<ReadItem>,
188}
189
190impl Source {
191    /// Create a new empty Source
192    pub fn new() -> Self {
193        Self::default()
194    }
195
196    /// Add data to the source. This can be returned to the caller either in one chunk or
197    /// incrementally - for example if 20 bytes of data are added, the caller could read all 20
198    /// bytes in one call, or read 10 bytes twice before the `Source` will return the following
199    /// item.
200    pub fn data<T: Into<Vec<u8>>>(mut self, data: T) -> Self {
201        self.queue.push_back(ReadItem::Data(data.into()));
202        self
203    }
204
205    /// Add an error value to the `Source`.
206    pub fn error(mut self, e: MockError) -> Self {
207        self.queue.push_back(ReadItem::Error(e));
208        self
209    }
210
211    /// Add a "connection closed" item to the `Source`. When read, this will return `Ok(0)` to the
212    /// caller (which might then result in an error value if they used the [`read_exact`] method
213    /// instead of [`read`]).
214    ///
215    /// [`read`]: https://docs.rs/embedded-io/latest/embedded_io/trait.Read.html#tymethod.read
216    /// [`read_exact`]: https://docs.rs/embedded-io/latest/embedded_io/trait.Read.html#method.read_exact
217    pub fn closed(mut self) -> Self {
218        self.queue.push_back(ReadItem::Closed);
219        self
220    }
221
222    /// Check if all of the provided items were consumed
223    pub fn is_consumed(&self) -> bool {
224        self.queue.is_empty()
225    }
226
227    /// Get an [`OwnedHandle`] containing the `Source`.
228    pub fn owned_handle(&mut self) -> OwnedHandle<Self> {
229        OwnedHandle { inner: self }
230    }
231}
232
233/// A mock which can act as a data sink.
234///
235/// An instance of the mock can be constructed using the builder-style methods. Each item added by
236/// the builder methods will be returned in-order when data is written to the `Sink`.
237///
238/// Data can then be written to it using the [`embedded_io::Write`] or [`embedded_io_async::Write`]
239/// traits.
240///
241/// ### Blocking Example
242/// ```rust
243/// # use mock_embedded_io::{Sink, MockError};
244/// use embedded_io::Write;
245///
246/// let mut mock_sink = Sink::new()
247///                         .accept_data(12)
248///                         .error(MockError(embedded_io::ErrorKind::BrokenPipe));
249///
250/// let data_bytes = "hello world!".as_bytes();
251/// let res = mock_sink.write_all(data_bytes);
252/// assert!(res.is_ok());
253///
254/// let res = mock_sink.write(data_bytes);
255/// assert!(res.is_err_and(|e| e == MockError(embedded_io::ErrorKind::BrokenPipe)));
256/// ```
257///
258/// ### Async Example
259/// ```rust
260/// # use mock_embedded_io::{Sink, MockError};
261/// # #[tokio::main]
262/// # async fn main() {
263/// use embedded_io_async::Write;
264///
265/// let mut mock_sink = Sink::new()
266///                         .accept_data(12)
267///                         .error(MockError(embedded_io::ErrorKind::BrokenPipe));
268///
269/// let data_bytes = "hello world!".as_bytes();
270/// let res = mock_sink.write_all(data_bytes).await;
271/// assert!(res.is_ok());
272///
273/// let res = mock_sink.write(data_bytes).await;
274/// assert!(res.is_err_and(|e| e == MockError(embedded_io::ErrorKind::BrokenPipe)));
275/// # }
276/// ```
277///
278/// [`embedded_io::Write`]: https://docs.rs/embedded-io/latest/embedded_io/trait.Read.html
279/// [`embedded_io_async::Write`]: https://docs.rs/embedded-io-async/latest/embedded_io_async/trait.Read.html
280#[derive(Debug, Default)]
281pub struct Sink {
282    /// A queue of items to return to the caller
283    queue: VecDeque<WriteItem>,
284
285    /// The data that has been received from the writer
286    data: Vec<u8>,
287}
288
289impl Sink {
290    /// Create a new empty Sink.
291    pub fn new() -> Self {
292        Self::default()
293    }
294
295    /// Accept n bytes of data written to the Sink
296    pub fn accept_data(mut self, n: usize) -> Self {
297        self.queue.push_back(WriteItem::AcceptData(n));
298        self
299    }
300
301    /// Add an error value to the `Sink`
302    pub fn error(mut self, e: MockError) -> Self {
303        self.queue.push_back(WriteItem::Error(e));
304        self
305    }
306
307    /// Add a "connection closed" item to the `Sink`. When written, this will return `Ok(0)` to the
308    /// caller (which might then result in an error value if they used the [`write_all`] method
309    /// instead of [`write`]).
310    ///
311    /// [`write`]: https://docs.rs/embedded-io/latest/embedded_io/trait.Write.html#tymethod.write
312    /// [`write_all`]: https://docs.rs/embedded-io/latest/embedded_io/trait.Write.html#method.write_all
313    pub fn closed(mut self) -> Self {
314        self.queue.push_back(WriteItem::Closed);
315        self
316    }
317
318    /// Check if all of the provided items were consumed
319    pub fn is_consumed(&self) -> bool {
320        self.queue.is_empty()
321    }
322
323    /// Get the inner data that has been received from the writer
324    pub fn into_inner_data(self) -> Vec<u8> {
325        self.data
326    }
327
328    /// Get an [`OwnedHandle`] containing the `Sink`
329    pub fn owned_handle(&mut self) -> OwnedHandle<Self> {
330        OwnedHandle { inner: self }
331    }
332}
333
334impl ErrorType for Source {
335    type Error = MockError;
336}
337
338impl ErrorType for Sink {
339    type Error = MockError;
340}
341
342impl embedded_io::Read for Source {
343    fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
344        let next_item = self
345            .queue
346            .pop_front()
347            .expect("The caller tried to read data, but the Source is completely consumed");
348
349        match next_item {
350            ReadItem::Data(data) => {
351                let n = buf.len().min(data.len());
352                let (to_send, to_pend) = data.split_at(n);
353
354                // If we can't send all the data to the caller, put some back in the queue
355                if !to_pend.is_empty() {
356                    self.queue.push_front(ReadItem::Data(Vec::from(to_pend)));
357                }
358
359                buf[0..n].copy_from_slice(to_send);
360                Ok(n)
361            }
362            ReadItem::Error(e) => Err(e),
363            ReadItem::Closed => Ok(0),
364        }
365    }
366}
367
368impl embedded_io_async::Read for Source {
369    async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
370        embedded_io::Read::read(self, buf)
371    }
372}
373
374impl embedded_io::Write for Sink {
375    fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
376        let next_chunk = self
377            .queue
378            .pop_front()
379            .expect("The caller tried to write data, but the Sink is completely consumed");
380
381        match next_chunk {
382            WriteItem::AcceptData(maxsize) => {
383                let n = buf.len().min(maxsize);
384                let remaining = maxsize - n;
385
386                // If the max size wasn't written, push the remaining length back to the queue
387                if remaining > 0 {
388                    self.queue.push_front(WriteItem::AcceptData(remaining));
389                }
390
391                self.data.extend_from_slice(buf);
392                Ok(n)
393            }
394            WriteItem::Error(e) => Err(e),
395            WriteItem::Closed => Ok(0),
396        }
397    }
398
399    fn flush(&mut self) -> Result<(), Self::Error> {
400        Ok(())
401    }
402}
403
404impl embedded_io_async::Write for Sink {
405    async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
406        embedded_io::Write::write(self, buf)
407    }
408}
409
410impl<T: ErrorType> ErrorType for OwnedHandle<'_, T> {
411    type Error = T::Error;
412}
413
414impl<T: embedded_io::Write> embedded_io::Write for OwnedHandle<'_, T> {
415    fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
416        self.inner.write(buf)
417    }
418
419    fn flush(&mut self) -> Result<(), Self::Error> {
420        self.inner.flush()
421    }
422}
423
424impl<T: embedded_io_async::Write> embedded_io_async::Write for OwnedHandle<'_, T> {
425    async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
426        self.inner.write(buf).await
427    }
428}
429
430impl<T: embedded_io::Read> embedded_io::Read for OwnedHandle<'_, T> {
431    fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
432        self.inner.read(buf)
433    }
434}
435
436impl<T: embedded_io_async::Read> embedded_io_async::Read for OwnedHandle<'_, T> {
437    async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
438        self.inner.read(buf).await
439    }
440}