mirror of
https://github.com/uutils/coreutils
synced 2024-11-16 17:58:06 +00:00
od: implement Read for MultifileReader
also add tests and fix error handling
This commit is contained in:
parent
c15936ad68
commit
2b10cc47ff
3 changed files with 229 additions and 41 deletions
102
src/od/mockstream.rs
Normal file
102
src/od/mockstream.rs
Normal file
|
@ -0,0 +1,102 @@
|
|||
// https://github.com/lazy-bitfield/rust-mockstream/pull/2
|
||||
|
||||
use std::io::{Cursor, Read, Result, Error, ErrorKind};
|
||||
use std::error::Error as errorError;
|
||||
|
||||
/// `FailingMockStream` mocks a stream which will fail upon read or write
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use std::io::{Cursor, Read};
|
||||
///
|
||||
/// struct CountIo {}
|
||||
///
|
||||
/// impl CountIo {
|
||||
/// fn read_data(&self, r: &mut Read) -> usize {
|
||||
/// let mut count: usize = 0;
|
||||
/// let mut retries = 3;
|
||||
///
|
||||
/// loop {
|
||||
/// let mut buffer = [0; 5];
|
||||
/// match r.read(&mut buffer) {
|
||||
/// Err(_) => {
|
||||
/// if retries == 0 { break; }
|
||||
/// retries -= 1;
|
||||
/// },
|
||||
/// Ok(0) => break,
|
||||
/// Ok(n) => count += n,
|
||||
/// }
|
||||
/// }
|
||||
/// count
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// #[test]
|
||||
/// fn test_io_retries() {
|
||||
/// let mut c = Cursor::new(&b"1234"[..])
|
||||
/// .chain(FailingMockStream::new(ErrorKind::Other, "Failing", 3))
|
||||
/// .chain(Cursor::new(&b"5678"[..]));
|
||||
///
|
||||
/// let sut = CountIo {};
|
||||
/// // this will fail unless read_data performs at least 3 retries on I/O errors
|
||||
/// assert_eq!(8, sut.read_data(&mut c));
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Clone)]
|
||||
pub struct FailingMockStream {
|
||||
kind: ErrorKind,
|
||||
message: &'static str,
|
||||
repeat_count: i32,
|
||||
}
|
||||
|
||||
impl FailingMockStream {
|
||||
/// Creates a FailingMockStream
|
||||
///
|
||||
/// When `read` or `write` is called, it will return an error `repeat_count` times.
|
||||
/// `kind` and `message` can be specified to define the exact error.
|
||||
pub fn new(kind: ErrorKind, message: &'static str, repeat_count: i32) -> FailingMockStream {
|
||||
FailingMockStream { kind: kind, message: message, repeat_count: repeat_count, }
|
||||
}
|
||||
|
||||
fn error(&mut self) -> Result<usize> {
|
||||
if self.repeat_count == 0 {
|
||||
return Ok(0)
|
||||
}
|
||||
else {
|
||||
if self.repeat_count > 0 {
|
||||
self.repeat_count -= 1;
|
||||
}
|
||||
Err(Error::new(self.kind, self.message))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Read for FailingMockStream {
|
||||
fn read(&mut self, _: &mut [u8]) -> Result<usize> {
|
||||
self.error()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_failing_mock_stream_read() {
|
||||
let mut s = FailingMockStream::new(ErrorKind::BrokenPipe, "The dog ate the ethernet cable", 1);
|
||||
let mut v = [0; 4];
|
||||
let error = s.read(v.as_mut()).unwrap_err();
|
||||
assert_eq!(error.kind(), ErrorKind::BrokenPipe);
|
||||
assert_eq!(error.description(), "The dog ate the ethernet cable");
|
||||
// after a single error, it will return Ok(0)
|
||||
assert_eq!(s.read(v.as_mut()).unwrap(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_failing_mock_stream_chain_interrupted() {
|
||||
let mut c = Cursor::new(&b"abcd"[..])
|
||||
.chain(FailingMockStream::new(ErrorKind::Interrupted, "Interrupted", 5))
|
||||
.chain(Cursor::new(&b"ABCD"[..]));
|
||||
|
||||
let mut v = [0; 8];
|
||||
c.read_exact(v.as_mut()).unwrap();
|
||||
assert_eq!(v, [0x61, 0x62, 0x63, 0x64, 0x41, 0x42, 0x43, 0x44]);
|
||||
assert_eq!(c.read(v.as_mut()).unwrap(), 0);
|
||||
}
|
|
@ -3,24 +3,26 @@ use std::io;
|
|||
use std::io::BufReader;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::vec::Vec;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum InputSource<'a> {
|
||||
FileName(&'a str ),
|
||||
Stdin
|
||||
Stdin,
|
||||
#[allow(dead_code)]
|
||||
Stream(Box<io::Read>),
|
||||
}
|
||||
|
||||
// MultifileReader - concatenate all our input, file or stdin.
|
||||
pub struct MultifileReader<'a> {
|
||||
ni: std::slice::Iter<'a, InputSource<'a>>,
|
||||
ni: Vec<InputSource<'a>>,
|
||||
curr_file: Option<Box<io::Read>>,
|
||||
pub any_err: bool,
|
||||
}
|
||||
|
||||
impl<'b> MultifileReader<'b> {
|
||||
pub fn new<'a>(fnames: &'a [InputSource]) -> MultifileReader<'a> {
|
||||
pub fn new<'a>(fnames: Vec<InputSource<'a>>) -> MultifileReader<'a> {
|
||||
let mut mf = MultifileReader {
|
||||
ni: fnames.iter(),
|
||||
ni: fnames,
|
||||
curr_file: None, // normally this means done; call next_file()
|
||||
any_err: false,
|
||||
};
|
||||
|
@ -31,47 +33,50 @@ impl<'b> MultifileReader<'b> {
|
|||
fn next_file(&mut self) {
|
||||
// loop retries with subsequent files if err - normally 'loops' once
|
||||
loop {
|
||||
match self.ni.next() {
|
||||
None => {
|
||||
if self.ni.len() == 0 {
|
||||
self.curr_file = None;
|
||||
return;
|
||||
}
|
||||
match self.ni.remove(0) {
|
||||
InputSource::Stdin => {
|
||||
self.curr_file = Some(Box::new(BufReader::new(std::io::stdin())));
|
||||
return;
|
||||
}
|
||||
Some(input) => {
|
||||
match *input {
|
||||
InputSource::Stdin => {
|
||||
self.curr_file = Some(Box::new(BufReader::new(std::io::stdin())));
|
||||
InputSource::FileName(fname) => {
|
||||
match File::open(fname) {
|
||||
Ok(f) => {
|
||||
self.curr_file = Some(Box::new(BufReader::new(f)));
|
||||
return;
|
||||
}
|
||||
InputSource::FileName(fname) => {
|
||||
match File::open(fname) {
|
||||
Ok(f) => {
|
||||
self.curr_file = Some(Box::new(BufReader::new(f)));
|
||||
return;
|
||||
}
|
||||
Err(e) => {
|
||||
// If any file can't be opened,
|
||||
// print an error at the time that the file is needed,
|
||||
// then move on the the next file.
|
||||
// This matches the behavior of the original `od`
|
||||
eprintln!("{}: '{}': {}",
|
||||
executable!().split("::").next().unwrap(), // remove module
|
||||
fname, e);
|
||||
self.any_err = true
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
// If any file can't be opened,
|
||||
// print an error at the time that the file is needed,
|
||||
// then move on the the next file.
|
||||
// This matches the behavior of the original `od`
|
||||
eprintln!("{}: '{}': {}",
|
||||
executable!().split("::").next().unwrap(), // remove module
|
||||
fname, e);
|
||||
self.any_err = true
|
||||
}
|
||||
}
|
||||
}
|
||||
InputSource::Stream(s) => {
|
||||
self.curr_file = Some(s);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'b> io::Read for MultifileReader<'b> {
|
||||
|
||||
// Fill buf with bytes read from the list of files
|
||||
// Returns Ok(<number of bytes read>)
|
||||
// Handles io errors itself, thus always returns OK
|
||||
// Fills the provided buffer completely, unless it has run out of input.
|
||||
// If any call returns short (< buf.len()), all subsequent calls will return Ok<0>
|
||||
pub fn f_read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
let mut xfrd = 0;
|
||||
// while buffer we are filling is not full.. May go thru several files.
|
||||
'fillloop: while xfrd < buf.len() {
|
||||
|
@ -83,7 +88,13 @@ impl<'b> MultifileReader<'b> {
|
|||
xfrd += match curr_file.read(&mut buf[xfrd..]) {
|
||||
Ok(0) => break,
|
||||
Ok(n) => n,
|
||||
Err(e) => panic!("file error: {}", e),
|
||||
Err(e) => {
|
||||
eprintln!("{}: I/O: {}",
|
||||
executable!().split("::").next().unwrap(), // remove module
|
||||
e);
|
||||
self.any_err = true;
|
||||
break;
|
||||
},
|
||||
};
|
||||
if xfrd == buf.len() {
|
||||
// transferred all that was asked for.
|
||||
|
@ -97,3 +108,78 @@ impl<'b> MultifileReader<'b> {
|
|||
Ok(xfrd)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::io::{Cursor, Read, ErrorKind};
|
||||
use mockstream::*;
|
||||
|
||||
#[test]
|
||||
fn test_multi_file_reader_one_read() {
|
||||
let mut inputs = Vec::new();
|
||||
inputs.push(InputSource::Stream(Box::new(Cursor::new(&b"abcd"[..]))));
|
||||
inputs.push(InputSource::Stream(Box::new(Cursor::new(&b"ABCD"[..]))));
|
||||
let mut v = [0; 10];
|
||||
|
||||
let mut sut = MultifileReader::new(inputs);
|
||||
|
||||
assert_eq!(sut.read(v.as_mut()).unwrap(), 8);
|
||||
assert_eq!(v, [0x61, 0x62, 0x63, 0x64, 0x41, 0x42, 0x43, 0x44, 0, 0]);
|
||||
assert_eq!(sut.read(v.as_mut()).unwrap(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multi_file_reader_two_reads() {
|
||||
let mut inputs = Vec::new();
|
||||
inputs.push(InputSource::Stream(Box::new(Cursor::new(&b"abcd"[..]))));
|
||||
inputs.push(InputSource::Stream(Box::new(Cursor::new(&b"ABCD"[..]))));
|
||||
let mut v = [0; 5];
|
||||
|
||||
let mut sut = MultifileReader::new(inputs);
|
||||
|
||||
assert_eq!(sut.read(v.as_mut()).unwrap(), 5);
|
||||
assert_eq!(v, [0x61, 0x62, 0x63, 0x64, 0x41]);
|
||||
assert_eq!(sut.read(v.as_mut()).unwrap(), 3);
|
||||
assert_eq!(v, [0x42, 0x43, 0x44, 0x64, 0x41]); // last two bytes are not overwritten
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multi_file_reader_read_error() {
|
||||
let c = Cursor::new(&b"1234"[..])
|
||||
.chain(FailingMockStream::new(ErrorKind::Other, "Failing", 1))
|
||||
.chain(Cursor::new(&b"5678"[..]));
|
||||
let mut inputs = Vec::new();
|
||||
inputs.push(InputSource::Stream(Box::new(c)));
|
||||
inputs.push(InputSource::Stream(Box::new(Cursor::new(&b"ABCD"[..]))));
|
||||
let mut v = [0; 5];
|
||||
|
||||
let mut sut = MultifileReader::new(inputs);
|
||||
|
||||
assert_eq!(sut.read(v.as_mut()).unwrap(), 5);
|
||||
assert_eq!(v, [49, 50, 51, 52, 65]);
|
||||
assert_eq!(sut.read(v.as_mut()).unwrap(), 3);
|
||||
assert_eq!(v, [66, 67, 68, 52, 65]); // last two bytes are not overwritten
|
||||
|
||||
// note: no retry on i/o error, so 5678 is missing
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multi_file_reader_read_error_at_start() {
|
||||
let mut inputs = Vec::new();
|
||||
inputs.push(InputSource::Stream(Box::new(FailingMockStream::new(ErrorKind::Other, "Failing", 1))));
|
||||
inputs.push(InputSource::Stream(Box::new(Cursor::new(&b"abcd"[..]))));
|
||||
inputs.push(InputSource::Stream(Box::new(FailingMockStream::new(ErrorKind::Other, "Failing", 1))));
|
||||
inputs.push(InputSource::Stream(Box::new(Cursor::new(&b"ABCD"[..]))));
|
||||
inputs.push(InputSource::Stream(Box::new(FailingMockStream::new(ErrorKind::Other, "Failing", 1))));
|
||||
let mut v = [0; 5];
|
||||
|
||||
let mut sut = MultifileReader::new(inputs);
|
||||
|
||||
assert_eq!(sut.read(v.as_mut()).unwrap(), 5);
|
||||
assert_eq!(v, [0x61, 0x62, 0x63, 0x64, 0x41]);
|
||||
assert_eq!(sut.read(v.as_mut()).unwrap(), 3);
|
||||
assert_eq!(v, [0x42, 0x43, 0x44, 0x64, 0x41]); // last two bytes are not overwritten
|
||||
}
|
||||
|
||||
}
|
||||
|
|
22
src/od/od.rs
22
src/od/od.rs
|
@ -22,8 +22,11 @@ mod formatteriteminfo;
|
|||
mod prn_int;
|
||||
mod prn_char;
|
||||
mod prn_float;
|
||||
#[cfg(test)]
|
||||
mod mockstream;
|
||||
|
||||
use std::cmp;
|
||||
use std::io::Read;
|
||||
use std::io::Write;
|
||||
use unindent::*;
|
||||
use byteorder_io::*;
|
||||
|
@ -134,8 +137,7 @@ pub fn uumain(args: Vec<String>) -> i32 {
|
|||
};
|
||||
|
||||
// Gather up file names - args which don't start with '-'
|
||||
let stdnionly = [InputSource::Stdin];
|
||||
let inputs = args[1..]
|
||||
let mut inputs = args[1..]
|
||||
.iter()
|
||||
.filter_map(|w| match w as &str {
|
||||
"--" => Some(InputSource::Stdin),
|
||||
|
@ -143,12 +145,10 @@ pub fn uumain(args: Vec<String>) -> i32 {
|
|||
x => Some(InputSource::FileName(x)),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
// If no input files named, use stdin.
|
||||
let inputs = if inputs.len() == 0 {
|
||||
&stdnionly[..]
|
||||
} else {
|
||||
&inputs[..]
|
||||
};
|
||||
if inputs.len() == 0 {
|
||||
inputs.push(InputSource::Stdin);
|
||||
}
|
||||
|
||||
// Gather up format flags, we don't use getopts becase we need keep them in order.
|
||||
let flags = args[1..]
|
||||
.iter()
|
||||
|
@ -216,11 +216,11 @@ pub fn uumain(args: Vec<String>) -> i32 {
|
|||
|
||||
let output_duplicates = matches.opt_present("v");
|
||||
|
||||
odfunc(line_bytes, input_offset_base, byte_order, &inputs, &formats[..], output_duplicates)
|
||||
odfunc(line_bytes, input_offset_base, byte_order, inputs, &formats[..], output_duplicates)
|
||||
}
|
||||
|
||||
fn odfunc(line_bytes: usize, input_offset_base: Radix, byte_order: ByteOrder,
|
||||
fnames: &[InputSource], formats: &[FormatterItemInfo], output_duplicates: bool) -> i32 {
|
||||
fnames: Vec<InputSource>, formats: &[FormatterItemInfo], output_duplicates: bool) -> i32 {
|
||||
|
||||
let mut mf = MultifileReader::new(fnames);
|
||||
let mut addr = 0;
|
||||
|
@ -270,7 +270,7 @@ fn odfunc(line_bytes: usize, input_offset_base: Radix, byte_order: ByteOrder,
|
|||
// print each line data (or multi-format raster of several lines describing the same data).
|
||||
// TODO: we need to read more data in case a multi-byte sequence starts at the end of the line
|
||||
|
||||
match mf.f_read(bytes.as_mut_slice()) {
|
||||
match mf.read(bytes.as_mut_slice()) {
|
||||
Ok(0) => {
|
||||
if input_offset_base != Radix::NoPrefix {
|
||||
print!("{}\n", print_with_radix(input_offset_base, addr)); // print final offset
|
||||
|
|
Loading…
Reference in a new issue