1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
// This file is part of the uutils coreutils package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
//! Tests for EINTR (ErrorKind::Interrupted) handling across utilities
//!
//! This module provides test utilities and integration tests to verify that
//! utilities properly handle signal interruptions during I/O operations.
//!
//! # CI Integration
//! EINTR handling tests are NOW visible in CI logs through integration tests:
//! - `test_cat_eintr_handling` in `tests/by-util/test_cat.rs`
//! - `test_comm_eintr_handling` in `tests/by-util/test_comm.rs`
//! - `test_od_eintr_handling` in `tests/by-util/test_od.rs`
//!
//! These integration tests use the mock utilities from this module to verify
//! that each utility properly handles signal interruptions during I/O operations.
//! Test results appear in CI logs under the "Test" steps when running `cargo nextest run`.
//!
//! # Note
//! EINTR is a POSIX error code for interrupted system calls
//! cspell:ignore EINTR worl
use std::io::{self, Read, Write};
/// A mock reader that injects `ErrorKind::Interrupted` errors
///
/// This reader wraps another reader and injects a specified number of
/// `Interrupted` errors before allowing reads to succeed. This simulates
/// the behavior of system calls being interrupted by signals (EINTR).
pub struct InterruptingReader<R: Read> {
inner: R,
interrupts_remaining: usize,
bytes_read: usize,
}
impl<R: Read> InterruptingReader<R> {
/// Create a new interrupting reader
///
/// # Arguments
/// * `inner` - The underlying reader to wrap
/// * `num_interrupts` - Number of times to return `Interrupted` error before succeeding
pub fn new(inner: R, num_interrupts: usize) -> Self {
Self {
inner,
interrupts_remaining: num_interrupts,
bytes_read: 0,
}
}
/// Get the total number of bytes successfully read
pub fn bytes_read(&self) -> usize {
self.bytes_read
}
}
impl<R: Read> Read for InterruptingReader<R> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
if self.interrupts_remaining > 0 {
self.interrupts_remaining -= 1;
return Err(io::Error::from(io::ErrorKind::Interrupted));
}
match self.inner.read(buf) {
Ok(n) => {
self.bytes_read += n;
Ok(n)
}
err => err,
}
}
}
/// A mock writer that injects `ErrorKind::Interrupted` errors
pub struct InterruptingWriter<W: Write> {
inner: W,
interrupts_remaining: usize,
bytes_written: usize,
}
impl<W: Write> InterruptingWriter<W> {
/// Create a new interrupting writer
pub fn new(inner: W, num_interrupts: usize) -> Self {
Self {
inner,
interrupts_remaining: num_interrupts,
bytes_written: 0,
}
}
/// Get the total number of bytes successfully written
pub fn bytes_written(&self) -> usize {
self.bytes_written
}
}
impl<W: Write> Write for InterruptingWriter<W> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
if self.interrupts_remaining > 0 {
self.interrupts_remaining -= 1;
return Err(io::Error::from(io::ErrorKind::Interrupted));
}
match self.inner.write(buf) {
Ok(n) => {
self.bytes_written += n;
Ok(n)
}
err => err,
}
}
fn flush(&mut self) -> io::Result<()> {
self.inner.flush()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
#[test]
fn test_interrupting_reader_no_interrupts() {
let data = b"hello world";
let mut reader = InterruptingReader::new(Cursor::new(data), 0);
let mut buf = [0u8; 11];
let n = reader.read(&mut buf).unwrap();
assert_eq!(n, 11);
assert_eq!(&buf, data);
assert_eq!(reader.bytes_read(), 11);
}
#[test]
fn test_interrupting_reader_with_interrupts() {
let data = b"hello world";
let mut reader = InterruptingReader::new(Cursor::new(data), 3);
let mut buf = [0u8; 11];
// First 3 reads should fail with Interrupted
for i in 0..3 {
let result = reader.read(&mut buf);
assert!(result.is_err());
assert_eq!(
result.unwrap_err().kind(),
io::ErrorKind::Interrupted,
"Read {} should be interrupted",
i
);
}
// Fourth read should succeed
let n = reader.read(&mut buf).unwrap();
assert_eq!(n, 11);
assert_eq!(&buf, data);
assert_eq!(reader.bytes_read(), 11);
}
#[test]
fn test_interrupting_reader_multiple_chunks() {
let data = b"hello world";
let mut reader = InterruptingReader::new(Cursor::new(data), 2);
let mut buf = [0u8; 5];
// First 2 reads should fail
assert!(reader.read(&mut buf).is_err());
assert!(reader.read(&mut buf).is_err());
// Third read gets first 5 bytes
let n = reader.read(&mut buf).unwrap();
assert_eq!(n, 5);
assert_eq!(&buf, b"hello");
// Read rest of data without interruption
let n = reader.read(&mut buf).unwrap();
assert_eq!(n, 5);
assert_eq!(&buf, b" worl"); // Second chunk of "hello world"
let n = reader.read(&mut buf).unwrap();
assert_eq!(n, 1);
assert_eq!(buf[0], b'd'); // Final 'd' from "world"
assert_eq!(reader.bytes_read(), 11);
}
#[test]
fn test_interrupting_writer_no_interrupts() {
let mut buffer = Vec::new();
let mut writer = InterruptingWriter::new(&mut buffer, 0);
let n = writer.write(b"test").unwrap();
assert_eq!(n, 4);
writer.flush().unwrap();
assert_eq!(buffer, b"test");
assert_eq!(writer.bytes_written(), 4);
}
#[test]
fn test_interrupting_writer_with_interrupts() {
let mut buffer = Vec::new();
let mut writer = InterruptingWriter::new(&mut buffer, 2);
// First 2 writes should fail
assert!(writer.write(b"test").is_err());
assert!(writer.write(b"test").is_err());
// Third write should succeed
let n = writer.write(b"test").unwrap();
assert_eq!(n, 4);
writer.flush().unwrap();
assert_eq!(buffer, b"test");
assert_eq!(writer.bytes_written(), 4);
}
}