mirror of
https://github.com/uutils/coreutils
synced 2024-11-15 01:17:09 +00:00
MV implementation
Support for all commandline options except one, see src/mv/mv.rs Many tests, included a todo list of more tests too, see tests/mv.rs
This commit is contained in:
parent
1f37abcc9c
commit
beba02757e
2 changed files with 728 additions and 66 deletions
362
src/mv/mv.rs
362
src/mv/mv.rs
|
@ -3,87 +3,347 @@
|
|||
/*
|
||||
* This file is part of the uutils coreutils package.
|
||||
*
|
||||
* (c) Orvar Segerström <orvarsegerstrom@gmail.com>
|
||||
* (c) Sokovikov Evgeniy <skv-headless@yandex.ru>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE file
|
||||
* that was distributed with this source code.
|
||||
*/
|
||||
|
||||
#![feature(macro_rules)]
|
||||
#![feature(macro_rules, if_let)]
|
||||
|
||||
extern crate getopts;
|
||||
|
||||
use std::io::fs;
|
||||
|
||||
use std::io::{BufferedReader, IoResult, fs};
|
||||
use std::io::stdio::stdin_raw;
|
||||
use std::io::fs::PathExtensions;
|
||||
use std::path::GenericPath;
|
||||
use getopts::{
|
||||
getopts,
|
||||
optflag,
|
||||
optflagopt,
|
||||
optopt,
|
||||
usage,
|
||||
};
|
||||
|
||||
static NAME: &'static str = "mv";
|
||||
static VERSION: &'static str = "0.0.1";
|
||||
|
||||
#[path = "../common/util.rs"]
|
||||
mod util;
|
||||
|
||||
pub fn uumain(args: Vec<String>) -> int {
|
||||
let opts = [
|
||||
optflag("h", "help", "display this help and exit"),
|
||||
optflag("V", "version", "output version information and exit"),
|
||||
];
|
||||
let matches = match getopts(args.tail(), opts) {
|
||||
Ok(m) => m,
|
||||
Err(f) => crash!(1, "Invalid options\n{}", f)
|
||||
};
|
||||
static NAME: &'static str = "mv";
|
||||
static VERSION: &'static str = "0.0.1";
|
||||
|
||||
let progname = &args[0];
|
||||
let usage = usage("Move SOURCE to DEST", opts);
|
||||
|
||||
if matches.opt_present("version") {
|
||||
println!("{}", VERSION);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if matches.opt_present("help") {
|
||||
help(progname.as_slice(), usage.as_slice());
|
||||
return 0;
|
||||
}
|
||||
|
||||
let source = if matches.free.len() < 1 {
|
||||
println!("error: Missing SOURCE argument. Try --help.");
|
||||
return 1;
|
||||
} else {
|
||||
Path::new(matches.free[0].as_slice())
|
||||
};
|
||||
|
||||
let dest = if matches.free.len() < 2 {
|
||||
println!("error: Missing DEST argument. Try --help.");
|
||||
return 1;
|
||||
} else {
|
||||
Path::new(matches.free[1].as_slice())
|
||||
};
|
||||
|
||||
mv(source, dest)
|
||||
pub struct Behaviour {
|
||||
overwrite: OverwriteMode,
|
||||
backup: BackupMode,
|
||||
suffix: String,
|
||||
update: bool,
|
||||
target_dir: Option<String>,
|
||||
no_target_dir: bool,
|
||||
verbose: bool,
|
||||
}
|
||||
|
||||
fn mv(source: Path, dest: Path) -> int {
|
||||
let io_result = fs::rename(&source, &dest);
|
||||
#[deriving(Eq, PartialEq)]
|
||||
pub enum OverwriteMode {
|
||||
NoClobber,
|
||||
Interactive,
|
||||
Force,
|
||||
}
|
||||
|
||||
if io_result.is_err() {
|
||||
let err = io_result.unwrap_err();
|
||||
println!("error: {:s}", err.to_string());
|
||||
1
|
||||
#[deriving(Eq, PartialEq)]
|
||||
pub enum BackupMode {
|
||||
NoBackup,
|
||||
SimpleBackup,
|
||||
NumberedBackup,
|
||||
ExistingBackup,
|
||||
}
|
||||
|
||||
pub fn uumain(args: Vec<String>) -> int {
|
||||
let program = args[0].as_slice();
|
||||
let opts = [
|
||||
optflagopt("", "backup", "make a backup of each existing destination file", "CONTROL"),
|
||||
optflag("b", "", "like --backup but does not accept an argument"),
|
||||
optflag("f", "force", "do not prompt before overwriting"),
|
||||
optflag("i", "interactive", "prompt before override"),
|
||||
optflag("n", "no-clobber", "do not overwrite an existing file"),
|
||||
// I have yet to find a use-case (and thereby write a test) where this option is useful.
|
||||
//optflag("", "strip-trailing-slashes", "remove any trailing slashes from each SOURCE\n \
|
||||
// argument"),
|
||||
optopt("S", "suffix", "override the usual backup suffix", "SUFFIX"),
|
||||
optopt("t", "target-directory", "move all SOURCE arguments into DIRECTORY", "DIRECTORY"),
|
||||
optflag("T", "no-target-directory", "treat DEST as a normal file"),
|
||||
optflag("u", "update", "move only when the SOURCE file is newer\n \
|
||||
than the destination file or when the\n \
|
||||
destination file is missing"),
|
||||
optflag("v", "verbose", "explain what is being done"),
|
||||
optflag("h", "help", "display this help and exit"),
|
||||
optflag("V", "version", "output version information and exit"),
|
||||
];
|
||||
|
||||
let matches = match getopts(args.tail(), opts) {
|
||||
Ok(m) => m,
|
||||
Err(f) => {
|
||||
show_error!("Invalid options\n{}", f);
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
let usage = usage("Move SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY.", opts);
|
||||
|
||||
/* This does not exactly match the GNU implementation:
|
||||
* The GNU mv defaults to Force, but if more than one of the
|
||||
* overwrite options are supplied, only the last takes effect.
|
||||
* To default to no-clobber in that situation seems safer:
|
||||
*/
|
||||
let overwrite_mode = if matches.opt_present("no-clobber") {
|
||||
NoClobber
|
||||
} else if matches.opt_present("interactive") {
|
||||
Interactive
|
||||
} else {
|
||||
0
|
||||
Force
|
||||
};
|
||||
|
||||
let backup_mode = if matches.opt_present("b") {
|
||||
SimpleBackup
|
||||
} else if matches.opt_present("backup") {
|
||||
match matches.opt_str("backup") {
|
||||
None => SimpleBackup,
|
||||
Some(mode) => match mode.as_slice() {
|
||||
"simple" | "never" => SimpleBackup,
|
||||
"numbered" | "t" => NumberedBackup,
|
||||
"existing" | "nil" => ExistingBackup,
|
||||
"none" | "off" => NoBackup,
|
||||
x => {
|
||||
show_error!("invalid argument ‘{}’ for ‘backup type’\n\
|
||||
Try 'mv --help' for more information.", x);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
NoBackup
|
||||
};
|
||||
|
||||
if overwrite_mode == NoClobber && backup_mode != NoBackup {
|
||||
show_error!("options --backup and --no-clobber are mutually exclusive\n\
|
||||
Try 'mv --help' for more information.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
let backup_suffix = if matches.opt_present("suffix") {
|
||||
match matches.opt_str("suffix") {
|
||||
Some(x) => x,
|
||||
None => {
|
||||
show_error!("option '--suffix' requires an argument\n\
|
||||
Try 'mv --help' for more information.");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
"~".into_string()
|
||||
};
|
||||
|
||||
if matches.opt_present("T") && matches.opt_present("t") {
|
||||
show_error!("cannot combine --target-directory (-t) and --no-target-directory (-T)");
|
||||
return 1;
|
||||
}
|
||||
|
||||
let behaviour = Behaviour {
|
||||
overwrite: overwrite_mode,
|
||||
backup: backup_mode,
|
||||
suffix: backup_suffix,
|
||||
update: matches.opt_present("u"),
|
||||
target_dir: matches.opt_str("t"),
|
||||
no_target_dir: matches.opt_present("T"),
|
||||
verbose: matches.opt_present("v"),
|
||||
};
|
||||
|
||||
let string_to_path = |s: &String| { Path::new(s.as_slice()) };
|
||||
let paths: Vec<Path> = matches.free.iter().map(string_to_path).collect();
|
||||
|
||||
if matches.opt_present("version") {
|
||||
version();
|
||||
0
|
||||
} else if matches.opt_present("help") {
|
||||
help(program.as_slice(), usage.as_slice());
|
||||
0
|
||||
} else {
|
||||
exec(paths.as_slice(), behaviour)
|
||||
}
|
||||
}
|
||||
|
||||
fn version() {
|
||||
println!("{} {}", NAME, VERSION);
|
||||
}
|
||||
|
||||
fn help(progname: &str, usage: &str) {
|
||||
let msg = format!("Usage: {0} SOURCE DEST\n \
|
||||
or: {0} SOURCE... DIRECTORY\n \
|
||||
or: {0} SOURCE... DIRECTORY \
|
||||
\n\
|
||||
{1}", progname, usage);
|
||||
println!("{}", msg);
|
||||
}
|
||||
|
||||
fn exec(files: &[Path], b: Behaviour) -> int {
|
||||
match b.target_dir {
|
||||
Some(ref name) => return move_files_into_dir(files, &Path::new(name.as_slice()), &b),
|
||||
None => {}
|
||||
}
|
||||
match files {
|
||||
[] | [_] => {
|
||||
show_error!("missing file operand\n\
|
||||
Try 'mv --help' for more information.");
|
||||
return 1;
|
||||
},
|
||||
[ref source, ref target] => {
|
||||
if !source.exists() {
|
||||
show_error!("cannot stat ‘{}’: No such file or directory", source.display());
|
||||
return 1;
|
||||
}
|
||||
|
||||
if target.is_dir() {
|
||||
if b.no_target_dir {
|
||||
if !source.is_dir() {
|
||||
show_error!("cannot overwrite directory ‘{}’ with non-directory",
|
||||
target.display());
|
||||
return 1;
|
||||
}
|
||||
|
||||
return match rename(source, target, &b) {
|
||||
Err(e) => {
|
||||
show_error!("{}", e);
|
||||
1
|
||||
},
|
||||
_ => 0
|
||||
}
|
||||
}
|
||||
|
||||
return move_files_into_dir(&[source.clone()], target, &b);
|
||||
}
|
||||
|
||||
match rename(source, target, &b) {
|
||||
Err(e) => {
|
||||
show_error!("{}", e);
|
||||
return 1;
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
fs => {
|
||||
if b.no_target_dir {
|
||||
show_error!("mv: extra operand ‘{}’\n\
|
||||
Try 'mv --help' for more information.", fs[2].display());
|
||||
return 1;
|
||||
}
|
||||
let target_dir = fs.last().unwrap();
|
||||
move_files_into_dir(fs.init(), target_dir, &b);
|
||||
}
|
||||
}
|
||||
0
|
||||
}
|
||||
|
||||
fn move_files_into_dir(files: &[Path], target_dir: &Path, b: &Behaviour) -> int {
|
||||
if !target_dir.is_dir() {
|
||||
show_error!("target ‘{}’ is not a directory", target_dir.display());
|
||||
return 1;
|
||||
}
|
||||
|
||||
let mut all_successful = true;
|
||||
for sourcepath in files.iter() {
|
||||
let targetpath = match sourcepath.filename_str() {
|
||||
Some(name) => target_dir.join(name),
|
||||
None => {
|
||||
show_error!("cannot stat ‘{}’: No such file or directory",
|
||||
sourcepath.display());
|
||||
|
||||
all_successful = false;
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
match rename(sourcepath, &targetpath, b) {
|
||||
Err(e) => {
|
||||
show_error!("mv: cannot move ‘{}’ to ‘{}’: {}",
|
||||
sourcepath.display(), targetpath.display(), e);
|
||||
all_successful = false;
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
};
|
||||
if all_successful { 0 } else { 1 }
|
||||
}
|
||||
|
||||
fn rename(from: &Path, to: &Path, b: &Behaviour) -> IoResult<()> {
|
||||
let mut backup_path = None;
|
||||
|
||||
if to.is_file() {
|
||||
match b.overwrite {
|
||||
NoClobber => return Ok(()),
|
||||
Interactive => {
|
||||
print!("{}: overwrite ‘{}’? ", NAME, to.display());
|
||||
if !read_yes() {
|
||||
return Ok(());
|
||||
}
|
||||
},
|
||||
Force => {}
|
||||
};
|
||||
|
||||
backup_path = match b.backup {
|
||||
NoBackup => None,
|
||||
SimpleBackup => Some(simple_backup_path(to, &b.suffix)),
|
||||
NumberedBackup => Some(numbered_backup_path(to)),
|
||||
ExistingBackup => Some(existing_backup_path(to, &b.suffix))
|
||||
};
|
||||
if let Some(ref p) = backup_path {
|
||||
try!(fs::rename(to, p));
|
||||
}
|
||||
|
||||
if b.update {
|
||||
if try!(from.stat()).modified <= try!(to.stat()).modified {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if b.verbose {
|
||||
print!("‘{}’ -> ‘{}’", from.display(), to.display());
|
||||
match backup_path {
|
||||
Some(path) => println!(" (backup: ‘{}’)", path.display()),
|
||||
None => println!("")
|
||||
}
|
||||
}
|
||||
fs::rename(from, to)
|
||||
}
|
||||
|
||||
fn read_yes() -> bool {
|
||||
match BufferedReader::new(stdin_raw()).read_line() {
|
||||
Ok(s) => match s.as_slice().slice_shift_char() {
|
||||
(Some(x), _) => x == 'y' || x == 'Y',
|
||||
_ => false
|
||||
},
|
||||
_ => false
|
||||
}
|
||||
}
|
||||
|
||||
fn simple_backup_path(path: &Path, suffix: &String) -> Path {
|
||||
let mut p = path.clone().into_vec();
|
||||
p.push_all(suffix.as_slice().as_bytes());
|
||||
return Path::new(p);
|
||||
}
|
||||
|
||||
fn numbered_backup_path(path: &Path) -> Path {
|
||||
let mut i: u64 = 1;
|
||||
loop {
|
||||
let new_path = simple_backup_path(path, &format!(".~{}~", i));
|
||||
if !new_path.exists() {
|
||||
return new_path;
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn existing_backup_path(path: &Path, suffix: &String) -> Path {
|
||||
let test_path = simple_backup_path(path, &".~1~".into_string());
|
||||
if test_path.exists() {
|
||||
return numbered_backup_path(path);
|
||||
}
|
||||
return simple_backup_path(path, suffix);
|
||||
}
|
||||
|
|
432
test/mv.rs
432
test/mv.rs
|
@ -1,24 +1,426 @@
|
|||
#![feature(macro_rules)]
|
||||
|
||||
extern crate time;
|
||||
|
||||
use std::io::{process, fs, FilePermission};
|
||||
use std::io::process::Command;
|
||||
use std::io::fs::{PathExtensions};
|
||||
use std::io::fs::PathExtensions;
|
||||
use std::io::pipe::PipeStream;
|
||||
use std::str::from_utf8;
|
||||
|
||||
static EXE: &'static str = "./mv";
|
||||
static TEST_HELLO_WORLD_SOURCE: &'static str = "hello_world.txt";
|
||||
static TEST_HELLO_WORLD_DEST: &'static str = "move_of_hello_world.txt";
|
||||
|
||||
#[test]
|
||||
fn test_mv() {
|
||||
let prog = Command::new(EXE)
|
||||
.arg(TEST_HELLO_WORLD_SOURCE)
|
||||
.arg(TEST_HELLO_WORLD_DEST)
|
||||
.status();
|
||||
|
||||
let exit_success = prog.unwrap().success();
|
||||
assert_eq!(exit_success, true);
|
||||
macro_rules! assert_empty_stderr(
|
||||
($cond:expr) => (
|
||||
if $cond.stderr.len() > 0 {
|
||||
fail!(format!("stderr: {}", $cond.stderr))
|
||||
}
|
||||
);
|
||||
)
|
||||
struct CmdResult {
|
||||
success: bool,
|
||||
stderr: String,
|
||||
stdout: String,
|
||||
}
|
||||
fn run(cmd: &mut Command) -> CmdResult {
|
||||
let prog = cmd.spawn().unwrap().wait_with_output().unwrap();
|
||||
CmdResult {
|
||||
success: prog.status.success(),
|
||||
stderr: from_utf8(prog.error.as_slice()).unwrap().into_string(),
|
||||
stdout: from_utf8(prog.output.as_slice()).unwrap().into_string(),
|
||||
}
|
||||
}
|
||||
fn run_interactive(cmd: &mut Command, f: |&mut PipeStream|) -> CmdResult {
|
||||
let stdin_cfg = process::CreatePipe(true, false);
|
||||
let mut command = cmd.stdin(stdin_cfg).spawn().unwrap();
|
||||
|
||||
let dest = Path::new(TEST_HELLO_WORLD_DEST);
|
||||
assert!(dest.exists() == true);
|
||||
f(command.stdin.as_mut().unwrap());
|
||||
|
||||
let source = Path::new(TEST_HELLO_WORLD_SOURCE);
|
||||
assert!(source.exists() == false);
|
||||
let prog = command.wait_with_output().unwrap();
|
||||
CmdResult {
|
||||
success: prog.status.success(),
|
||||
stderr: from_utf8(prog.error.as_slice()).unwrap().into_string(),
|
||||
stdout: from_utf8(prog.output.as_slice()).unwrap().into_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn mkdir(dir: &str) {
|
||||
fs::mkdir(&Path::new(dir), FilePermission::from_bits_truncate(0o755 as u32)).unwrap();
|
||||
}
|
||||
fn touch(file: &str) {
|
||||
fs::File::create(&Path::new(file)).unwrap();
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn test_mv_rename_dir() {
|
||||
let dir1 = "test_mv_rename_dir";
|
||||
let dir2 = "test_mv_rename_dir2";
|
||||
|
||||
mkdir(dir1);
|
||||
|
||||
let result = run(Command::new(EXE).arg(dir1).arg(dir2));
|
||||
assert_empty_stderr!(result);
|
||||
assert!(result.success);
|
||||
|
||||
assert!(Path::new(dir2).is_dir());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mv_rename_file() {
|
||||
let file1 = "test_mv_rename_file";
|
||||
let file2 = "test_mv_rename_file2";
|
||||
|
||||
touch(file1);
|
||||
|
||||
let result = run(Command::new(EXE).arg(file1).arg(file2));
|
||||
assert_empty_stderr!(result);
|
||||
assert!(result.success);
|
||||
|
||||
assert!(Path::new(file2).is_file());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mv_move_file_into_dir() {
|
||||
let dir = "test_mv_move_file_into_dir_dir";
|
||||
let file = "test_mv_move_file_into_dir_file";
|
||||
|
||||
mkdir(dir);
|
||||
touch(file);
|
||||
|
||||
let result = run(Command::new(EXE).arg(file).arg(dir));
|
||||
assert_empty_stderr!(result);
|
||||
assert!(result.success);
|
||||
|
||||
assert!(Path::new(format!("{}/{}", dir, file)).is_file());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mv_multiple_files() {
|
||||
let target_dir = "test_mv_multiple_files_dir";
|
||||
let file_a = "test_mv_multiple_file_a";
|
||||
let file_b = "test_mv_multiple_file_b";
|
||||
|
||||
mkdir(target_dir);
|
||||
touch(file_a);
|
||||
touch(file_b);
|
||||
|
||||
let result = run(Command::new(EXE).arg(file_a).arg(file_b).arg(target_dir));
|
||||
assert_empty_stderr!(result);
|
||||
assert!(result.success);
|
||||
|
||||
assert!(Path::new(format!("{}/{}", target_dir, file_a)).is_file());
|
||||
assert!(Path::new(format!("{}/{}", target_dir, file_b)).is_file());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mv_multiple_folders() {
|
||||
let target_dir = "test_mv_multiple_dirs_dir";
|
||||
let dir_a = "test_mv_multiple_dir_a";
|
||||
let dir_b = "test_mv_multiple_dir_b";
|
||||
|
||||
mkdir(target_dir);
|
||||
mkdir(dir_a);
|
||||
mkdir(dir_b);
|
||||
|
||||
let result = run(Command::new(EXE).arg(dir_a).arg(dir_b).arg(target_dir));
|
||||
assert_empty_stderr!(result);
|
||||
assert!(result.success);
|
||||
|
||||
assert!(Path::new(format!("{}/{}", target_dir, dir_a)).is_dir());
|
||||
assert!(Path::new(format!("{}/{}", target_dir, dir_b)).is_dir());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mv_interactive() {
|
||||
let file_a = "test_mv_interactive_file_a";
|
||||
let file_b = "test_mv_interactive_file_b";
|
||||
|
||||
touch(file_a);
|
||||
touch(file_b);
|
||||
|
||||
|
||||
let ia1 = |stdin: &mut PipeStream| {
|
||||
stdin.write(b"n").unwrap();
|
||||
};
|
||||
let result1 = run_interactive(Command::new(EXE).arg("-i").arg(file_a).arg(file_b), ia1);
|
||||
|
||||
assert_empty_stderr!(result1);
|
||||
assert!(result1.success);
|
||||
|
||||
assert!(Path::new(file_a).is_file());
|
||||
assert!(Path::new(file_b).is_file());
|
||||
|
||||
|
||||
let ia2 = |stdin: &mut PipeStream| {
|
||||
stdin.write(b"Yesh").unwrap();
|
||||
};
|
||||
let result2 = run_interactive(Command::new(EXE).arg("-i").arg(file_a).arg(file_b), ia2);
|
||||
|
||||
assert_empty_stderr!(result2);
|
||||
assert!(result2.success);
|
||||
|
||||
assert!(!Path::new(file_a).is_file());
|
||||
assert!(Path::new(file_b).is_file());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mv_no_clobber() {
|
||||
let file_a = "test_mv_no_clobber_file_a";
|
||||
let file_b = "test_mv_no_clobber_file_b";
|
||||
|
||||
touch(file_a);
|
||||
touch(file_b);
|
||||
|
||||
let result = run(Command::new(EXE).arg("-n").arg(file_a).arg(file_b));
|
||||
assert_empty_stderr!(result);
|
||||
assert!(result.success);
|
||||
|
||||
assert!(Path::new(file_a).is_file());
|
||||
assert!(Path::new(file_b).is_file());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mv_replace_file() {
|
||||
let file_a = "test_mv_replace_file_a";
|
||||
let file_b = "test_mv_replace_file_b";
|
||||
|
||||
touch(file_a);
|
||||
touch(file_b);
|
||||
|
||||
let result = run(Command::new(EXE).arg(file_a).arg(file_b));
|
||||
assert_empty_stderr!(result);
|
||||
assert!(result.success);
|
||||
|
||||
assert!(!Path::new(file_a).is_file());
|
||||
assert!(Path::new(file_b).is_file());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mv_force_replace_file() {
|
||||
let file_a = "test_mv_force_replace_file_a";
|
||||
let file_b = "test_mv_force_replace_file_b";
|
||||
|
||||
touch(file_a);
|
||||
touch(file_b);
|
||||
|
||||
let result = run(Command::new(EXE).arg("--force").arg(file_a).arg(file_b));
|
||||
assert_empty_stderr!(result);
|
||||
assert!(result.success);
|
||||
|
||||
assert!(!Path::new(file_a).is_file());
|
||||
assert!(Path::new(file_b).is_file());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mv_simple_backup() {
|
||||
let file_a = "test_mv_simple_backup_file_a";
|
||||
let file_b = "test_mv_simple_backup_file_b";
|
||||
|
||||
touch(file_a);
|
||||
touch(file_b);
|
||||
let result = run(Command::new(EXE).arg("-b").arg(file_a).arg(file_b));
|
||||
|
||||
assert_empty_stderr!(result);
|
||||
assert!(result.success);
|
||||
|
||||
assert!(!Path::new(file_a).is_file());
|
||||
assert!(Path::new(file_b).is_file());
|
||||
assert!(Path::new(format!("{}~", file_b)).is_file());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mv_custom_backup_suffix() {
|
||||
let file_a = "test_mv_custom_backup_suffix_file_a";
|
||||
let file_b = "test_mv_custom_backup_suffix_file_b";
|
||||
let suffix = "super-suffix-of-the-century";
|
||||
|
||||
touch(file_a);
|
||||
touch(file_b);
|
||||
let result = run(Command::new(EXE)
|
||||
.arg("-b").arg(format!("--suffix={}", suffix))
|
||||
.arg(file_a).arg(file_b));
|
||||
|
||||
assert_empty_stderr!(result);
|
||||
assert!(result.success);
|
||||
|
||||
assert!(!Path::new(file_a).is_file());
|
||||
assert!(Path::new(file_b).is_file());
|
||||
assert!(Path::new(format!("{}{}", file_b, suffix)).is_file());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mv_backup_numbering() {
|
||||
let file_a = "test_mv_backup_numbering_file_a";
|
||||
let file_b = "test_mv_backup_numbering_file_b";
|
||||
|
||||
touch(file_a);
|
||||
touch(file_b);
|
||||
let result = run(Command::new(EXE).arg("--backup=t").arg(file_a).arg(file_b));
|
||||
|
||||
assert_empty_stderr!(result);
|
||||
assert!(result.success);
|
||||
|
||||
assert!(!Path::new(file_a).is_file());
|
||||
assert!(Path::new(file_b).is_file());
|
||||
assert!(Path::new(format!("{}.~1~", file_b)).is_file());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mv_existing_backup() {
|
||||
let file_a = "test_mv_existing_backup_file_a";
|
||||
let file_b = "test_mv_existing_backup_file_b";
|
||||
let file_b_backup = "test_mv_existing_backup_file_b.~1~";
|
||||
let resulting_backup = "test_mv_existing_backup_file_b.~2~";
|
||||
|
||||
touch(file_a);
|
||||
touch(file_b);
|
||||
touch(file_b_backup);
|
||||
let result = run(Command::new(EXE).arg("--backup=nil").arg(file_a).arg(file_b));
|
||||
|
||||
assert_empty_stderr!(result);
|
||||
assert!(result.success);
|
||||
|
||||
assert!(!Path::new(file_a).is_file());
|
||||
assert!(Path::new(file_b).is_file());
|
||||
assert!(Path::new(file_b_backup).is_file());
|
||||
assert!(Path::new(resulting_backup).is_file());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mv_update_option() {
|
||||
let file_a = "test_mv_update_option_file_a";
|
||||
let file_b = "test_mv_update_option_file_b";
|
||||
|
||||
touch(file_a);
|
||||
touch(file_b);
|
||||
let now = (time::get_time().sec * 1000) as u64;
|
||||
fs::change_file_times(&Path::new(file_a), now, now).unwrap();
|
||||
fs::change_file_times(&Path::new(file_b), now, now+3600).unwrap();
|
||||
|
||||
let result1 = run(Command::new(EXE).arg("--update").arg(file_a).arg(file_b));
|
||||
|
||||
assert_empty_stderr!(result1);
|
||||
assert!(result1.success);
|
||||
|
||||
assert!(Path::new(file_a).is_file());
|
||||
assert!(Path::new(file_b).is_file());
|
||||
|
||||
let result2 = run(Command::new(EXE).arg("--update").arg(file_b).arg(file_a));
|
||||
|
||||
assert_empty_stderr!(result2);
|
||||
assert!(result2.success);
|
||||
|
||||
assert!(Path::new(file_a).is_file());
|
||||
assert!(!Path::new(file_b).is_file());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mv_target_dir() {
|
||||
let dir = "test_mv_target_dir_dir";
|
||||
let file_a = "test_mv_target_dir_file_a";
|
||||
let file_b = "test_mv_target_dir_file_b";
|
||||
|
||||
touch(file_a);
|
||||
touch(file_b);
|
||||
mkdir(dir);
|
||||
let result = run(Command::new(EXE).arg("-t").arg(dir).arg(file_a).arg(file_b));
|
||||
|
||||
assert_empty_stderr!(result);
|
||||
assert!(result.success);
|
||||
|
||||
assert!(!Path::new(file_a).is_file());
|
||||
assert!(!Path::new(file_b).is_file());
|
||||
assert!(Path::new(format!("{}/{}", dir, file_a)).is_file());
|
||||
assert!(Path::new(format!("{}/{}", dir, file_b)).is_file());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mv_overwrite_dir() {
|
||||
let dir_a = "test_mv_overwrite_dir_a";
|
||||
let dir_b = "test_mv_overwrite_dir_b";
|
||||
|
||||
mkdir(dir_a);
|
||||
mkdir(dir_b);
|
||||
let result = run(Command::new(EXE).arg("-T").arg(dir_a).arg(dir_b));
|
||||
|
||||
assert_empty_stderr!(result);
|
||||
assert!(result.success);
|
||||
|
||||
assert!(!Path::new(dir_a).is_dir());
|
||||
assert!(Path::new(dir_b).is_dir());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mv_errors() {
|
||||
let dir = "test_mv_errors_dir";
|
||||
let file_a = "test_mv_errors_file_a";
|
||||
let file_b = "test_mv_errors_file_b";
|
||||
mkdir(dir);
|
||||
touch(file_a);
|
||||
touch(file_b);
|
||||
|
||||
// $ mv -T -t a b
|
||||
// mv: cannot combine --target-directory (-t) and --no-target-directory (-T)
|
||||
let result = run(Command::new(EXE).arg("-T").arg("-t").arg(dir).arg(file_a).arg(file_b));
|
||||
assert_eq!(result.stderr.as_slice(),
|
||||
"mv: error: cannot combine --target-directory (-t) and --no-target-directory (-T)\n");
|
||||
assert!(!result.success);
|
||||
|
||||
|
||||
// $ touch file && mkdir dir
|
||||
// $ mv -T file dir
|
||||
// err == mv: cannot overwrite directory ‘dir’ with non-directory
|
||||
let result = run(Command::new(EXE).arg("-T").arg(file_a).arg(dir));
|
||||
assert_eq!(result.stderr.as_slice(),
|
||||
format!("mv: error: cannot overwrite directory ‘{}’ with non-directory\n", dir).as_slice());
|
||||
assert!(!result.success);
|
||||
|
||||
// $ mkdir dir && touch file
|
||||
// $ mv dir file
|
||||
// err == mv: cannot overwrite non-directory ‘file’ with directory ‘dir’
|
||||
let result = run(Command::new(EXE).arg(dir).arg(file_a));
|
||||
assert!(result.stderr.len() > 0);
|
||||
assert!(!result.success);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mv_verbose() {
|
||||
let dir = "test_mv_verbose_dir";
|
||||
let file_a = "test_mv_verbose_file_a";
|
||||
let file_b = "test_mv_verbose_file_b";
|
||||
mkdir(dir);
|
||||
touch(file_a);
|
||||
touch(file_b);
|
||||
|
||||
let result = run(Command::new(EXE).arg("-v").arg(file_a).arg(file_b));
|
||||
assert_empty_stderr!(result);
|
||||
assert_eq!(result.stdout.as_slice(),
|
||||
format!("‘{}’ -> ‘{}’\n", file_a, file_b).as_slice());
|
||||
assert!(result.success);
|
||||
|
||||
|
||||
touch(file_a);
|
||||
let result = run(Command::new(EXE).arg("-vb").arg(file_a).arg(file_b));
|
||||
assert_empty_stderr!(result);
|
||||
assert_eq!(result.stdout.as_slice(),
|
||||
format!("‘{}’ -> ‘{}’ (backup: ‘{}~’)\n", file_a, file_b, file_b).as_slice());
|
||||
assert!(result.success);
|
||||
}
|
||||
|
||||
|
||||
// Todo:
|
||||
|
||||
// $ touch a b
|
||||
// $ chmod -w b
|
||||
// $ ll
|
||||
// total 0
|
||||
// -rw-rw-r-- 1 user user 0 okt 25 11:21 a
|
||||
// -r--r--r-- 1 user user 0 okt 25 11:21 b
|
||||
// $
|
||||
// $ mv -v a b
|
||||
// mv: try to overwrite ‘b’, overriding mode 0444 (r--r--r--)? y
|
||||
// ‘a’ -> ‘b’
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue