partial_io/
lib.rs

1// Copyright (c) The partial-io Contributors
2// SPDX-License-Identifier: MIT
3
4#![cfg_attr(doc_cfg, feature(doc_auto_cfg))]
5
6//! Helpers for testing I/O behavior with partial, interrupted and blocking reads and writes.
7//!
8//! This library provides:
9//!
10//! * `PartialRead` and `PartialWrite`, which wrap existing `Read` and
11//!   `Write` implementations and allow specifying arbitrary behavior on the
12//!   next `read`, `write` or `flush` call.
13//! * With the optional `futures03` and `tokio1` features, `PartialAsyncRead` and
14//!   `PartialAsyncWrite` to wrap existing `AsyncRead` and `AsyncWrite`
15//!   implementations. These implementations are task-aware, so they will know
16//!   how to pause and unpause tasks if they return a `WouldBlock` error.
17//! * With the optional `proptest1` ([proptest]) and `quickcheck1` ([quickcheck]) features,
18//!   generation of random sequences of operations for property-based testing. See the
19//!   `proptest_types` and `quickcheck_types` documentation for more.
20//!
21//! # Motivation
22//!
23//! A `Read` or `Write` wrapper is conceptually simple but can be difficult to
24//! get right, especially if the wrapper has an internal buffer. Common
25//! issues include:
26//!
27//! * A partial read or write, even without an error, might leave the wrapper
28//!   in an invalid state ([example fix][1]).
29//!
30//! With the `AsyncRead` and `AsyncWrite` provided by `futures03` and `tokio1`:
31//!
32//! * A call to `read_to_end` or `write_all` within the wrapper might be partly
33//!   successful but then error out. These functions will return the error
34//!   without informing the caller of how much was read or written. Wrappers
35//!   with an internal buffer will want to advance their state corresponding
36//!   to the partial success, so they can't use `read_to_end` or `write_all`
37//!   ([example fix][2]).
38//! * Instances must propagate `Poll::Pending` up, but that shouldn't leave
39//!   them in an invalid state.
40//!
41//! These situations can be hard to think about and hard to test.
42//!
43//! `partial-io` can help in two ways:
44//!
45//! 1. For a known bug involving any of these situations, `partial-io` can help
46//!    you write a test.
47//! 2. With the `quickcheck1` feature enabled, `partial-io` can also help shake
48//!    out bugs in your wrapper. See `quickcheck_types` for more.
49//!
50//! # Examples
51//!
52//! ```rust
53//! use std::io::{self, Cursor, Read};
54//!
55//! use partial_io::{PartialOp, PartialRead};
56//!
57//! let data = b"Hello, world!".to_vec();
58//! let cursor = Cursor::new(data);  // Cursor<Vec<u8>> implements io::Read
59//! let ops = vec![PartialOp::Limited(7), PartialOp::Err(io::ErrorKind::Interrupted)];
60//! let mut partial_read = PartialRead::new(cursor, ops);
61//!
62//! let mut out = vec![0; 256];
63//!
64//! // The first read will read 7 bytes.
65//! assert_eq!(partial_read.read(&mut out).unwrap(), 7);
66//! assert_eq!(&out[..7], b"Hello, ");
67//! // The second read will fail with ErrorKind::Interrupted.
68//! assert_eq!(partial_read.read(&mut out[7..]).unwrap_err().kind(), io::ErrorKind::Interrupted);
69//! // The iterator has run out of operations, so it no longer truncates reads.
70//! assert_eq!(partial_read.read(&mut out[7..]).unwrap(), 6);
71//! assert_eq!(&out[..13], b"Hello, world!");
72//! ```
73//!
74//! For a real-world example, see the [tests in `zstd-rs`].
75//!
76//! [proptest]: https://altsysrq.github.io/proptest-book/intro.html
77//! [quickcheck]: https://docs.rs/quickcheck
78//! [1]: https://github.com/gyscos/zstd-rs/commit/3123e418595f6badd5b06db2a14c4ff4555e7705
79//! [2]: https://github.com/gyscos/zstd-rs/commit/02dc9d9a3419618fc729542b45c96c32b0f178bb
80//! [tests in `zstd-rs`]: https://github.com/gyscos/zstd-rs/blob/master/src/stream/mod.rs
81
82#[cfg(feature = "futures03")]
83mod async_read;
84#[cfg(feature = "futures03")]
85mod async_write;
86#[cfg(feature = "futures03")]
87mod futures_util;
88#[cfg(feature = "proptest1")]
89pub mod proptest_types;
90#[cfg(feature = "quickcheck1")]
91pub mod quickcheck_types;
92mod read;
93mod write;
94
95use std::io;
96
97#[cfg(feature = "tokio1")]
98pub use crate::async_read::tokio_impl::ReadBufExt;
99#[cfg(feature = "futures03")]
100pub use crate::async_read::PartialAsyncRead;
101#[cfg(feature = "futures03")]
102pub use crate::async_write::PartialAsyncWrite;
103pub use crate::{read::PartialRead, write::PartialWrite};
104
105/// What to do the next time an IO operation is performed.
106///
107/// This is not the same as `io::Result<Option<usize>>` because it contains
108/// `io::ErrorKind` instances, not `io::Error` instances. This allows it to be
109/// clonable.
110#[derive(Clone, Debug)]
111pub enum PartialOp {
112    /// Limit the next IO operation to a certain number of bytes.
113    ///
114    /// The wrapper will call into the inner `Read` or `Write`
115    /// instance. Depending on what the underlying operation does, this may
116    /// return an error or a fewer number of bytes.
117    ///
118    /// Some methods like `Write::flush` and `AsyncWrite::poll_flush` don't
119    /// have a limit. For these methods, `Limited(n)` behaves the same as
120    /// `Unlimited`.
121    Limited(usize),
122
123    /// Do not limit the next IO operation.
124    ///
125    /// The wrapper will call into the inner `Read` or `Write`
126    /// instance. Depending on what the underlying operation does, this may
127    /// return an error or a limited number of bytes.
128    Unlimited,
129
130    /// Return an error instead of calling into the underlying operation.
131    ///
132    /// For methods on `Async` traits:
133    /// * `ErrorKind::WouldBlock` is translated to `Poll::Pending` and the task
134    ///   is scheduled to be woken up in the future.
135    /// * `ErrorKind::Interrupted` causes a retry.
136    Err(io::ErrorKind),
137}
138
139#[inline]
140fn make_ops<I>(iter: I) -> Box<dyn Iterator<Item = PartialOp> + Send>
141where
142    I: IntoIterator<Item = PartialOp> + 'static,
143    I::IntoIter: Send,
144{
145    Box::new(iter.into_iter().fuse())
146}
147
148#[cfg(test)]
149mod tests {
150    pub fn assert_send<S: Send>() {}
151}