diff --git a/crates/nu-command/src/filesystem/cp.rs b/crates/nu-command/src/filesystem/cp.rs index 36099ed03..9995dcfad 100644 --- a/crates/nu-command/src/filesystem/cp.rs +++ b/crates/nu-command/src/filesystem/cp.rs @@ -1,4 +1,5 @@ use std::fs::read_link; +use std::io::{BufReader, BufWriter, ErrorKind, Read, Write}; use std::path::PathBuf; use nu_engine::env::current_dir; @@ -14,6 +15,7 @@ use nu_protocol::{ use super::util::try_interaction; use crate::filesystem::util::FileStructure; +use crate::progress_bar; const GLOB_PARAMS: nu_glob::MatchOptions = nu_glob::MatchOptions { case_sensitive: true, @@ -61,6 +63,7 @@ impl Command for Cp { "no symbolic links are followed, only works if -r is active", Some('n'), ) + .switch("progress", "enable progress bar", Some('p')) .category(Category::FileSystem) } @@ -82,6 +85,7 @@ impl Command for Cp { let recursive = call.has_flag("recursive"); let verbose = call.has_flag("verbose"); let interactive = call.has_flag("interactive"); + let progress = call.has_flag("progress"); let current_dir_path = current_dir(engine_state, stack)?; let source = current_dir_path.join(src.item.as_str()); @@ -98,6 +102,7 @@ impl Command for Cp { let ctrlc = engine_state.ctrlc.clone(); let span = call.head; + // Get an iterator with all the source files. let sources: Vec<_> = match nu_glob::glob_with(&source.to_string_lossy(), GLOB_PARAMS) { Ok(files) => files.collect(), Err(e) => { @@ -179,8 +184,23 @@ impl Command for Cp { Vec::new(), )); } else if interactive && dst.exists() { - interactive_copy(interactive, src, dst, span, copy_file) + if progress { + interactive_copy( + interactive, + src, + dst, + span, + copy_file_with_progressbar, + ) + } else { + interactive_copy(interactive, src, dst, span, copy_file) + } + } else if progress { + // uses std::io::copy to get the progress + // slower std::fs::copy but useful if user needs to see the progress + copy_file_with_progressbar(src, dst, span) } else { + // uses std::fs::copy copy_file(src, dst, span) }; result.push(res); @@ -268,7 +288,19 @@ impl Command for Cp { result.push(res); } else if s.is_file() { let res = if interactive && d.exists() { - interactive_copy(interactive, s, d, span, copy_file) + if progress { + interactive_copy( + interactive, + s, + d, + span, + copy_file_with_progressbar, + ) + } else { + interactive_copy(interactive, s, d, span, copy_file) + } + } else if progress { + copy_file_with_progressbar(s, d, span) } else { copy_file(s, d, span) }; @@ -349,50 +381,95 @@ fn interactive_copy( } } +// This uses `std::fs::copy` to copy a file. There is another function called `copy_file_with_progressbar` +// which uses `read` and `write` instead. This is to get the progress of the copy. Try to keep the logic in +// this function in sync with `copy_file_with_progressbar` fn copy_file(src: PathBuf, dst: PathBuf, span: Span) -> Value { match std::fs::copy(&src, &dst) { Ok(_) => { let msg = format!("copied {:} to {:}", src.display(), dst.display()); Value::String { val: msg, span } } - Err(e) => { - let message_src = format!( - "copying file '{src_display}' failed: {e}", - src_display = src.display() - ); - let message_dst = format!( - "copying to destination '{dst_display}' failed: {e}", - dst_display = dst.display() - ); - use std::io::ErrorKind; - let shell_error = match e.kind() { - ErrorKind::NotFound => { - if std::path::Path::new(&dst).exists() { - ShellError::FileNotFoundCustom(message_src, span) - } else { - ShellError::FileNotFoundCustom(message_dst, span) - } - } - ErrorKind::PermissionDenied => match std::fs::metadata(&dst) { - Ok(meta) => { - if meta.permissions().readonly() { - ShellError::PermissionDeniedError(message_dst, span) - } else { - ShellError::PermissionDeniedError(message_src, span) + Err(e) => convert_io_error(e, src, dst, span), + } +} + +// This uses `read` and `write` to copy a file. There is another function called `copy_file` +// which uses `std::fs::copy` instead which is faster but does not provide progress updates for the copy. try to keep the +// logic in this function in sync with `copy_file` +fn copy_file_with_progressbar(src: PathBuf, dst: PathBuf, span: Span) -> Value { + let mut bytes_processed: u64 = 0; + let mut process_failed: Option = None; + + let file_in = match std::fs::File::open(&src) { + Ok(file) => file, + Err(error) => return convert_io_error(error, src, dst, span), + }; + + let file_size = match file_in.metadata() { + Ok(metadata) => Some(metadata.len()), + _ => None, + }; + + let mut bar = progress_bar::NuProgressBar::new(file_size); + + let file_out = match std::fs::File::create(&dst) { + Ok(file) => file, + Err(error) => return convert_io_error(error, src, dst, span), + }; + let mut buffer = [0u8; 8192]; + let mut buf_reader = BufReader::new(file_in); + let mut buf_writer = BufWriter::new(file_out); + + loop { + // Read src file + match buf_reader.read(&mut buffer) { + // src file read successfully + Ok(bytes_read) => { + // Write dst file + match buf_writer.write(&buffer[..bytes_read]) { + // dst file written successfully + Ok(bytes_written) => { + // Update the total amount of bytes that has been saved and then print the progress bar + bytes_processed += bytes_written as u64; + bar.update_bar(bytes_processed); + + // the last block of bytes is going to be lower than the buffer size + // let's break the loop once we write the last block + if bytes_read < buffer.len() { + break; } } - Err(_) => ShellError::PermissionDeniedError(message_dst, span), - }, - ErrorKind::Interrupted => ShellError::IOInterrupted(message_src, span), - ErrorKind::OutOfMemory => ShellError::OutOfMemoryError(message_src, span), - // TODO: handle ExecutableFileBusy etc. when io_error_more is stabilized - // https://github.com/rust-lang/rust/issues/86442 - _ => ShellError::IOErrorSpanned(message_src, span), - }; - - Value::Error { error: shell_error } - } + Err(e) => { + // There was a problem writing the dst file + process_failed = Some(e); + break; + } + } + } + Err(e) => { + // There was a problem reading the src file + process_failed = Some(e); + break; + } + }; } + + // If copying the file failed + if let Some(error) = process_failed { + bar.abandoned_msg("# !! Error !!".to_owned()); + return convert_io_error(error, src, dst, span); + } + + // Get the name of the file to print it out at the end + let file_name = &src + .file_name() + .unwrap_or_else(|| std::ffi::OsStr::new("")) + .to_string_lossy(); + let msg = format!("copied {:} to {:}", src.display(), dst.display()); + bar.finished_msg(format!(" {} copied!", &file_name)); + + Value::String { val: msg, span } } fn copy_symlink(src: PathBuf, dst: PathBuf, span: Span) -> Value { @@ -438,3 +515,43 @@ fn copy_symlink(src: PathBuf, dst: PathBuf, span: Span) -> Value { }, } } + +// Function to convert io::Errors to more specific ShellErrors +fn convert_io_error(error: std::io::Error, src: PathBuf, dst: PathBuf, span: Span) -> Value { + let message_src = format!( + "copying file '{src_display}' failed: {error}", + src_display = src.display() + ); + + let message_dst = format!( + "copying to destination '{dst_display}' failed: {error}", + dst_display = dst.display() + ); + + let shell_error = match error.kind() { + ErrorKind::NotFound => { + if std::path::Path::new(&dst).exists() { + ShellError::FileNotFoundCustom(message_src, span) + } else { + ShellError::FileNotFoundCustom(message_dst, span) + } + } + ErrorKind::PermissionDenied => match std::fs::metadata(&dst) { + Ok(meta) => { + if meta.permissions().readonly() { + ShellError::PermissionDeniedError(message_dst, span) + } else { + ShellError::PermissionDeniedError(message_src, span) + } + } + Err(_) => ShellError::PermissionDeniedError(message_dst, span), + }, + ErrorKind::Interrupted => ShellError::IOInterrupted(message_src, span), + ErrorKind::OutOfMemory => ShellError::OutOfMemoryError(message_src, span), + // TODO: handle ExecutableFileBusy etc. when io_error_more is stabilized + // https://github.com/rust-lang/rust/issues/86442 + _ => ShellError::IOErrorSpanned(message_src, span), + }; + + Value::Error { error: shell_error } +} diff --git a/crates/nu-command/src/progress_bar.rs b/crates/nu-command/src/progress_bar.rs index b757cde48..b97502f05 100644 --- a/crates/nu-command/src/progress_bar.rs +++ b/crates/nu-command/src/progress_bar.rs @@ -16,12 +16,12 @@ impl NuProgressBar { let template = match total_bytes { Some(_) => { // We will use a progress bar if we know the total bytes of the stream - ProgressStyle::with_template("{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {bytes}/{total_bytes} {binary_bytes_per_sec} ({eta}) {msg}") + ProgressStyle::with_template("{spinner:.green} [{elapsed_precise}] [{bar:30.cyan/blue}] [{bytes}/{total_bytes}] {binary_bytes_per_sec} ({eta}) {wide_msg}") } _ => { // But if we don't know the total then we just show the stats progress ProgressStyle::with_template( - "{spinner:.green} [{elapsed_precise}] {bytes} {binary_bytes_per_sec} {msg}", + "{spinner:.green} [{elapsed_precise}] {bytes} {binary_bytes_per_sec} {wide_msg}", ) } }; @@ -49,10 +49,9 @@ impl NuProgressBar { self.pb.set_position(bytes_processed); } - // Commenting this for now but adding it in the future - //pub fn finished_msg(&self, msg: String) { - // self.pb.finish_with_message(msg); - //} + pub fn finished_msg(&self, msg: String) { + self.pb.finish_with_message(msg); + } pub fn abandoned_msg(&self, msg: String) { self.pb.abandon_with_message(msg); diff --git a/crates/nu-command/tests/commands/cp.rs b/crates/nu-command/tests/commands/cp.rs index e10d961fb..4852a1f2e 100644 --- a/crates/nu-command/tests/commands/cp.rs +++ b/crates/nu-command/tests/commands/cp.rs @@ -7,40 +7,82 @@ use nu_test_support::nu; use nu_test_support::playground::Playground; use std::path::Path; +fn get_file_hash(file: T) -> String { + nu!("open {} | to text | hash md5", file).out +} + #[test] fn copies_a_file() { + copies_a_file_impl(false); + copies_a_file_impl(true); +} + +fn copies_a_file_impl(progress: bool) { Playground::setup("cp_test_1", |dirs, _| { + let test_file = dirs.formats().join("sample.ini"); + let progress_flag = if progress { "-p" } else { "" }; + + // Get the hash of the file content to check integrity after copy. + let first_hash = get_file_hash(&test_file.display()); + nu!( cwd: dirs.root(), - "cp `{}` cp_test_1/sample.ini", - dirs.formats().join("sample.ini").display() + "cp {} `{}` cp_test_1/sample.ini", + progress_flag, + test_file.display() ); assert!(dirs.test().join("sample.ini").exists()); + + // Get the hash of the copied file content to check against first_hash. + let after_cp_hash = get_file_hash(dirs.test().join("sample.ini").display()); + assert_eq!(first_hash, after_cp_hash); }); } #[test] fn copies_the_file_inside_directory_if_path_to_copy_is_directory() { + copies_the_file_inside_directory_if_path_to_copy_is_directory_impl(false); + copies_the_file_inside_directory_if_path_to_copy_is_directory_impl(true); +} + +fn copies_the_file_inside_directory_if_path_to_copy_is_directory_impl(progress: bool) { Playground::setup("cp_test_2", |dirs, _| { let expected_file = AbsoluteFile::new(dirs.test().join("sample.ini")); + let progress_flag = if progress { "-p" } else { "" }; + // Get the hash of the file content to check integrity after copy. + let first_hash = get_file_hash(dirs.formats().join("../formats/sample.ini").display()); nu!( cwd: dirs.formats(), - "cp ../formats/sample.ini {}", + "cp {} ../formats/sample.ini {}", + progress_flag, expected_file.dir() ); assert!(dirs.test().join("sample.ini").exists()); + + // Check the integrity of the file. + let after_cp_hash = get_file_hash(expected_file); + assert_eq!(first_hash, after_cp_hash); }) } #[test] fn error_if_attempting_to_copy_a_directory_to_another_directory() { + error_if_attempting_to_copy_a_directory_to_another_directory_impl(false); + error_if_attempting_to_copy_a_directory_to_another_directory_impl(true); +} + +fn error_if_attempting_to_copy_a_directory_to_another_directory_impl(progress: bool) { Playground::setup("cp_test_3", |dirs, _| { + let progress_flag = if progress { "-p" } else { "" }; + let actual = nu!( cwd: dirs.formats(), - "cp ../formats {}", dirs.test().display() + "cp {} ../formats {}", + progress_flag, + dirs.test().display() ); assert!(actual.err.contains("../formats")); @@ -50,6 +92,17 @@ fn error_if_attempting_to_copy_a_directory_to_another_directory() { #[test] fn copies_the_directory_inside_directory_if_path_to_copy_is_directory_and_with_recursive_flag() { + copies_the_directory_inside_directory_if_path_to_copy_is_directory_and_with_recursive_flag_impl( + false, + ); + copies_the_directory_inside_directory_if_path_to_copy_is_directory_and_with_recursive_flag_impl( + true, + ); +} + +fn copies_the_directory_inside_directory_if_path_to_copy_is_directory_and_with_recursive_flag_impl( + progress: bool, +) { Playground::setup("cp_test_4", |dirs, sandbox| { sandbox .within("originals") @@ -61,10 +114,12 @@ fn copies_the_directory_inside_directory_if_path_to_copy_is_directory_and_with_r .mkdir("expected"); let expected_dir = dirs.test().join("expected").join("originals"); + let progress_flag = if progress { "-p" } else { "" }; nu!( cwd: dirs.test(), - "cp originals expected -r" + "cp {} originals expected -r", + progress_flag ); assert!(expected_dir.exists()); @@ -74,13 +129,18 @@ fn copies_the_directory_inside_directory_if_path_to_copy_is_directory_and_with_r Path::new("jonathan.txt"), Path::new("andres.txt") ], - expected_dir + &expected_dir )); }) } #[test] fn deep_copies_with_recursive_flag() { + deep_copies_with_recursive_flag_impl(false); + deep_copies_with_recursive_flag_impl(true); +} + +fn deep_copies_with_recursive_flag_impl(progress: bool) { Playground::setup("cp_test_5", |dirs, sandbox| { sandbox .within("originals") @@ -100,6 +160,7 @@ fn deep_copies_with_recursive_flag() { .mkdir("expected"); let expected_dir = dirs.test().join("expected").join("originals"); + let progress_flag = if progress { "-p" } else { "" }; let jonathans_expected_copied_dir = expected_dir.join("contributors").join("jonathan"); let andres_expected_copied_dir = expected_dir.join("contributors").join("andres"); @@ -107,31 +168,48 @@ fn deep_copies_with_recursive_flag() { nu!( cwd: dirs.test(), - "cp originals expected --recursive" + "cp {} originals expected --recursive", + progress_flag ); assert!(expected_dir.exists()); assert!(files_exist_at( vec![Path::new("errors.txt"), Path::new("multishells.txt")], - jonathans_expected_copied_dir + &jonathans_expected_copied_dir )); assert!(files_exist_at( vec![Path::new("coverage.txt"), Path::new("commands.txt")], - andres_expected_copied_dir + &andres_expected_copied_dir )); assert!(files_exist_at( vec![Path::new("defer-evaluation.txt")], - yehudas_expected_copied_dir + &yehudas_expected_copied_dir )); }) } #[test] fn copies_using_path_with_wildcard() { + copies_using_path_with_wildcard_impl(false); + copies_using_path_with_wildcard_impl(true); +} + +fn copies_using_path_with_wildcard_impl(progress: bool) { Playground::setup("cp_test_6", |dirs, _| { + let progress_flag = if progress { "-p" } else { "" }; + + // Get the hash of the file content to check integrity after copy. + let src_hashes = nu!( + cwd: dirs.formats(), + "for file in (ls ../formats/*) { open --raw $file.name | to text | hash md5 }" + ) + .out; + nu!( cwd: dirs.formats(), - "cp -r ../formats/* {}", dirs.test().display() + "cp {} -r ../formats/* {}", + progress_flag, + dirs.test().display() ); assert!(files_exist_at( @@ -145,15 +223,38 @@ fn copies_using_path_with_wildcard() { ], dirs.test() )); + + // Check integrity after the copy is done + let dst_hashes = nu!( + cwd: dirs.formats(), + "for file in (ls {}) {{ open --raw $file.name | to text | hash md5 }}", dirs.test().display() + ).out; + assert_eq!(src_hashes, dst_hashes); }) } #[test] fn copies_using_a_glob() { + copies_using_a_glob_impl(false); + copies_using_a_glob_impl(true); +} + +fn copies_using_a_glob_impl(progress: bool) { Playground::setup("cp_test_7", |dirs, _| { + let progress_flag = if progress { "-p" } else { "" }; + + // Get the hash of the file content to check integrity after copy. + let src_hashes = nu!( + cwd: dirs.formats(), + "for file in (ls *) { open --raw $file.name | to text | hash md5 }" + ) + .out; + nu!( cwd: dirs.formats(), - "cp -r * {}", dirs.test().display() + "cp {} -r * {}", + progress_flag, + dirs.test().display() ); assert!(files_exist_at( @@ -167,21 +268,39 @@ fn copies_using_a_glob() { ], dirs.test() )); + + // Check integrity after the copy is done + let dst_hashes = nu!( + cwd: dirs.formats(), + "for file in (ls {}) {{ open --raw $file.name | to text | hash md5 }}", + dirs.test().display() + ) + .out; + assert_eq!(src_hashes, dst_hashes); }); } #[test] fn copies_same_file_twice() { + copies_same_file_twice_impl(false); + copies_same_file_twice_impl(true); +} + +fn copies_same_file_twice_impl(progress: bool) { Playground::setup("cp_test_8", |dirs, _| { + let progress_flag = if progress { "-p" } else { "" }; + nu!( cwd: dirs.root(), - "cp `{}` cp_test_8/sample.ini", + "cp {} `{}` cp_test_8/sample.ini", + progress_flag, dirs.formats().join("sample.ini").display() ); nu!( cwd: dirs.root(), - "cp `{}` cp_test_8/sample.ini", + "cp {} `{}` cp_test_8/sample.ini", + progress_flag, dirs.formats().join("sample.ini").display() ); @@ -191,6 +310,11 @@ fn copies_same_file_twice() { #[test] fn copy_files_using_glob_two_parents_up_using_multiple_dots() { + copy_files_using_glob_two_parents_up_using_multiple_dots_imp(false); + copy_files_using_glob_two_parents_up_using_multiple_dots_imp(true); +} + +fn copy_files_using_glob_two_parents_up_using_multiple_dots_imp(progress: bool) { Playground::setup("cp_test_9", |dirs, sandbox| { sandbox.within("foo").within("bar").with_files(vec![ EmptyFile("jonathan.json"), @@ -200,11 +324,12 @@ fn copy_files_using_glob_two_parents_up_using_multiple_dots() { EmptyFile("many_more.ppl"), ]); + let progress_flag = if progress { "-p" } else { "" }; + nu!( cwd: dirs.test().join("foo/bar"), - r#" - cp * ... - "# + " cp {} * ...", + progress_flag, ); assert!(files_exist_at( @@ -222,16 +347,24 @@ fn copy_files_using_glob_two_parents_up_using_multiple_dots() { #[test] fn copy_file_and_dir_from_two_parents_up_using_multiple_dots_to_current_dir_recursive() { + copy_file_and_dir_from_two_parents_up_using_multiple_dots_to_current_dir_recursive_impl(false); + copy_file_and_dir_from_two_parents_up_using_multiple_dots_to_current_dir_recursive_impl(true); +} + +fn copy_file_and_dir_from_two_parents_up_using_multiple_dots_to_current_dir_recursive_impl( + progress: bool, +) { Playground::setup("cp_test_10", |dirs, sandbox| { sandbox.with_files(vec![EmptyFile("hello_there")]); sandbox.mkdir("hello_again"); sandbox.within("foo").mkdir("bar"); + let progress_flag = if progress { "-p" } else { "" }; + nu!( cwd: dirs.test().join("foo/bar"), - r#" - cp -r .../hello* . - "# + "cp {} -r .../hello* .", + progress_flag ); let expected = dirs.test().join("foo/bar"); @@ -242,12 +375,20 @@ fn copy_file_and_dir_from_two_parents_up_using_multiple_dots_to_current_dir_recu #[test] fn copy_to_non_existing_dir() { + copy_to_non_existing_dir_impl(false); + copy_to_non_existing_dir_impl(true); +} + +fn copy_to_non_existing_dir_impl(progress: bool) { Playground::setup("cp_test_11", |_dirs, sandbox| { sandbox.with_files(vec![EmptyFile("empty_file")]); + let progress_flag = if progress { "-p" } else { "" }; + let actual = nu!( cwd: sandbox.cwd(), - "cp empty_file ~/not_a_dir/", + "cp {} empty_file ~/not_a_dir/", + progress_flag ); assert!(actual.err.contains("directory not found")); assert!(actual.err.contains("destination directory does not exist")); @@ -256,6 +397,11 @@ fn copy_to_non_existing_dir() { #[test] fn copy_dir_contains_symlink_ignored() { + copy_dir_contains_symlink_ignored_impl(false); + copy_dir_contains_symlink_ignored_impl(true); +} + +fn copy_dir_contains_symlink_ignored_impl(progress: bool) { Playground::setup("cp_test_12", |_dirs, sandbox| { sandbox .within("tmp_dir") @@ -263,10 +409,13 @@ fn copy_dir_contains_symlink_ignored() { .within("tmp_dir") .symlink("good_bye", "dangle_symlink"); + let progress_flag = if progress { "-p" } else { "" }; + // make symbolic link and copy. nu!( cwd: sandbox.cwd(), - "rm tmp_dir/good_bye; cp -r tmp_dir tmp_dir_2", + "rm {} tmp_dir/good_bye; cp -r tmp_dir tmp_dir_2", + progress_flag ); // check hello_there exists inside `tmp_dir_2`, and `dangle_symlink` don't exists inside `tmp_dir_2`. @@ -279,6 +428,11 @@ fn copy_dir_contains_symlink_ignored() { #[test] fn copy_dir_contains_symlink() { + copy_dir_contains_symlink_impl(false); + copy_dir_contains_symlink_impl(true); +} + +fn copy_dir_contains_symlink_impl(progress: bool) { Playground::setup("cp_test_13", |_dirs, sandbox| { sandbox .within("tmp_dir") @@ -286,10 +440,13 @@ fn copy_dir_contains_symlink() { .within("tmp_dir") .symlink("good_bye", "dangle_symlink"); + let progress_flag = if progress { "-p" } else { "" }; + // make symbolic link and copy. nu!( cwd: sandbox.cwd(), - "rm tmp_dir/good_bye; cp -r -n tmp_dir tmp_dir_2", + "rm tmp_dir/good_bye; cp {} -r -n tmp_dir tmp_dir_2", + progress_flag ); // check hello_there exists inside `tmp_dir_2`, and `dangle_symlink` also exists inside `tmp_dir_2`. @@ -302,6 +459,11 @@ fn copy_dir_contains_symlink() { #[test] fn copy_dir_symlink_file_body_not_changed() { + copy_dir_symlink_file_body_not_changed_impl(false); + copy_dir_symlink_file_body_not_changed_impl(true); +} + +fn copy_dir_symlink_file_body_not_changed_impl(progress: bool) { Playground::setup("cp_test_14", |_dirs, sandbox| { sandbox .within("tmp_dir") @@ -309,10 +471,14 @@ fn copy_dir_symlink_file_body_not_changed() { .within("tmp_dir") .symlink("good_bye", "dangle_symlink"); + let progress_flag = if progress { "-p" } else { "" }; + // make symbolic link and copy. nu!( cwd: sandbox.cwd(), - "rm tmp_dir/good_bye; cp -r -n tmp_dir tmp_dir_2; rm -r tmp_dir; cp -r -n tmp_dir_2 tmp_dir; echo hello_data | save tmp_dir/good_bye", + "rm tmp_dir/good_bye; cp {} -r -n tmp_dir tmp_dir_2; rm -r tmp_dir; cp {} -r -n tmp_dir_2 tmp_dir; echo hello_data | save tmp_dir/good_bye", + progress_flag, + progress_flag, ); // check dangle_symlink in tmp_dir is no longer dangling. @@ -324,12 +490,20 @@ fn copy_dir_symlink_file_body_not_changed() { #[test] fn copy_identical_file() { + copy_identical_file_impl(false); + copy_identical_file_impl(true); +} + +fn copy_identical_file_impl(progress: bool) { Playground::setup("cp_test_15", |_dirs, sandbox| { sandbox.with_files(vec![EmptyFile("same.txt")]); + let progress_flag = if progress { "-p" } else { "" }; + let actual = nu!( cwd: sandbox.cwd(), - "cp same.txt same.txt", + "cp {} same.txt same.txt", + progress_flag, ); assert!(actual.err.contains("Copy aborted")); }); @@ -337,12 +511,20 @@ fn copy_identical_file() { #[test] fn copy_ignores_ansi() { + copy_ignores_ansi_impl(false); + copy_ignores_ansi_impl(true); +} + +fn copy_ignores_ansi_impl(progress: bool) { Playground::setup("cp_test_16", |_dirs, sandbox| { sandbox.with_files(vec![EmptyFile("test.txt")]); + let progress_flag = if progress { "-p" } else { "" }; + let actual = nu!( cwd: sandbox.cwd(), - "ls | find test | get name | cp $in.0 success.txt; ls | find success | get name | ansi strip | get 0", + "ls | find test | get name | cp {} $in.0 success.txt; ls | find success | get name | ansi strip | get 0", + progress_flag, ); assert_eq!(actual.out, "success.txt"); }); @@ -350,12 +532,20 @@ fn copy_ignores_ansi() { #[test] fn copy_file_not_exists_dst() { + copy_file_not_exists_dst_impl(false); + copy_file_not_exists_dst_impl(true); +} + +fn copy_file_not_exists_dst_impl(progress: bool) { Playground::setup("cp_test_17", |_dirs, sandbox| { sandbox.with_files(vec![EmptyFile("valid.txt")]); + let progress_flag = if progress { "-p" } else { "" }; + let actual = nu!( cwd: sandbox.cwd(), - "cp valid.txt ~/invalid_dir/invalid_dir1" + "cp {} valid.txt ~/invalid_dir/invalid_dir1", + progress_flag, ); assert!( actual.err.contains("invalid_dir1") && actual.err.contains("copying to destination") @@ -365,15 +555,23 @@ fn copy_file_not_exists_dst() { #[test] fn copy_file_with_read_permission() { + copy_file_with_read_permission_impl(false); + copy_file_with_read_permission_impl(true); +} + +fn copy_file_with_read_permission_impl(progress: bool) { Playground::setup("cp_test_18", |_dirs, sandbox| { sandbox.with_files(vec![ EmptyFile("valid.txt"), FileWithPermission("invalid_prem.txt", false), ]); + let progress_flag = if progress { "-p" } else { "" }; + let actual = nu!( cwd: sandbox.cwd(), - "cp valid.txt invalid_prem.txt", + "cp {} valid.txt invalid_prem.txt", + progress_flag, ); assert!( actual.err.contains("invalid_prem.txt")