od: implement Read for MultifileReader

also add tests and fix error handling
This commit is contained in:
Wim Hueskes 2016-08-02 22:55:23 +02:00
parent c15936ad68
commit 2b10cc47ff
3 changed files with 229 additions and 41 deletions

102
src/od/mockstream.rs Normal file
View 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);
}

View file

@ -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
}
}

View file

@ -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