1use std::io::{self, Read, Write};
13use std::sync::atomic::{AtomicUsize, Ordering};
14
15use crate::FailureSchedule;
16
17pub struct ChaosReader<R: Read> {
39 inner: R,
40 schedule: FailureSchedule,
41 attempt: AtomicUsize,
42}
43
44impl<R: Read> ChaosReader<R> {
45 pub fn new(inner: R, schedule: FailureSchedule) -> Self {
47 Self {
48 inner,
49 schedule,
50 attempt: AtomicUsize::new(0),
51 }
52 }
53
54 pub fn attempt_count(&self) -> usize {
56 self.attempt.load(Ordering::Relaxed)
57 }
58
59 pub fn into_inner(self) -> R {
61 self.inner
62 }
63}
64
65impl<R: Read> Read for ChaosReader<R> {
66 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
67 let n = self.attempt.fetch_add(1, Ordering::Relaxed) + 1;
68 if let Err(f) = self.schedule.maybe_fail(n) {
69 return Err(f.into());
70 }
71 self.inner.read(buf)
72 }
73}
74
75pub struct ChaosWriter<W: Write> {
100 inner: W,
101 schedule: FailureSchedule,
102 attempt: AtomicUsize,
103}
104
105impl<W: Write> ChaosWriter<W> {
106 pub fn new(inner: W, schedule: FailureSchedule) -> Self {
108 Self {
109 inner,
110 schedule,
111 attempt: AtomicUsize::new(0),
112 }
113 }
114
115 pub fn attempt_count(&self) -> usize {
117 self.attempt.load(Ordering::Relaxed)
118 }
119
120 pub fn into_inner(self) -> W {
122 self.inner
123 }
124}
125
126impl<W: Write> Write for ChaosWriter<W> {
127 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
128 let n = self.attempt.fetch_add(1, Ordering::Relaxed) + 1;
129 if let Err(f) = self.schedule.maybe_fail(n) {
130 if matches!(f.mode, crate::FailureMode::PartialWrite) && !buf.is_empty() {
133 let _ = self.inner.write(&buf[..1])?;
134 }
135 return Err(f.into());
136 }
137 self.inner.write(buf)
138 }
139
140 fn flush(&mut self) -> io::Result<()> {
141 self.inner.flush()
142 }
143}
144
145pub type ChaosFile = ChaosWriter<std::fs::File>;
160
161impl ChaosFile {
162 pub fn create(
164 path: impl AsRef<std::path::Path>,
165 schedule: FailureSchedule,
166 ) -> io::Result<Self> {
167 let f = std::fs::File::create(path)?;
168 Ok(Self::new(f, schedule))
169 }
170
171 pub fn append(
173 path: impl AsRef<std::path::Path>,
174 schedule: FailureSchedule,
175 ) -> io::Result<Self> {
176 let f = std::fs::OpenOptions::new()
177 .create(true)
178 .append(true)
179 .open(path)?;
180 Ok(Self::new(f, schedule))
181 }
182}
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187 use crate::FailureMode;
188
189 #[test]
190 fn reader_passes_through_when_schedule_does_not_fire() {
191 let data: &[u8] = b"hello";
192 let schedule = FailureSchedule::on_attempts(&[10], FailureMode::IoError);
193 let mut r = ChaosReader::new(data, schedule);
194 let mut buf = [0u8; 5];
195 let n = r.read(&mut buf).unwrap();
196 assert_eq!(n, 5);
197 assert_eq!(&buf, b"hello");
198 }
199
200 #[test]
201 fn reader_fails_when_schedule_fires() {
202 let data: &[u8] = b"hello";
203 let schedule = FailureSchedule::on_attempts(&[1], FailureMode::Timeout);
204 let mut r = ChaosReader::new(data, schedule);
205 let mut buf = [0u8; 5];
206 let err = r.read(&mut buf).unwrap_err();
207 assert_eq!(err.kind(), io::ErrorKind::TimedOut);
208 }
209
210 #[test]
211 fn reader_attempt_count_increments() {
212 let data: &[u8] = b"abc";
213 let schedule = FailureSchedule::on_attempts(&[], FailureMode::IoError);
214 let mut r = ChaosReader::new(data, schedule);
215 let mut buf = [0u8; 1];
216 for _ in 0..3 {
217 let _ = r.read(&mut buf);
218 }
219 assert_eq!(r.attempt_count(), 3);
220 }
221
222 #[test]
223 fn writer_passes_through_bytes() {
224 let sink: Vec<u8> = Vec::new();
225 let schedule = FailureSchedule::on_attempts(&[], FailureMode::IoError);
226 let mut w = ChaosWriter::new(sink, schedule);
227 w.write_all(b"hello").unwrap();
228 let sink = w.into_inner();
229 assert_eq!(sink, b"hello");
230 }
231
232 #[test]
233 fn writer_fails_on_scheduled_attempt() {
234 let sink: Vec<u8> = Vec::new();
235 let schedule = FailureSchedule::on_attempts(&[2], FailureMode::ConnectionReset);
236 let mut w = ChaosWriter::new(sink, schedule);
237 w.write_all(b"a").unwrap();
238 let err = w.write_all(b"b").unwrap_err();
239 assert_eq!(err.kind(), io::ErrorKind::ConnectionReset);
240 let sink = w.into_inner();
241 assert_eq!(sink, b"a");
242 }
243
244 #[test]
245 fn writer_partial_write_emits_one_byte_then_error() {
246 let sink: Vec<u8> = Vec::new();
247 let schedule = FailureSchedule::on_attempts(&[1], FailureMode::PartialWrite);
248 let mut w = ChaosWriter::new(sink, schedule);
249 let err = w.write(b"abcd").unwrap_err();
250 assert_eq!(err.kind(), io::ErrorKind::WriteZero);
251 let sink = w.into_inner();
252 assert_eq!(sink, b"a");
253 }
254
255 #[test]
256 fn chaos_file_writes_and_truncates_on_partial() {
257 let dir = tempfile::tempdir().unwrap();
258 let path = dir.path().join("log.txt");
259 let schedule = FailureSchedule::on_attempts(&[2], FailureMode::PartialWrite);
260 let mut f = ChaosFile::create(&path, schedule).unwrap();
261 f.write_all(b"first").unwrap();
262 let _ = f.write(b"second"); drop(f);
264 let bytes = std::fs::read(&path).unwrap();
265 assert_eq!(bytes, b"firsts");
267 }
268
269 #[test]
270 fn into_inner_returns_underlying() {
271 let data: &[u8] = b"x";
272 let schedule = FailureSchedule::on_attempts(&[], FailureMode::IoError);
273 let r = ChaosReader::new(data, schedule);
274 let inner = r.into_inner();
275 assert_eq!(inner, b"x");
276 }
277}