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}