Addresses issues raised in PR#2474

- runs rustfmt on test_dd.rs
- eliminates compiler warnings
- adds many words to spellchecker ignore list
- adds sanity test for vexing conv=nocreat issue. Still WIP.
This commit is contained in:
Tyler 2021-07-06 11:52:48 -07:00
parent 418ecbe61a
commit 1ad89c5e89
9 changed files with 455 additions and 360 deletions

2
Cargo.lock generated
View file

@ -1911,7 +1911,7 @@ dependencies = [
[[package]]
name = "uu_dd"
version = "0.0.4"
version = "0.0.6"
dependencies = [
"byte-unit",
"clap",

View file

@ -248,7 +248,7 @@ cp = { optional=true, version="0.0.6", package="uu_cp", path="src/uu/cp" }
csplit = { optional=true, version="0.0.6", package="uu_csplit", path="src/uu/csplit" }
cut = { optional=true, version="0.0.6", package="uu_cut", path="src/uu/cut" }
date = { optional=true, version="0.0.6", package="uu_date", path="src/uu/date" }
dd = { optional=true, version="0.0.4", package="uu_dd", path="src/uu/dd" }
dd = { optional=true, version="0.0.6", package="uu_dd", path="src/uu/dd" }
df = { optional=true, version="0.0.6", package="uu_df", path="src/uu/df" }
dircolors= { optional=true, version="0.0.6", package="uu_dircolors", path="src/uu/dircolors" }
dirname = { optional=true, version="0.0.6", package="uu_dirname", path="src/uu/dirname" }

View file

@ -1,6 +1,6 @@
[package]
name = "uu_dd"
version = "0.0.4"
version = "0.0.6"
authors = ["uutils developers"]
license = "MIT"
description = "dd ~ (uutils) copy and convert files"
@ -16,12 +16,12 @@ path = "src/dd.rs"
[dependencies]
byte-unit = "4.0"
clap = "2.33.3"
clap = { version = "2.33", features = [ "wrap_help" ] }
debug_print = "1.0"
gcd = "2.0"
libc = "0.2"
signal-hook = "0.3.9"
uucore = { version=">=0.0.7", package="uucore", path="../../uucore" }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[dev-dependencies]

View file

@ -162,10 +162,9 @@ impl std::fmt::Display for InternalError {
Self::WrongInputType | Self::WrongOutputType => {
write!(f, "Internal dd error: Wrong Input/Output data type")
}
Self::InvalidConvBlockUnblockCase => write!(
f,
"Internal dd error: Invalid Conversion, Block, or Unblock data"
),
Self::InvalidConvBlockUnblockCase => {
write!(f, "Invalid Conversion, Block, or Unblock data")
}
}
}
}

View file

@ -5,7 +5,7 @@
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
// spell-checker:ignore (T0DO)
// spell-checker:ignore (parseargs xfer cflags iflags parseargs parseargs xfer cflags iflags iflags iflags xfer cflags oflags oflags oflags oflags dsync DSYNC noatime NOATIME noctty NOCTTY nofollow NOFOLLOW nonblock NONBLOCK xfer cflags fname fname tlen rlen fullblock rlen tlen tlen noerror rlen rlen remaing plen plen plen plen oflag dsync DSYNC noatime NOATIME noctty NOCTTY nofollow NOFOLLOW nonblock NONBLOCK fname notrunc nocreat fname wlen wlen wlen wlen rstat rstat curr curr curr curr rposition rstat ctable ctable rstat ctable ctable btotal btotal btotal btotal SIGUSR SIGUSR sigval SIGINFO SIGINFO sigval SIGINFO SIGINFO SIGUSR sigval SIGUSR permenantly sigval itegral itegral wstat rmax rmax rmax rsofar rremain rmax rsofar rremain bmax bmax bmax bremain bmax wstat bremain wstat wstat opertaions Noxfer opertaions fileout Noxfer INFILE OUTFILE fileout fileout INFILE INFILE OUTFILE OUTFILE iflag oflag iflag noxfer ucase lcase ucase lcase nocreat nocreat notrunc noerror IFLAG IFLAG iflag iflag fullblock oflag dsync syncronized syncronized nonblock noatime nocache noctty nofollow notrunc OFLAG OFLAG oflag notrunc dsync syncronized syncronized nonblock noatime nocache noctty nofollow T0DO)
#[macro_use]
extern crate uucore;
@ -38,6 +38,7 @@ use std::fs::{File, OpenOptions};
use std::io::{self, Read, Seek, Write};
#[cfg(unix)]
use std::os::unix::fs::OpenOptionsExt;
use std::path::Path;
use std::sync::{atomic::AtomicUsize, atomic::Ordering, mpsc, Arc};
use std::thread;
use std::time;
@ -303,11 +304,7 @@ impl Output<io::Stdout> {
let dst = io::stdout();
Ok(Output {
dst,
obs,
cflags,
})
Ok(Output { dst, obs, cflags })
}
fn fsync(&mut self) -> io::Result<()> {
@ -360,40 +357,42 @@ fn make_unix_oflags(oflags: &OFlags) -> Option<libc::c_int> {
impl Output<File> {
fn new(matches: &Matches) -> Result<Self, Box<dyn Error>> {
fn open_dst(
path: &Path,
cflags: &OConvFlags,
oflags: &OFlags,
) -> Result<File, Box<dyn Error>> {
let mut opts = OpenOptions::new();
opts.write(true)
.create(!cflags.nocreat)
.truncate(!cflags.notrunc)
.create_new(cflags.excl)
.append(oflags.append);
if cfg!(unix) {
if let Some(libc_flags) = make_unix_oflags(oflags) {
opts.custom_flags(libc_flags);
}
}
let dst = opts.open(path)?;
Ok(dst)
}
let obs = parseargs::parse_obs(matches)?;
let cflags = parseargs::parse_conv_flag_output(matches)?;
let oflags = parseargs::parse_oflags(matches)?;
let seek = parseargs::parse_seek_amt(&obs, &oflags, matches)?;
if let Some(fname) = matches.value_of("of") {
let mut dst = {
let mut opts = OpenOptions::new();
opts.write(true)
.create(true)
.truncate(!cflags.notrunc)
.append(oflags.append)
// 'create_new' overrides 'create'
.create_new(cflags.excl && !cflags.nocreat);
if cfg!(unix) {
if let Some(libc_flags) = make_unix_oflags(&oflags) {
opts.custom_flags(libc_flags);
}
}
opts.open(fname)?
};
let mut dst = open_dst(Path::new(&fname), &cflags, &oflags)?;
if let Some(amt) = seek {
let amt: u64 = amt.try_into()?;
dst.seek(io::SeekFrom::Start(amt))?;
}
Ok(Output {
dst,
obs,
cflags,
})
Ok(Output { dst, obs, cflags })
} else {
// The following error should only occur if someone
// mistakenly calls Output::<File>::new() without checking
@ -1083,7 +1082,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.iter()
.fold(Vec::new(), append_dashes_if_not_present);
let matches = build_dd_app!()
let matches = uu_app()
// TODO: usage, after_help
//.usage(...)
//.after_help(...)
@ -1134,14 +1133,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
}
pub fn uu_app() -> clap::App<'static, 'static> {
build_dd_app!()
}
#[macro_export]
macro_rules! build_dd_app (
() =>
{
clap::App::new(executable!())
clap::App::new(executable!())
.version(crate_version!())
.about(ABOUT)
.arg(
@ -1306,5 +1298,176 @@ General-Flags
")
)
};
);
}
// #[macro_export]
// macro_rules! build_dd_app (
// () =>
// {
// clap::App::new(executable!())
// .version(crate_version!())
// .about(ABOUT)
// .arg(
// clap::Arg::with_name(options::INFILE)
// .long(options::INFILE)
// .takes_value(true)
// .help("if=FILE (alternatively --if FILE) specifies the file used for input. When not specified, stdin is used instead")
// )
// .arg(
// clap::Arg::with_name(options::OUTFILE)
// .long(options::OUTFILE)
// .takes_value(true)
// .help("of=FILE (alternatively --of FILE) specifies the file used for output. When not specified, stdout is used instead")
// )
// .arg(
// clap::Arg::with_name(options::IBS)
// .long(options::IBS)
// .takes_value(true)
// .help("ibs=N (alternatively --ibs N) specifies the size of buffer used for reads (default: 512). Multiplier strings permitted.")
// )
// .arg(
// clap::Arg::with_name(options::OBS)
// .long(options::OBS)
// .takes_value(true)
// .help("obs=N (alternatively --obs N) specifies the size of buffer used for writes (default: 512). Multiplier strings permitted.")
// )
// .arg(
// clap::Arg::with_name(options::BS)
// .long(options::BS)
// .takes_value(true)
// .help("bs=N (alternatively --bs N) specifies ibs=N and obs=N (default: 512). If ibs or obs are also specified, bs=N takes precedence. Multiplier strings permitted.")
// )
// .arg(
// clap::Arg::with_name(options::CBS)
// .long(options::CBS)
// .takes_value(true)
// .help("cbs=BYTES (alternatively --cbs BYTES) specifies the 'conversion block size' in bytes. Applies to the conv=block, and conv=unblock operations. Multiplier strings permitted.")
// )
// .arg(
// clap::Arg::with_name(options::SKIP)
// .long(options::SKIP)
// .takes_value(true)
// .help("skip=N (alternatively --skip N) causes N ibs-sized records of input to be skipped before beginning copy/convert operations. See iflag=count_bytes if skipping N bytes is preferred. Multiplier strings permitted.")
// )
// .arg(
// clap::Arg::with_name(options::SEEK)
// .long(options::SEEK)
// .takes_value(true)
// .help("seek=N (alternatively --seek N) seeks N obs-sized records into output before beginning copy/convert operations. See oflag=seek_bytes if seeking N bytes is preferred. Multiplier strings permitted.")
// )
// .arg(
// clap::Arg::with_name(options::COUNT)
// .long(options::COUNT)
// .takes_value(true)
// .help("count=N (alternatively --count N) stop reading input after N ibs-sized read operations rather than proceeding until EOF. See iflag=count_bytes if stopping after N bytes is preferred. Multiplier strings permitted.")
// )
// .arg(
// clap::Arg::with_name(options::STATUS)
// .long(options::STATUS)
// .takes_value(true)
// .help("status=LEVEL (alternatively --status LEVEL) controls whether volume and performance stats are written to stderr.
//
// When unspecified, dd will print stats upon completion. An example is below.
// \t6+0 records in
// \t16+0 records out
// \t8192 bytes (8.2 kB, 8.0 KiB) copied, 0.00057009 s, 14.4 MB/s
// The first two lines are the 'volume' stats and the final line is the 'performance' stats.
// The volume stats indicate the number of complete and partial ibs-sized reads, or obs-sized writes that took place during the copy. The format of the volume stats is <complete>+<partial>. If records have been truncated (see conv=block), the volume stats will contain the number of truncated records.
//
// Permissible LEVEL values are:
// \t progress: Print periodic performance stats as the copy proceeds.
// \t noxfer: Print final volume stats, but not performance stats.
// \t none: Do not print any stats.
//
// Printing performance stats is also triggered by the INFO signal (where supported), or the USR1 signal. Setting the POSIXLY_CORRECT environment variable to any value (including an empty value) will cause the USR1 signal to be ignored.
//
// ")
// )
// .arg(
// clap::Arg::with_name(options::CONV)
// .long(options::CONV)
// .takes_value(true)
// .help("conv=CONV[,CONV] (alternatively --conv CONV[,CONV]) specifies a comma-separated list of conversion options or (for legacy reasons) file-flags. Conversion options and file flags may be intermixed.
//
// Conversion options:
// \t One of {ascii, ebcdic, ibm} will perform an encoding conversion.
// \t\t 'ascii' converts from EBCDIC to ASCII. This is the inverse of the 'ebcdic' option.
// \t\t 'ebcdic' converts from ASCII to EBCDIC. This is the inverse of the 'ascii' option.
// \t\t 'ibm' converts from ASCII to EBCDIC, applying the conventions for '[', ']' and '~' specified in POSIX.
//
// \t One of {ucase, lcase} will perform a case conversion. Works in conjunction with option {ascii, ebcdic, ibm} to infer input encoding. If no other conversion option is specified, input is assumed to be ascii.
// \t\t 'ucase' converts from lower-case to upper-case
// \t\t 'lcase' converts from upper-case to lower-case.
//
// \t One of {block, unblock}. Convert between lines terminated by newline characters, and fixed-width lines padded by spaces (without any newlines). Both the 'block' and 'unblock' options require cbs=BYTES be specified.
// \t\t 'block' for each newline less than the size indicated by cbs=BYTES, remove the newline and pad with spaces up to cbs. Lines longer than cbs are truncated.
// \t\t 'unblock' for each block of input of the size indicated by cbs=BYTES, remove right-trailing spaces and replace with a newline character.
//
// \t 'sparse' attempts to seek the output when an obs-sized block consists of only zeros.
// \t 'swab' swaps each adjacent pair of bytes. If an odd number of bytes is present, the final byte is omitted.
// \t 'sync' pad each ibs-sided block with zeros. If 'block' or 'unblock' is specified, pad with spaces instead.
//
// Flags:
// \t One of {excl, nocreat}
// \t\t 'excl' the output file must be created. Fail if the output file is already present.
// \t\t 'nocreat' the output file will not be created. Fail if the output file in not already present.
// \t 'notrunc' the output file will not be truncated. If this option is not present, output will be truncated when opened.
// \t 'noerror' all read errors will be ignored. If this option is not present, dd will only ignore Error::Interrupted.
// \t 'fdatasync' data will be written before finishing.
// \t 'fsync' data and metadata will be written before finishing.
//
// ")
// )
// .arg(
// clap::Arg::with_name(options::IFLAG)
// .long(options::IFLAG)
// .takes_value(true)
// .help("iflag=FLAG[,FLAG] (alternatively --iflag FLAG[,FLAG]) a comma separated list of input flags which specify how the input source is treated. FLAG may be any of the input-flags or general-flags specified below.
//
// Input-Flags
// \t 'count_bytes' a value to count=N will be interpreted as bytes.
// \t 'skip_bytes' a value to skip=N will be interpreted as bytes.
// \t 'fullblock' wait for ibs bytes from each read. zero-length reads are still considered EOF.
//
// General-Flags
// \t 'direct' use direct I/O for data.
// \t 'directory' fail unless the given input (if used as an iflag) or output (if used as an oflag) is a directory.
// \t 'dsync' use syncronized I/O for data.
// \t 'sync' use syncronized I/O for data and metadata.
// \t 'nonblock' use non-blocking I/O.
// \t 'noatime' do not update access time.
// \t 'nocache' request that OS drop cache.
// \t 'noctty' do not assign a controlling tty.
// \t 'nofollow' do not follow system links.
//
// Output-Flags
// \t 'append' open file in append mode. Consider setting conv=notrunc as well.
// \t 'seek_bytes' a value to seek=N will be interpreted as bytes.
//
// ")
// )
// .arg(
// clap::Arg::with_name(options::OFLAG)
// .long(options::OFLAG)
// .takes_value(true)
// .help("oflag=FLAG[,FLAG] (alternatively --oflag FLAG[,FLAG]) a comma separated list of output flags which specify how the output source is treated. FLAG may be any of the output-flags or general-flags specified below.
//
// Output-Flags
// \t 'append' open file in append mode. Consider setting conv=notrunc as well.
// \t 'seek_bytes' a value to seek=N will be interpreted as bytes.
//
// General-Flags
// \t 'direct' use direct I/O for data.
// \t 'directory' fail unless the given input (if used as an iflag) or output (if used as an oflag) is a directory.
// \t 'dsync' use syncronized I/O for data.
// \t 'sync' use syncronized I/O for data and metadata.
// \t 'nonblock' use non-blocking I/O.
// \t 'noatime' do not update access time.
// \t 'nocache' request that OS drop cache.
// \t 'noctty' do not assign a controlling tty.
// \t 'nofollow' do not follow system links.
//
// ")
// )
// };
// );

View file

@ -313,3 +313,16 @@ fn bsize_test_bs_eq() {
assert_eq!(res, m);
}
#[test]
#[should_panic]
fn test_nocreate_causes_failure_when_ofile_doesnt_exist() {
let args = vec![
String::from("dd"),
String::from("--conv=nocreat"),
String::from("--of=not-a-real.file"),
];
let matches = uu_app().get_matches_from_safe(args).unwrap();
let _ = Output::<File>::new(&matches).unwrap();
}

View file

@ -1,3 +1,5 @@
// spell-checker:ignore (parseargs xfer cflags iflags parseargs parseargs xfer cflags iflags iflags iflags xfer cflags oflags oflags oflags oflags dsync DSYNC noatime NOATIME noctty NOCTTY nofollow NOFOLLOW nonblock NONBLOCK xfer cflags fname fname tlen rlen fullblock rlen tlen tlen noerror rlen rlen remaing plen plen plen plen oflag dsync DSYNC noatime NOATIME noctty NOCTTY nofollow NOFOLLOW nonblock NONBLOCK fname notrunc nocreat fname wlen wlen wlen wlen rstat rstat curr curr curr curr rposition rstat ctable ctable rstat ctable ctable btotal btotal btotal btotal SIGUSR SIGUSR sigval SIGINFO SIGINFO sigval SIGINFO SIGINFO SIGUSR sigval SIGUSR permenantly sigval itegral itegral wstat rmax rmax rmax rsofar rremain rmax rsofar rremain bmax bmax bmax bremain bmax wstat bremain wstat wstat opertaions Noxfer opertaions fileout Noxfer INFILE OUTFILE fileout fileout INFILE INFILE OUTFILE OUTFILE iflag oflag iflag noxfer ucase lcase ucase lcase nocreat nocreat notrunc noerror IFLAG IFLAG iflag iflag fullblock oflag dsync syncronized syncronized nonblock noatime nocache noctty nofollow notrunc OFLAG OFLAG oflag notrunc dsync syncronized syncronized nonblock noatime nocache noctty nofollow T0DO)
#[cfg(test)]
mod unit_tests;
@ -127,6 +129,7 @@ impl std::str::FromStr for ConvFlag {
}
}
#[derive(Debug, PartialEq)]
enum Flag {
// Input only
FullBlock,
@ -317,9 +320,7 @@ fn parse_multiplier<'a>(s: &'a str) -> Result<usize, ParseError> {
};
mult.try_into()
.map_err(|_e| {
ParseError::MultiplierStringWouldOverflow(s.to_string())
})
.map_err(|_e| ParseError::MultiplierStringWouldOverflow(s.to_string()))
}
fn parse_bytes_only(s: &str) -> Result<usize, ParseError> {

View file

@ -1,6 +1,6 @@
use super::*;
use crate::{build_dd_app, StatusLevel};
use crate::StatusLevel;
#[cfg(not(unix))]
#[test]
@ -23,7 +23,7 @@ fn unimplemented_flags_should_error_non_unix() {
format!("--iflag={}", flag),
format!("--oflag={}", flag),
];
let matches = build_dd_app!().get_matches_from_safe(args).unwrap();
let matches = uu_app().get_matches_from_safe(args).unwrap();
match parse_iflags(&matches) {
Ok(_) => unfailed.push(format!("iflag={}", flag)),
@ -54,7 +54,7 @@ fn unimplemented_flags_should_error() {
format!("--iflag={}", flag),
format!("--oflag={}", flag),
];
let matches = build_dd_app!().get_matches_from_safe(args).unwrap();
let matches = uu_app().get_matches_from_safe(args).unwrap();
match parse_iflags(&matches) {
Ok(_) => unfailed.push(format!("iflag={}", flag)),
@ -82,7 +82,7 @@ fn test_status_level_absent() {
String::from("--of=bar.file"),
];
let matches = build_dd_app!().get_matches_from_safe(args).unwrap();
let matches = uu_app().get_matches_from_safe(args).unwrap();
let st = parse_status_level(&matches).unwrap();
assert_eq!(st, None);
@ -97,7 +97,7 @@ fn test_status_level_none() {
String::from("--of=bar.file"),
];
let matches = build_dd_app!().get_matches_from_safe(args).unwrap();
let matches = uu_app().get_matches_from_safe(args).unwrap();
let st = parse_status_level(&matches).unwrap().unwrap();
assert_eq!(st, StatusLevel::None);
@ -112,7 +112,7 @@ fn test_status_level_progress() {
String::from("--status=progress"),
];
let matches = build_dd_app!().get_matches_from_safe(args).unwrap();
let matches = uu_app().get_matches_from_safe(args).unwrap();
let st = parse_status_level(&matches).unwrap().unwrap();
assert_eq!(st, StatusLevel::Progress);
@ -127,7 +127,7 @@ fn test_status_level_noxfer() {
String::from("--of=bar.file"),
];
let matches = build_dd_app!().get_matches_from_safe(args).unwrap();
let matches = uu_app().get_matches_from_safe(args).unwrap();
let st = parse_status_level(&matches).unwrap().unwrap();
assert_eq!(st, StatusLevel::Noxfer);
@ -140,7 +140,7 @@ fn test_status_level_noxfer() {
fn icf_ctable_error() {
let args = vec![String::from("dd"), String::from("--conv=ascii,ebcdic,ibm")];
let matches = build_dd_app!().get_matches_from_safe(args).unwrap();
let matches = uu_app().get_matches_from_safe(args).unwrap();
let _ = parse_conv_flag_input(&matches).unwrap();
}
@ -150,7 +150,7 @@ fn icf_ctable_error() {
fn icf_case_error() {
let args = vec![String::from("dd"), String::from("--conv=ucase,lcase")];
let matches = build_dd_app!().get_matches_from_safe(args).unwrap();
let matches = uu_app().get_matches_from_safe(args).unwrap();
let _ = parse_conv_flag_input(&matches).unwrap();
}
@ -160,7 +160,7 @@ fn icf_case_error() {
fn icf_block_error() {
let args = vec![String::from("dd"), String::from("--conv=block,unblock")];
let matches = build_dd_app!().get_matches_from_safe(args).unwrap();
let matches = uu_app().get_matches_from_safe(args).unwrap();
let _ = parse_conv_flag_input(&matches).unwrap();
}
@ -170,7 +170,7 @@ fn icf_block_error() {
fn icf_creat_error() {
let args = vec![String::from("dd"), String::from("--conv=excl,nocreat")];
let matches = build_dd_app!().get_matches_from_safe(args).unwrap();
let matches = uu_app().get_matches_from_safe(args).unwrap();
let _ = parse_conv_flag_output(&matches).unwrap();
}
@ -180,7 +180,7 @@ fn parse_icf_token_ibm() {
let exp = vec![ConvFlag::FmtAtoI];
let args = vec![String::from("dd"), String::from("--conv=ibm")];
let matches = build_dd_app!().get_matches_from_safe(args).unwrap();
let matches = uu_app().get_matches_from_safe(args).unwrap();
let act = parse_flag_list::<ConvFlag>("conv", &matches).unwrap();
@ -198,7 +198,7 @@ fn parse_icf_tokens_elu() {
String::from("dd"),
String::from("--conv=ebcdic,lcase,unblock"),
];
let matches = build_dd_app!().get_matches_from_safe(args).unwrap();
let matches = uu_app().get_matches_from_safe(args).unwrap();
let act = parse_flag_list::<ConvFlag>("conv", &matches).unwrap();
assert_eq!(exp.len(), act.len());
@ -229,7 +229,7 @@ fn parse_icf_tokens_remaining() {
String::from("dd"),
String::from("--conv=ascii,ucase,block,sparse,swab,sync,noerror,excl,nocreat,notrunc,noerror,fdatasync,fsync"),
];
let matches = build_dd_app!().get_matches_from_safe(args).unwrap();
let matches = uu_app().get_matches_from_safe(args).unwrap();
let act = parse_flag_list::<ConvFlag>("conv", &matches).unwrap();
@ -239,6 +239,82 @@ fn parse_icf_tokens_remaining() {
}
}
#[test]
fn parse_iflag_tokens() {
let exp = vec![
Flag::FullBlock,
Flag::CountBytes,
Flag::SkipBytes,
// Flag::Cio,
Flag::Direct,
Flag::Directory,
Flag::Dsync,
Flag::Sync,
// Flag::NoCache,
Flag::NonBlock,
Flag::NoATime,
Flag::NoCtty,
Flag::NoFollow,
// Flag::NoLinks,
// Flag::Binary,
// Flag::Text,
Flag::Append,
Flag::SeekBytes,
];
let args = vec![
String::from("dd"),
String::from("--iflag=fullblock,count_bytes,skip_bytes,direct,directory,dsync,sync,nonblock,noatime,noctty,nofollow,append,seek_bytes"),
// String::from("--iflag=fullblock,count_bytes,skip_bytes,cio,direct,directory,dsync,sync,nocache,nonblock,noatime,noctty,nofollow,nolinks,binary,text,append,seek_bytes"),
];
let matches = uu_app().get_matches_from_safe(args).unwrap();
let act = parse_flag_list::<Flag>("iflag", &matches).unwrap();
assert_eq!(exp.len(), act.len());
for cf in &exp {
assert!(exp.contains(&cf));
}
}
#[test]
fn parse_oflag_tokens() {
let exp = vec![
Flag::FullBlock,
Flag::CountBytes,
Flag::SkipBytes,
// Flag::Cio,
Flag::Direct,
Flag::Directory,
Flag::Dsync,
Flag::Sync,
// Flag::NoCache,
Flag::NonBlock,
Flag::NoATime,
Flag::NoCtty,
Flag::NoFollow,
// Flag::NoLinks,
// Flag::Binary,
// Flag::Text,
Flag::Append,
Flag::SeekBytes,
];
let args = vec![
String::from("dd"),
String::from("--oflag=fullblock,count_bytes,skip_bytes,direct,directory,dsync,sync,nonblock,noatime,noctty,nofollow,append,seek_bytes"),
// String::from("--oflag=fullblock,count_bytes,skip_bytes,cio,direct,directory,dsync,sync,nocache,nonblock,noatime,noctty,nofollow,nolinks,binary,text,append,seek_bytes"),
];
let matches = uu_app().get_matches_from_safe(args).unwrap();
let act = parse_flag_list::<Flag>("oflag", &matches).unwrap();
assert_eq!(exp.len(), act.len());
for cf in &exp {
assert!(exp.contains(&cf));
}
}
// ----- Multiplier Strings etc. -----
macro_rules! test_byte_parser (
( $test_name:ident, $bs_str:expr, $bs:expr ) =>

View file

@ -1,53 +1,41 @@
use crate::common::util::*;
use std::fs::{File, OpenOptions};
use std::io::{BufReader, Read, Write};
use std::path::{PathBuf};
use std::fs::{OpenOptions, File};
use std::path::PathBuf;
use tempfile::tempfile;
macro_rules! inf
{
($fname:expr) =>
{{
macro_rules! inf {
($fname:expr) => {{
&format!("if={}", $fname)
}};
}
macro_rules! of
{
($fname:expr) =>
{{
macro_rules! of {
($fname:expr) => {{
&format!("of={}", $fname)
}};
}
macro_rules! fixture_path
{
($fname:expr) =>
{{
macro_rules! fixture_path {
($fname:expr) => {{
PathBuf::from(format!("./tests/fixtures/dd/{}", $fname))
}};
}
macro_rules! assert_fixture_exists
{
($fname:expr) =>
{{
macro_rules! assert_fixture_exists {
($fname:expr) => {{
let fpath = fixture_path!($fname);
if !fpath.exists()
{
if !fpath.exists() {
panic!("Fixture missing: {:?}", fpath);
}
}};
}
macro_rules! assert_fixture_not_exists
{
($fname:expr) =>
{{
macro_rules! assert_fixture_not_exists {
($fname:expr) => {{
let fpath = PathBuf::from(format!("./fixtures/dd/{}", $fname));
if fpath.exists()
{
if fpath.exists() {
panic!("Fixture present: {:?}", fpath);
}
}};
@ -84,34 +72,23 @@ macro_rules! cmp_file (
};
);
fn build_ascii_block(n: usize) -> Vec<u8>
{
(0..=127)
.cycle()
.take(n)
.collect()
fn build_ascii_block(n: usize) -> Vec<u8> {
(0..=127).cycle().take(n).collect()
}
// Sanity Tests
#[test]
fn version()
{
new_ucmd!()
.args(&["--version"])
.succeeds();
fn version() {
new_ucmd!().args(&["--version"]).succeeds();
}
#[test]
fn help()
{
new_ucmd!()
.args(&["--help"])
.succeeds();
fn help() {
new_ucmd!().args(&["--help"]).succeeds();
}
#[test]
fn test_stdin_stdout()
{
fn test_stdin_stdout() {
let input = build_ascii_block(521);
let output = String::from_utf8(input.clone()).unwrap();
new_ucmd!()
@ -125,17 +102,12 @@ fn test_stdin_stdout()
// Top-Level Items
// count=N, skip=N, status=LEVEL, conv=FLAG, *flag=FLAG
#[test]
fn test_stdin_stdout_count()
{
fn test_stdin_stdout_count() {
let input = build_ascii_block(521);
let mut output = String::from_utf8(input.clone()).unwrap();
output.truncate(256);
new_ucmd!()
.args(&[
"status=none",
"count=2",
"ibs=128",
])
.args(&["status=none", "count=2", "ibs=128"])
.pipe_in(input)
.run()
.no_stderr()
@ -143,17 +115,12 @@ fn test_stdin_stdout_count()
}
#[test]
fn test_stdin_stdout_count_bytes()
{
fn test_stdin_stdout_count_bytes() {
let input = build_ascii_block(521);
let mut output = String::from_utf8(input.clone()).unwrap();
output.truncate(256);
new_ucmd!()
.args(&[
"status=none",
"count=256",
"iflag=count_bytes",
])
.args(&["status=none", "count=256", "iflag=count_bytes"])
.pipe_in(input)
.run()
.no_stderr()
@ -161,17 +128,12 @@ fn test_stdin_stdout_count_bytes()
}
#[test]
fn test_stdin_stdout_skip()
{
fn test_stdin_stdout_skip() {
let input = build_ascii_block(521);
let mut output = String::from_utf8(input.clone()).unwrap();
let _ = output.drain(..256);
new_ucmd!()
.args(&[
"status=none",
"skip=2",
"ibs=128",
])
.args(&["status=none", "skip=2", "ibs=128"])
.pipe_in(input)
.run()
.no_stderr()
@ -179,18 +141,12 @@ fn test_stdin_stdout_skip()
}
#[test]
fn test_stdin_stdout_skip_bytes()
{
fn test_stdin_stdout_skip_bytes() {
let input = build_ascii_block(521);
let mut output = String::from_utf8(input.clone()).unwrap();
let _ = output.drain(..256);
new_ucmd!()
.args(&[
"status=none",
"skip=256",
"ibs=128",
"iflag=skip_bytes",
])
.args(&["status=none", "skip=256", "ibs=128", "iflag=skip_bytes"])
.pipe_in(input)
.run()
.no_stderr()
@ -198,16 +154,11 @@ fn test_stdin_stdout_skip_bytes()
}
#[test]
fn test_stdin_stdout_skip_w_multiplier()
{
let input = build_ascii_block(10*1024);
let output = String::from_utf8(input[5*1024..].to_vec()).unwrap();
fn test_stdin_stdout_skip_w_multiplier() {
let input = build_ascii_block(10 * 1024);
let output = String::from_utf8(input[5 * 1024..].to_vec()).unwrap();
new_ucmd!()
.args(&[
"status=none",
"skip=5K",
"iflag=skip_bytes"
])
.args(&["status=none", "skip=5K", "iflag=skip_bytes"])
.pipe_in(input)
.run()
.no_stderr()
@ -216,16 +167,11 @@ fn test_stdin_stdout_skip_w_multiplier()
}
#[test]
fn test_stdin_stdout_count_w_multiplier()
{
let input = build_ascii_block(5*1024);
let output = String::from_utf8(input[..2*1024].to_vec()).unwrap();
fn test_stdin_stdout_count_w_multiplier() {
let input = build_ascii_block(5 * 1024);
let output = String::from_utf8(input[..2 * 1024].to_vec()).unwrap();
new_ucmd!()
.args(&[
"status=none",
"count=2KiB",
"iflag=count_bytes",
])
.args(&["status=none", "count=2KiB", "iflag=count_bytes"])
.pipe_in(input)
.run()
.no_stderr()
@ -234,133 +180,93 @@ fn test_stdin_stdout_count_w_multiplier()
}
#[test]
fn test_final_stats_noxfer()
{
fn test_final_stats_noxfer() {
new_ucmd!()
.args(&[
"status=noxfer",
])
.args(&["status=noxfer"])
.succeeds()
.stderr_only("");
}
#[test]
fn test_final_stats_unspec()
{
fn test_final_stats_unspec() {
let output = vec![
"0+0 records in",
"0+0 records out",
"0 bytes (0 B, 0 B) copied, 0.0 s, 0 B/s",
];
let output = output.into_iter()
.fold(String::new(), | mut acc, s | {
acc.push_str(s);
acc.push('\n');
acc
});
new_ucmd!()
.run()
.stderr_only(&output)
.success();
let output = output.into_iter().fold(String::new(), |mut acc, s| {
acc.push_str(s);
acc.push('\n');
acc
});
new_ucmd!().run().stderr_only(&output).success();
}
#[test]
fn test_excl_causes_failure_when_present()
{
fn test_excl_causes_failure_when_present() {
let fname = "this-file-exists-excl.txt";
assert_fixture_exists!(&fname);
let (_fix, mut ucmd) = at_and_ucmd!();
ucmd.args(&[
"of=this-file-exists-excl.txt",
"conv=excl",
])
ucmd.args(&["of=this-file-exists-excl.txt", "conv=excl"])
.fails();
}
#[test]
fn test_atime_updated()
{
fn test_atime_updated() {
let fname = "this-file-exists-no-noatime.txt";
assert_fixture_exists!(&fname);
let (fix, mut ucmd) = at_and_ucmd!();
ucmd.args(&[
"status=none",
inf!(fname),
]);
ucmd.args(&["status=none", inf!(fname)]);
let pre_atime = fix.metadata(&fname).accessed().unwrap();
ucmd.pipe_in("")
.run()
.no_stderr()
.success();
ucmd.pipe_in("").run().no_stderr().success();
let post_atime = fix.metadata(&fname).accessed().unwrap();
assert!(pre_atime != post_atime);
}
#[test]
fn test_noatime_does_not_update_infile_atime()
{
fn test_noatime_does_not_update_infile_atime() {
let fname = "this-ifile-exists-noatime.txt";
assert_fixture_exists!(&fname);
let (fix, mut ucmd) = at_and_ucmd!();
ucmd.args(&[
"status=none",
"iflag=noatime",
inf!(fname),
]);
ucmd.args(&["status=none", "iflag=noatime", inf!(fname)]);
let pre_atime = fix.metadata(&fname).accessed().unwrap();
ucmd.run()
.no_stderr()
.success();
ucmd.run().no_stderr().success();
let post_atime = fix.metadata(&fname).accessed().unwrap();
assert_eq!(pre_atime, post_atime);
}
#[test]
fn test_noatime_does_not_update_ofile_atime()
{
fn test_noatime_does_not_update_ofile_atime() {
let fname = "this-ofile-exists-noatime.txt";
assert_fixture_exists!(&fname);
let (fix, mut ucmd) = at_and_ucmd!();
ucmd.args(&[
"status=none",
"oflag=noatime",
of!(fname),
]);
ucmd.args(&["status=none", "oflag=noatime", of!(fname)]);
let pre_atime = fix.metadata(&fname).accessed().unwrap();
ucmd.pipe_in("")
.run()
.no_stderr()
.success();
ucmd.pipe_in("").run().no_stderr().success();
let post_atime = fix.metadata(&fname).accessed().unwrap();
assert_eq!(pre_atime, post_atime);
}
#[test]
fn test_nocreat_causes_failure_when_outfile_not_present()
{
fn test_nocreat_causes_failure_when_outfile_not_present() {
let fname = "this-file-does-not-exist.txt";
assert_fixture_not_exists!(&fname);
let (fix, mut ucmd) = at_and_ucmd!();
ucmd.args(&[
"conv=nocreat",
of!(&fname),
])
.pipe_in("")
.run();
ucmd.args(&["conv=nocreat", of!(&fname)]).pipe_in("").run();
assert!(!fix.file_exists(&fname));
@ -368,25 +274,17 @@ fn test_nocreat_causes_failure_when_outfile_not_present()
}
#[test]
fn test_notrunc_does_not_truncate()
{
fn test_notrunc_does_not_truncate() {
// Set up test if needed (eg. after failure)
let fname = "this-file-exists-notrunc.txt";
let fpath = fixture_path!(fname);
match fpath.metadata()
{
Ok(m) if m.len() == 256 => {},
_ =>
build_test_file!(&fpath, &build_ascii_block(256)),
match fpath.metadata() {
Ok(m) if m.len() == 256 => {}
_ => build_test_file!(&fpath, &build_ascii_block(256)),
}
let (fix, mut ucmd) = at_and_ucmd!();
ucmd.args(&[
"status=none",
"conv=notrunc",
of!(&fname),
"if=null.txt",
])
ucmd.args(&["status=none", "conv=notrunc", of!(&fname), "if=null.txt"])
.run()
.no_stdout()
.no_stderr()
@ -396,24 +294,17 @@ fn test_notrunc_does_not_truncate()
}
#[test]
fn test_existing_file_truncated()
{
fn test_existing_file_truncated() {
// Set up test if needed (eg. after failure)
let fname = "this-file-exists-truncated.txt";
let fpath = fixture_path!(fname);
match fpath.metadata()
{
Ok(m) if m.len() == 256 => {},
_ =>
build_test_file!(&fpath, &vec![0; 256]),
match fpath.metadata() {
Ok(m) if m.len() == 256 => {}
_ => build_test_file!(&fpath, &vec![0; 256]),
}
let (fix, mut ucmd) = at_and_ucmd!();
ucmd.args(&[
"status=none",
"if=null.txt",
of!(fname),
])
ucmd.args(&["status=none", "if=null.txt", of!(fname)])
.run()
.no_stdout()
.no_stderr()
@ -423,37 +314,28 @@ fn test_existing_file_truncated()
}
#[test]
fn test_null_stats()
{
fn test_null_stats() {
let stats = vec![
"0+0 records in\n",
"0+0 records out\n",
"0 bytes (0 B, 0 B) copied, 0.0 s, 0 B/s\n",
];
let stats = stats.into_iter()
.fold(String::new(), | mut acc, s | {
acc.push_str(s);
acc
});
let stats = stats.into_iter().fold(String::new(), |mut acc, s| {
acc.push_str(s);
acc
});
new_ucmd!()
.args(&[
"if=null.txt",
])
.args(&["if=null.txt"])
.run()
.stderr_only(stats)
.success();
}
#[test]
fn test_null_fullblock()
{
fn test_null_fullblock() {
new_ucmd!()
.args(&[
"if=null.txt",
"status=none",
"iflag=fullblock",
])
.args(&["if=null.txt", "status=none", "iflag=fullblock"])
.run()
.no_stdout()
.no_stderr()
@ -463,8 +345,7 @@ fn test_null_fullblock()
#[cfg(unix)]
// #[ignore] // See note below before running this test!
#[test]
fn test_fullblock()
{
fn test_fullblock() {
let tname = "fullblock-from-urand";
let tmp_fn = format!("TESTFILE-{}.tmp", &tname);
let exp_stats = vec![
@ -472,11 +353,10 @@ fn test_fullblock()
"1+0 records out\n",
"134217728 bytes (134 MB, 128 MiB) copied,",
];
let exp_stats = exp_stats.into_iter()
.fold(Vec::new(), | mut acc, s | {
acc.extend(s.bytes());
acc
});
let exp_stats = exp_stats.into_iter().fold(Vec::new(), |mut acc, s| {
acc.extend(s.bytes());
acc
});
let ucmd = new_ucmd!()
.args(&[
@ -491,7 +371,8 @@ fn test_fullblock()
// a reasonable value for testing most systems.
"count=1",
"iflag=fullblock",
]).run();
])
.run();
ucmd.success();
let run_stats = &ucmd.stderr()[..exp_stats.len()];
@ -500,20 +381,12 @@ fn test_fullblock()
// Fileio
#[test]
fn test_ys_to_stdout()
{
let output: Vec<_> = String::from("y\n")
.bytes()
.cycle()
.take(1024)
.collect();
fn test_ys_to_stdout() {
let output: Vec<_> = String::from("y\n").bytes().cycle().take(1024).collect();
let output = String::from_utf8(output).unwrap();
new_ucmd!()
.args(&[
"status=none",
"if=y-nl-1k.txt",
])
.args(&["status=none", "if=y-nl-1k.txt"])
.run()
.no_stderr()
.stdout_is(output)
@ -521,15 +394,11 @@ fn test_ys_to_stdout()
}
#[test]
fn test_zeros_to_stdout()
{
let output = vec![0; 256*1024];
fn test_zeros_to_stdout() {
let output = vec![0; 256 * 1024];
let output = String::from_utf8(output).unwrap();
new_ucmd!()
.args(&[
"status=none",
"if=zero-256k.txt",
])
.args(&["status=none", "if=zero-256k.txt"])
.run()
.no_stderr()
.stdout_is(output)
@ -537,22 +406,12 @@ fn test_zeros_to_stdout()
}
#[test]
fn test_to_stdout_with_ibs_obs()
{
let output: Vec<_> = String::from("y\n")
.bytes()
.cycle()
.take(1024)
.collect();
fn test_to_stdout_with_ibs_obs() {
let output: Vec<_> = String::from("y\n").bytes().cycle().take(1024).collect();
let output = String::from_utf8(output).unwrap();
new_ucmd!()
.args(&[
"status=none",
"if=y-nl-1k.txt",
"ibs=521",
"obs=1031",
])
.args(&["status=none", "if=y-nl-1k.txt", "ibs=521", "obs=1031"])
.run()
.no_stderr()
.stdout_is(output)
@ -560,17 +419,13 @@ fn test_to_stdout_with_ibs_obs()
}
#[test]
fn test_ascii_10k_to_stdout()
{
let output = build_ascii_block(1024*1024);
fn test_ascii_10k_to_stdout() {
let output = build_ascii_block(1024 * 1024);
// build_test_file!("ascii-10k.txt", &output);
let output = String::from_utf8(output).unwrap();
new_ucmd!()
.args(&[
"status=none",
"if=ascii-10k.txt",
])
.args(&["status=none", "if=ascii-10k.txt"])
.run()
.no_stderr()
.stdout_is(output)
@ -578,30 +433,26 @@ fn test_ascii_10k_to_stdout()
}
#[test]
fn test_zeros_to_file()
{
fn test_zeros_to_file() {
let tname = "zero-256k";
let test_fn = format!("{}.txt", tname);
let tmp_fn = format!("TESTFILE-{}.tmp", &tname);
let (fix, mut ucmd) = at_and_ucmd!();
ucmd.args(&[
"status=none",
inf!(test_fn),
of!(tmp_fn),
])
.run()
.no_stderr()
.no_stdout()
.success();
ucmd.args(&["status=none", inf!(test_fn), of!(tmp_fn)])
.run()
.no_stderr()
.no_stdout()
.success();
cmp_file!(File::open(fixture_path!(&test_fn)).unwrap(),
fix.open(&tmp_fn));
cmp_file!(
File::open(fixture_path!(&test_fn)).unwrap(),
fix.open(&tmp_fn)
);
}
#[test]
fn test_to_file_with_ibs_obs()
{
fn test_to_file_with_ibs_obs() {
let tname = "zero-256k";
let test_fn = format!("{}.txt", tname);
let tmp_fn = format!("TESTFILE-{}.tmp", &tname);
@ -614,46 +465,47 @@ fn test_to_file_with_ibs_obs()
"ibs=222",
"obs=111",
])
.run()
.no_stderr()
.no_stdout()
.success();
.run()
.no_stderr()
.no_stdout()
.success();
cmp_file!(File::open(fixture_path!(&test_fn)).unwrap(),
fix.open(&tmp_fn));
cmp_file!(
File::open(fixture_path!(&test_fn)).unwrap(),
fix.open(&tmp_fn)
);
}
#[test]
fn test_ascii_521k_to_file()
{
fn test_ascii_521k_to_file() {
let tname = "ascii-521k";
let input = build_ascii_block(512*1024);
let input = build_ascii_block(512 * 1024);
let tmp_fn = format!("TESTFILE-{}.tmp", &tname);
let (fix, mut ucmd) = at_and_ucmd!();
ucmd.args(&[
"status=none",
of!(tmp_fn),
])
ucmd.args(&["status=none", of!(tmp_fn)])
.pipe_in(input.clone())
.run()
.no_stderr()
.no_stdout()
.success();
assert_eq!(512*1024, fix.metadata(&tmp_fn).len());
assert_eq!(512 * 1024, fix.metadata(&tmp_fn).len());
cmp_file!({ let mut input_f = tempfile().unwrap();
input_f.write(&input).unwrap();
input_f },
fix.open(&tmp_fn));
cmp_file!(
{
let mut input_f = tempfile().unwrap();
input_f.write(&input).unwrap();
input_f
},
fix.open(&tmp_fn)
);
}
#[ignore]
#[cfg(unix)]
#[test]
fn test_ascii_5_gibi_to_file()
{
fn test_ascii_5_gibi_to_file() {
let tname = "ascii-5G";
let tmp_fn = format!("TESTFILE-{}.tmp", &tname);
@ -665,37 +517,28 @@ fn test_ascii_5_gibi_to_file()
"if=/dev/zero",
of!(tmp_fn),
])
.run()
.no_stderr()
.no_stdout()
.success();
.run()
.no_stderr()
.no_stdout()
.success();
assert_eq!(5*1024*1024*1024, fix.metadata(&tmp_fn).len());
assert_eq!(5 * 1024 * 1024 * 1024, fix.metadata(&tmp_fn).len());
}
#[test]
fn test_self_transfer()
{
fn test_self_transfer() {
let fname = "self-transfer-256k.txt";
let (fix, mut ucmd) = at_and_ucmd!();
ucmd.args(&[
"status=none",
"conv=notrunc",
inf!(fname),
of!(fname),
]);
ucmd.args(&["status=none", "conv=notrunc", inf!(fname), of!(fname)]);
assert!(fix.file_exists(fname));
assert_eq!(256*1024, fix.metadata(fname).len());
assert_eq!(256 * 1024, fix.metadata(fname).len());
ucmd.run()
.no_stdout()
.no_stderr()
.success();
ucmd.run().no_stdout().no_stderr().success();
assert!(fix.file_exists(fname));
assert_eq!(256*1024, fix.metadata(fname).len());
assert_eq!(256 * 1024, fix.metadata(fname).len());
}
// conv=[ascii,ebcdic,ibm], conv=[ucase,lcase], conv=[block,unblock], conv=sync