diff --git a/Cargo.lock b/Cargo.lock index 2092b531c9..8b75dd6d50 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -121,12 +121,55 @@ dependencies = [ "vte 0.10.1", ] +[[package]] +name = "anstream" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is-terminal", + "utf8parse", +] + [[package]] name = "anstyle" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd" +[[package]] +name = "anstyle-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "anstyle-wincon" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c677ab05e09154296dd37acecd46420c17b9713e8366facafa8fc0885167cf4c" +dependencies = [ + "anstyle", + "windows-sys 0.48.0", +] + [[package]] name = "argminmax" version = "0.6.1" @@ -661,8 +704,12 @@ version = "4.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98c59138d527eeaf9b53f35a77fcc1fad9d883116070c63d5de1c7dc7b00c72b" dependencies = [ + "anstream", "anstyle", "clap_lex", + "once_cell", + "strsim", + "terminal_size 0.2.6", ] [[package]] @@ -680,6 +727,12 @@ dependencies = [ "encoding_rs", ] +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + [[package]] name = "comfy-table" version = "7.0.1" @@ -1113,6 +1166,12 @@ dependencies = [ "rust_decimal", ] +[[package]] +name = "dunce" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" + [[package]] name = "dyn-clone" version = "1.0.11" @@ -2863,6 +2922,7 @@ dependencies = [ "unicode-segmentation", "ureq", "url", + "uu_cp", "uuid", "wax", "which", @@ -3390,6 +3450,15 @@ dependencies = [ "hashbrown 0.13.2", ] +[[package]] +name = "os_display" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6229bad892b46b0dcfaaeb18ad0d2e56400f5aaea05b768bde96e73676cf75" +dependencies = [ + "unicode-width", +] + [[package]] name = "os_pipe" version = "1.1.4" @@ -4064,6 +4133,12 @@ version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + [[package]] name = "quick-xml" version = "0.28.2" @@ -4935,6 +5010,12 @@ dependencies = [ "vte 0.11.1", ] +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "strum" version = "0.24.1" @@ -5021,7 +5102,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36e39da5d30887b5690e29de4c5ebb8ddff64ebd9933f98a01daaa4fd11b36ea" dependencies = [ "peresil", - "quick-error", + "quick-error 1.2.3", "sxd-document", ] @@ -5542,6 +5623,59 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +[[package]] +name = "uu_cp" +version = "0.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce78537083be579c77dadfcced04e163a905ff51f3f83d11dcdaf252ea771c5" +dependencies = [ + "clap", + "filetime", + "indicatif", + "libc", + "quick-error 2.0.1", + "uucore", + "walkdir", + "xattr", +] + +[[package]] +name = "uucore" +version = "0.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bea3522caab8af3fe1de1f27d9691e4ea159efe4d86d4e176306792163936a6" +dependencies = [ + "clap", + "dunce", + "glob", + "libc", + "nix 0.26.2", + "once_cell", + "os_display", + "uucore_procs", + "walkdir", + "wild", + "winapi-util", + "windows-sys 0.48.0", +] + +[[package]] +name = "uucore_procs" +version = "0.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0847828ba11d397cc7b5c3b2b6397f367db71ffb94e0f42cb7e7b1fb3691d556" +dependencies = [ + "proc-macro2", + "quote", + "uuhelp_parser", +] + +[[package]] +name = "uuhelp_parser" +version = "0.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37db35583cf0ad896592892034f281ef0906c893b3b6d901acf3918357f28199" + [[package]] name = "uuid" version = "1.4.0" @@ -5733,6 +5867,15 @@ dependencies = [ "once_cell", ] +[[package]] +name = "wild" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b116685a6be0c52f5a103334cbff26db643826c7b3735fc0a3ba9871310a74" +dependencies = [ + "glob", +] + [[package]] name = "winapi" version = "0.3.9" @@ -5943,6 +6086,15 @@ dependencies = [ "version_check", ] +[[package]] +name = "xattr" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4686009f71ff3e5c4dbcf1a282d0a44db3f021ba69350cd42086b3e5f1c6985" +dependencies = [ + "libc", +] + [[package]] name = "xmlparser" version = "0.13.5" diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml index 9406027889..95f8d04d09 100644 --- a/crates/nu-command/Cargo.toml +++ b/crates/nu-command/Cargo.toml @@ -88,6 +88,7 @@ toml = "0.7" unicode-segmentation = "1.10" ureq = { version = "2.7", default-features = false, features = ["charset", "gzip", "json", "native-tls"] } url = "2.2" +uu_cp = "0.0.21" uuid = { version = "1.3", features = ["v4"] } wax = { version = "0.5" } which = { version = "4.4", optional = true } diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index a8c23bd17d..0ff540dd83 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -200,10 +200,11 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState { // FileSystem bind_command! { Cd, - Cp, Ls, Mkdir, Mv, + Cp, + UCp, Open, Start, Rm, diff --git a/crates/nu-command/src/filesystem/mod.rs b/crates/nu-command/src/filesystem/mod.rs index ab32215db0..5e7085a4a5 100644 --- a/crates/nu-command/src/filesystem/mod.rs +++ b/crates/nu-command/src/filesystem/mod.rs @@ -10,6 +10,7 @@ mod rm; mod save; mod start; mod touch; +mod ucp; mod util; mod watch; @@ -25,4 +26,5 @@ pub use rm::Rm; pub use save::Save; pub use start::Start; pub use touch::Touch; +pub use ucp::UCp; pub use watch::Watch; diff --git a/crates/nu-command/src/filesystem/ucp.rs b/crates/nu-command/src/filesystem/ucp.rs new file mode 100644 index 0000000000..5d564d935f --- /dev/null +++ b/crates/nu-command/src/filesystem/ucp.rs @@ -0,0 +1,244 @@ +use nu_path::expand_to_real_path; +use nu_protocol::{ + ast::{Argument, Call, Expr}, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Type, +}; +use std::path::PathBuf; +use uu_cp::{BackupMode, UpdateMode}; + +// TODO: related to uucore::error::set_exit_code(EXIT_ERR) +// const EXIT_ERR: i32 = 1; +const GLOB_PARAMS: nu_glob::MatchOptions = nu_glob::MatchOptions { + case_sensitive: true, + require_literal_separator: false, + require_literal_leading_dot: false, + recursive_match_hidden_dir: true, +}; + +pub fn collect_filepath_arguments(call: &Call) -> Vec> { + call.arguments + .iter() + .filter_map(|arg| match arg { + Argument::Positional(expression) => { + if let Expr::Filepath(p) = &expression.expr { + let pth = Spanned { + item: nu_utils::strip_ansi_string_unlikely(p.clone()), + span: expression.span, + }; + Some(pth) + } else { + None + } + } + _ => None, + }) + .collect::>>() +} + +#[derive(Clone)] +pub struct UCp; + +impl Command for UCp { + fn name(&self) -> &str { + "ucp" + } + + fn usage(&self) -> &str { + "Copy files using uutils/coreutils cp." + } + + fn search_terms(&self) -> Vec<&str> { + vec!["copy", "file", "files"] + } + + fn signature(&self) -> Signature { + Signature::build("ucp") + .input_output_types(vec![(Type::Nothing, Type::Nothing)]) + .switch("recursive", "copy directories recursively", Some('r')) + .switch("verbose", "explicitly state what is being done", Some('v')) + .switch( + "force", + "if an existing destination file cannot be opened, remove it and try + again (this option is ignored when the -n option is also used). + currently not implemented for windows", + Some('f'), + ) + .switch("interactive", "ask before overwriting files", Some('i')) + .switch("progress", "display a progress bar", Some('p')) + .switch("no-clobber", "do not overwrite an existing file", Some('n')) + .switch("debug", "explain how a file is copied. Implies -v", None) + .rest("paths", SyntaxShape::Filepath, "Copy SRC file/s to DEST") + .allow_variants_without_examples(true) + .category(Category::FileSystem) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Copy myfile to dir_b", + example: "ucp myfile dir_b", + result: None, + }, + Example { + description: "Recursively copy dir_a to dir_b", + example: "ucp -r dir_a dir_b", + result: None, + }, + Example { + description: "Recursively copy dir_a to dir_b, and print the feedbacks", + example: "ucp -r -v dir_a dir_b", + result: None, + }, + Example { + description: "Move many files into a directory", + example: "ucp *.txt dir_a", + result: None, + }, + ] + } + + fn run( + &self, + _engine_state: &EngineState, + _stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + let interactive = call.has_flag("interactive"); + let force = call.has_flag("force"); + let no_clobber = call.has_flag("no-clobber"); + let progress = call.has_flag("progress"); + let recursive = call.has_flag("recursive"); + let verbose = call.has_flag("verbose"); + + let debug = call.has_flag("debug"); + let overwrite = if no_clobber { + uu_cp::OverwriteMode::NoClobber + } else if interactive { + if force { + uu_cp::OverwriteMode::Interactive(uu_cp::ClobberMode::Force) + } else { + uu_cp::OverwriteMode::Interactive(uu_cp::ClobberMode::Standard) + } + } else if force { + uu_cp::OverwriteMode::Clobber(uu_cp::ClobberMode::Force) + } else { + uu_cp::OverwriteMode::Clobber(uu_cp::ClobberMode::Standard) + }; + #[cfg(any(target_os = "linux", target_os = "android", target_os = "macos"))] + let reflink_mode = uu_cp::ReflinkMode::Auto; + #[cfg(not(any(target_os = "linux", target_os = "android", target_os = "macos")))] + let reflink_mode = uu_cp::ReflinkMode::Never; + let mut paths = collect_filepath_arguments(call); + if paths.is_empty() { + return Err(ShellError::GenericError( + "Missing file operand".into(), + "Missing file operand".into(), + Some(call.head), + Some("Please provide source and destination paths".into()), + Vec::new(), + )); + } + + if paths.len() == 1 { + return Err(ShellError::GenericError( + "Missing destination path".into(), + format!("Missing destination path operand after {}", paths[0].item), + Some(paths[0].span), + None, + Vec::new(), + )); + } + let target = paths.pop().expect("Should not be reached?"); + let target_path = PathBuf::from(&target.item); + if target.item.ends_with('/') && !target_path.is_dir() { + return Err(ShellError::GenericError( + "is not a directory".into(), + "is not a directory".into(), + Some(target.span), + None, + Vec::new(), + )); + }; + // paths now contains the sources + let sources: Vec> = paths + .iter() + .map(|p| { + // Need to expand too make it work with globbing + let expanded_src = expand_to_real_path(&p.item); + match nu_glob::glob_with(&expanded_src.to_string_lossy(), GLOB_PARAMS) { + Ok(files) => { + let f = files.filter_map(Result::ok).collect::>(); + if f.is_empty() { + return Err(ShellError::FileNotFound(p.span)); + } + Ok(f) + } + Err(e) => Err(ShellError::GenericError( + e.to_string(), + "invalid pattern".to_string(), + Some(p.span), + None, + Vec::new(), + )), + } + }) + .collect::>, ShellError>>()?; + + let sources = sources.into_iter().flatten().collect::>(); + let options = uu_cp::Options { + overwrite, + reflink_mode, + recursive, + debug, + verbose: verbose || debug, + dereference: !recursive, + progress_bar: progress, + attributes_only: false, + backup: BackupMode::NoBackup, + copy_contents: false, + cli_dereference: false, + copy_mode: uu_cp::CopyMode::Copy, + no_target_dir: false, + one_file_system: false, + parents: false, + sparse_mode: uu_cp::SparseMode::Auto, + strip_trailing_slashes: false, + attributes: uu_cp::Attributes::NONE, + backup_suffix: String::from("~"), + target_dir: None, + update: UpdateMode::ReplaceAll, + }; + + if let Err(error) = uu_cp::copy(&sources, &target_path, &options) { + match error { + // code should still be EXIT_ERR as does GNU cp + uu_cp::Error::NotAllFilesCopied => {} + _ => { + return Err(ShellError::GenericError( + format!("{}", error), + format!("{}", error), + None, + None, + Vec::new(), + )) + } + }; + // TODO: What should we do in place of set_exit_code? + // uucore::error::set_exit_code(EXIT_ERR); + } + Ok(PipelineData::empty()) + } +} + +#[cfg(test)] +mod test { + use super::*; + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(UCp {}) + } +} diff --git a/crates/nu-command/tests/commands/mod.rs b/crates/nu-command/tests/commands/mod.rs index 7197bf0a19..99f36650db 100644 --- a/crates/nu-command/tests/commands/mod.rs +++ b/crates/nu-command/tests/commands/mod.rs @@ -100,6 +100,7 @@ mod to_text; mod touch; mod transpose; mod try_; +mod ucp; mod uniq; mod uniq_by; mod update; diff --git a/crates/nu-command/tests/commands/ucp.rs b/crates/nu-command/tests/commands/ucp.rs new file mode 100644 index 0000000000..7387df7747 --- /dev/null +++ b/crates/nu-command/tests/commands/ucp.rs @@ -0,0 +1,952 @@ +use nu_test_support::fs::file_contents; +use nu_test_support::fs::{ + files_exist_at, AbsoluteFile, + Stub::{EmptyFile, FileWithContent, FileWithPermission}, +}; +use nu_test_support::nu; +use nu_test_support::playground::Playground; + +use std::path::Path; + +fn get_file_hash(file: T) -> String { + nu!("open -r {} | 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("ucp_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(), + "ucp {} `{}` ucp_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("ucp_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(), + "ucp {} ../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); + }) +} + +// error msg changes on coreutils +#[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("ucp_test_3", |dirs, _| { + let progress_flag = if progress { "-p" } else { "" }; + let actual = nu!( + cwd: dirs.formats(), + "ucp {} ../formats {}", + progress_flag, + dirs.test().display() + ); + + // Changing to GNU error like error + // Slight bug since it should say formats, but its saying "." due to the `strip_prefix` + // that i do I think + // assert!(actual.err.contains("formats")); + // assert!(actual.err.contains("resolves to a directory (not copied)")); + assert!(actual.err.contains("omitting directory")); + + // directories must be copied using --recursive + // gnu says "omitting directory", vbecause -r was not given + }); +} + +#[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("ucp_test_4", |dirs, sandbox| { + sandbox + .within("originals") + .with_files(vec![ + EmptyFile("yehuda.txt"), + EmptyFile("jttxt"), + EmptyFile("andres.txt"), + ]) + .mkdir("expected"); + + let expected_dir = dirs.test().join("expected").join("originals"); + let progress_flag = if progress { "-p" } else { "" }; + + nu!( + cwd: dirs.test(), + "ucp {} originals expected -r", + progress_flag + ); + + assert!(expected_dir.exists()); + assert!(files_exist_at( + vec![ + Path::new("yehuda.txt"), + Path::new("jttxt"), + Path::new("andres.txt") + ], + &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("ucp_test_5", |dirs, sandbox| { + sandbox + .within("originals") + .with_files(vec![EmptyFile("manifest.txt")]) + .within("originals/contributors") + .with_files(vec![ + EmptyFile("yehuda.txt"), + EmptyFile("jttxt"), + EmptyFile("andres.txt"), + ]) + .within("originals/contributors/JT") + .with_files(vec![EmptyFile("errors.txt"), EmptyFile("multishells.txt")]) + .within("originals/contributors/andres") + .with_files(vec![EmptyFile("coverage.txt"), EmptyFile("commands.txt")]) + .within("originals/contributors/yehuda") + .with_files(vec![EmptyFile("defer-evaluation.txt")]) + .mkdir("expected"); + + let expected_dir = dirs.test().join("expected").join("originals"); + let progress_flag = if progress { "-p" } else { "" }; + + let jts_expected_copied_dir = expected_dir.join("contributors").join("JT"); + let andres_expected_copied_dir = expected_dir.join("contributors").join("andres"); + let yehudas_expected_copied_dir = expected_dir.join("contributors").join("yehuda"); + + nu!( + cwd: dirs.test(), + "ucp {} originals expected --recursive", + progress_flag + ); + + assert!(expected_dir.exists()); + assert!(files_exist_at( + vec![Path::new("errors.txt"), Path::new("multishells.txt")], + jts_expected_copied_dir + )); + assert!(files_exist_at( + vec![Path::new("coverage.txt"), Path::new("commands.txt")], + andres_expected_copied_dir + )); + assert!(files_exist_at( + vec![Path::new("defer-evaluation.txt")], + 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("ucp_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(), + "ucp {} -r ../formats/* {}", + progress_flag, + dirs.test().display() + ); + + assert!(files_exist_at( + vec![ + Path::new("caco3_plastics.csv"), + Path::new("cargo_sample.toml"), + Path::new("jt.xml"), + Path::new("sample.ini"), + Path::new("sgml_description.json"), + Path::new("utf16.ini"), + ], + 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("ucp_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(), + "ucp {} -r * {}", + progress_flag, + dirs.test().display() + ); + + assert!(files_exist_at( + vec![ + Path::new("caco3_plastics.csv"), + Path::new("cargo_sample.toml"), + Path::new("jt.xml"), + Path::new("sample.ini"), + Path::new("sgml_description.json"), + Path::new("utf16.ini"), + ], + 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("ucp_test_8", |dirs, _| { + let progress_flag = if progress { "-p" } else { "" }; + + nu!( + cwd: dirs.root(), + "ucp {} `{}` ucp_test_8/sample.ini", + progress_flag, + dirs.formats().join("sample.ini").display() + ); + + nu!( + cwd: dirs.root(), + "ucp {} `{}` ucp_test_8/sample.ini", + progress_flag, + dirs.formats().join("sample.ini").display() + ); + + assert!(dirs.test().join("sample.ini").exists()); + }); +} + +#[test] +#[ignore = "Behavior not supported by uutils cp"] +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("ucp_test_9", |dirs, sandbox| { + sandbox.within("foo").within("bar").with_files(vec![ + EmptyFile("jtjson"), + EmptyFile("andres.xml"), + EmptyFile("yehuda.yaml"), + EmptyFile("kevin.txt"), + EmptyFile("many_more.ppl"), + ]); + + let progress_flag = if progress { "-p" } else { "" }; + + nu!( + cwd: dirs.test().join("foo/bar"), + " cp {} * ...", + progress_flag, + ); + + assert!(files_exist_at( + vec![ + "yehuda.yaml", + "jtjson", + "andres.xml", + "kevin.txt", + "many_more.ppl", + ], + dirs.test() + )); + }) +} + +#[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("ucp_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"), + "ucp {} -r .../hello* .", + progress_flag + ); + + let expected = dirs.test().join("foo/bar"); + + assert!(files_exist_at(vec!["hello_there", "hello_again"], expected)); + }) +} + +// error msg changes on coreutils +#[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("ucp_test_11", |_dirs, sandbox| { + sandbox.with_files(vec![EmptyFile("empty_file")]); + + let progress_flag = if progress { "-p" } else { "" }; + + let actual = nu!( + cwd: sandbox.cwd(), + "ucp {} empty_file ~/not_a_dir/", + progress_flag + ); + // assert!(actual.err.contains("failed to access")); + assert!(actual.err.contains("is not a directory")); + }); +} + +#[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("ucp_test_12", |_dirs, sandbox| { + sandbox + .within("tmp_dir") + .with_files(vec![EmptyFile("hello_there"), EmptyFile("good_bye")]) + .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", + progress_flag + ); + + // check hello_there exists inside `tmp_dir_2`, and `dangle_symlink` don't exists inside `tmp_dir_2`. + let expected = sandbox.cwd().join("tmp_dir_2"); + assert!(files_exist_at(vec!["hello_there"], expected)); + // GNU cp will copy the broken symlink, so following their behavior + // thus commenting out below + // let path = expected.join("dangle_symlink"); + // assert!(!path.exists() && !path.is_symlink()); + }); +} + +#[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("ucp_test_13", |_dirs, sandbox| { + sandbox + .within("tmp_dir") + .with_files(vec![EmptyFile("hello_there"), EmptyFile("good_bye")]) + .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", + progress_flag + ); + + // check hello_there exists inside `tmp_dir_2`, and `dangle_symlink` also exists inside `tmp_dir_2`. + let expected = sandbox.cwd().join("tmp_dir_2"); + assert!(files_exist_at(vec!["hello_there"], expected.clone())); + let path = expected.join("dangle_symlink"); + assert!(path.is_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("ucp_test_14", |_dirs, sandbox| { + sandbox + .within("tmp_dir") + .with_files(vec![EmptyFile("hello_there"), EmptyFile("good_bye")]) + .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", + progress_flag, + progress_flag, + ); + + // check dangle_symlink in tmp_dir is no longer dangling. + let expected_file = sandbox.cwd().join("tmp_dir").join("dangle_symlink"); + let actual = file_contents(expected_file); + assert!(actual.contains("hello_data")); + }); +} + +// error msg changes on coreutils +#[test] +fn copy_identical_file() { + copy_identical_file_impl(false); + copy_identical_file_impl(true); +} + +fn copy_identical_file_impl(progress: bool) { + Playground::setup("ucp_test_15", |_dirs, sandbox| { + sandbox.with_files(vec![EmptyFile("same.txt")]); + + let progress_flag = if progress { "-p" } else { "" }; + + let actual = nu!( + cwd: sandbox.cwd(), + "ucp {} same.txt same.txt", + progress_flag, + ); + // assert!(actual.err.contains("Copy aborted")); + assert!(actual + .err + .contains("'same.txt' and 'same.txt' are the same file")); + }); +} + +#[test] +#[ignore = "File name in progress bar not on uutils impl"] +fn copy_ignores_ansi() { + copy_ignores_ansi_impl(false); + copy_ignores_ansi_impl(true); +} + +fn copy_ignores_ansi_impl(progress: bool) { + Playground::setup("ucp_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", + progress_flag, + ); + assert_eq!(actual.out, "success.txt"); + }); +} + +//apparently on windows error msg is different, but linux(where i test) is fine. +//fix later +#[cfg(unix)] +#[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("ucp_test_17", |_dirs, sandbox| { + sandbox.with_files(vec![EmptyFile("valid.txt")]); + + let progress_flag = if progress { "-p" } else { "" }; + + let actual = nu!( + cwd: sandbox.cwd(), + "ucp {} valid.txt ~/invalid_dir/invalid_dir1", + progress_flag, + ); + assert!( + actual.err.contains("invalid_dir1") && actual.err.contains("No such file or directory") + ); + }); +} + +//again slightly different error message on windows on tests +// compared to linux +#[test] +#[ignore] //FIXME: This test needs to be re-enabled once uu_cp has fixed the bug +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("ucp_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(), + "ucp {} valid.txt invalid_prem.txt", + progress_flag, + ); + assert!(actual.err.contains("invalid_prem.txt") && actual.err.contains("denied")); + }); +} + +// uutils/coreutils copy tests +static TEST_EXISTING_FILE: &str = "existing_file.txt"; +static TEST_HELLO_WORLD_SOURCE: &str = "hello_world.txt"; +static TEST_HELLO_WORLD_DEST: &str = "copy_of_hello_world.txt"; +static TEST_HOW_ARE_YOU_SOURCE: &str = "how_are_you.txt"; +static TEST_HOW_ARE_YOU_DEST: &str = "hello_dir/how_are_you.txt"; +static TEST_COPY_TO_FOLDER: &str = "hello_dir/"; +static TEST_COPY_TO_FOLDER_FILE: &str = "hello_dir/hello_world.txt"; +static TEST_COPY_FROM_FOLDER: &str = "hello_dir_with_file/"; +static TEST_COPY_FROM_FOLDER_FILE: &str = "hello_dir_with_file/hello_world.txt"; +static TEST_COPY_TO_FOLDER_NEW: &str = "hello_dir_new"; +static TEST_COPY_TO_FOLDER_NEW_FILE: &str = "hello_dir_new/hello_world.txt"; + +#[test] +fn test_cp_cp() { + Playground::setup("ucp_test_19", |dirs, _| { + let src = dirs.fixtures.join("cp").join(TEST_HELLO_WORLD_SOURCE); + + // Get the hash of the file content to check integrity after copy. + let src_hash = get_file_hash(src.display()); + + nu!( + cwd: dirs.root(), + "ucp {} ucp_test_19/{}", + src.display(), + TEST_HELLO_WORLD_DEST + ); + + assert!(dirs.test().join(TEST_HELLO_WORLD_DEST).exists()); + + // Get the hash of the copied file content to check against first_hash. + let after_cp_hash = get_file_hash(dirs.test().join(TEST_HELLO_WORLD_DEST).display()); + assert_eq!(src_hash, after_cp_hash); + }); +} + +#[test] +fn test_cp_existing_target() { + Playground::setup("ucp_test_20", |dirs, _| { + let src = dirs.fixtures.join("cp").join(TEST_HELLO_WORLD_SOURCE); + let existing = dirs.fixtures.join("cp").join(TEST_EXISTING_FILE); + + // Get the hash of the file content to check integrity after copy. + let src_hash = get_file_hash(src.display()); + + // Copy existing file to destination, so that it exists for the test + nu!( + cwd: dirs.root(), + "ucp {} ucp_test_20/{}", + existing.display(), + TEST_EXISTING_FILE + ); + + // At this point the src and existing files should be different + assert!(dirs.test().join(TEST_EXISTING_FILE).exists()); + + // Now for the test + nu!( + cwd: dirs.root(), + "ucp {} ucp_test_20/{}", + src.display(), + TEST_EXISTING_FILE + ); + + assert!(dirs.test().join(TEST_EXISTING_FILE).exists()); + + // Get the hash of the copied file content to check against first_hash. + let after_cp_hash = get_file_hash(dirs.test().join(TEST_EXISTING_FILE).display()); + assert_eq!(src_hash, after_cp_hash); + }); +} + +#[test] +fn test_cp_multiple_files() { + Playground::setup("ucp_test_21", |dirs, sandbox| { + let src1 = dirs.fixtures.join("cp").join(TEST_HELLO_WORLD_SOURCE); + let src2 = dirs.fixtures.join("cp").join(TEST_HOW_ARE_YOU_SOURCE); + + // Get the hash of the file content to check integrity after copy. + let src1_hash = get_file_hash(src1.display()); + let src2_hash = get_file_hash(src2.display()); + + //Create target directory + sandbox.mkdir(TEST_COPY_TO_FOLDER); + + // Start test + nu!( + cwd: dirs.root(), + "ucp {} {} ucp_test_21/{}", + src1.display(), + src2.display(), + TEST_COPY_TO_FOLDER + ); + + assert!(dirs.test().join(TEST_COPY_TO_FOLDER).exists()); + + // Get the hash of the copied file content to check against first_hash. + let after_cp_1_hash = get_file_hash(dirs.test().join(TEST_COPY_TO_FOLDER_FILE).display()); + let after_cp_2_hash = get_file_hash(dirs.test().join(TEST_HOW_ARE_YOU_DEST).display()); + assert_eq!(src1_hash, after_cp_1_hash); + assert_eq!(src2_hash, after_cp_2_hash); + }); +} + +#[test] +#[cfg(not(target_os = "macos"))] +fn test_cp_recurse() { + Playground::setup("ucp_test_22", |dirs, sandbox| { + // Create the relevant target directories + sandbox.mkdir(TEST_COPY_FROM_FOLDER); + sandbox.mkdir(TEST_COPY_TO_FOLDER_NEW); + let src = dirs + .fixtures + .join("cp") + .join(TEST_COPY_FROM_FOLDER) + .join(TEST_COPY_FROM_FOLDER_FILE); + + let src_hash = get_file_hash(src.display()); + // Start test + nu!( + cwd: dirs.root(), + "ucp -r {} ucp_test_22/{}", + TEST_COPY_FROM_FOLDER, + TEST_COPY_TO_FOLDER_NEW, + ); + let after_cp_hash = get_file_hash(dirs.test().join(TEST_COPY_TO_FOLDER_NEW_FILE).display()); + assert_eq!(src_hash, after_cp_hash); + }); +} + +#[test] +fn test_cp_with_dirs() { + Playground::setup("ucp_test_23", |dirs, sandbox| { + let src = dirs.fixtures.join("cp").join(TEST_HELLO_WORLD_SOURCE); + let src_hash = get_file_hash(src.display()); + + //Create target directory + sandbox.mkdir(TEST_COPY_TO_FOLDER); + // Start test + nu!( + cwd: dirs.root(), + "ucp {} ucp_test_23/{}", + src.display(), + TEST_COPY_TO_FOLDER, + ); + let after_cp_hash = get_file_hash(dirs.test().join(TEST_COPY_TO_FOLDER_FILE).display()); + assert_eq!(src_hash, after_cp_hash); + + // Other way around + sandbox.mkdir(TEST_COPY_FROM_FOLDER); + let src2 = dirs.fixtures.join("cp").join(TEST_COPY_FROM_FOLDER_FILE); + let src2_hash = get_file_hash(src2.display()); + nu!( + cwd: dirs.root(), + "ucp {} ucp_test_23/{}", + src2.display(), + TEST_HELLO_WORLD_DEST, + ); + let after_cp_2_hash = get_file_hash(dirs.test().join(TEST_HELLO_WORLD_DEST).display()); + assert_eq!(src2_hash, after_cp_2_hash); + }); +} +#[cfg(not(windows))] +#[test] +fn test_cp_arg_force() { + Playground::setup("ucp_test_24", |dirs, sandbox| { + let src = dirs.fixtures.join("cp").join(TEST_HELLO_WORLD_SOURCE); + let src_hash = get_file_hash(src.display()); + sandbox.with_files(vec![FileWithPermission("invalid_prem.txt", false)]); + + nu!( + cwd: dirs.root(), + "ucp {} --force ucp_test_24/{}", + src.display(), + "invalid_prem.txt" + ); + let after_cp_hash = get_file_hash(dirs.test().join("invalid_prem.txt").display()); + // Check content was copied by the use of --force + assert_eq!(src_hash, after_cp_hash); + }); +} + +#[test] +fn test_cp_directory_to_itself_disallowed() { + Playground::setup("ucp_test_25", |dirs, sandbox| { + sandbox.mkdir("d"); + let actual = nu!( + cwd: dirs.root(), + "ucp -r ucp_test_25/{} ucp_test_25/{}", + "d", + "d" + ); + actual + .err + .contains("cannot copy a directory, 'd', into itself, 'd/d'"); + }); +} + +#[test] +fn test_cp_nested_directory_to_itself_disallowed() { + Playground::setup("ucp_test_26", |dirs, sandbox| { + sandbox.mkdir("a"); + sandbox.mkdir("a/b"); + sandbox.mkdir("a/b/c"); + let actual = nu!( + cwd: dirs.test(), + "ucp -r {} {}", + "a/b", + "a/b/c" + ); + actual + .err + .contains("cannot copy a directory, 'a/b', into itself, 'a/b/c/b'"); + }); +} + +#[cfg(not(windows))] +#[test] +fn test_cp_same_file_force() { + Playground::setup("ucp_test_27", |dirs, sandbox| { + sandbox.with_files(vec![EmptyFile("f")]); + let actual = nu!( + cwd: dirs.test(), + "ucp --force {} {}", + "f", + "f" + ); + actual.err.contains("cp: 'f' and 'f' are the same file"); + assert!(!dirs.test().join("f~").exists()); + }); +} + +#[test] +fn test_cp_arg_no_clobber() { + Playground::setup("ucp_test_28", |dirs, _| { + let src = dirs.fixtures.join("cp").join(TEST_HELLO_WORLD_SOURCE); + let target = dirs.fixtures.join("cp").join(TEST_HOW_ARE_YOU_SOURCE); + let target_hash = get_file_hash(target.display()); + + let actual = nu!( + cwd: dirs.root(), + "ucp {} {} --no-clobber", + src.display(), + target.display() + ); + let after_cp_hash = get_file_hash(target.display()); + assert!(actual.err.contains("not replacing")); + // Check content was not clobbered + assert_eq!(after_cp_hash, target_hash); + }); +} + +#[test] +fn test_cp_arg_no_clobber_twice() { + Playground::setup("ucp_test_29", |dirs, sandbox| { + sandbox.with_files(vec![ + EmptyFile("source.txt"), + FileWithContent("source_with_body.txt", "some-body"), + ]); + nu!( + cwd: dirs.root(), + "ucp --no-clobber ucp_test_29/{} ucp_test_29/{}", + "source.txt", + "dest.txt" + ); + assert!(dirs.test().join("dest.txt").exists()); + + nu!( + cwd: dirs.root(), + "ucp --no-clobber ucp_test_29/{} ucp_test_29/{}", + "source_with_body.txt", + "dest.txt" + ); + // Should have same contents of original empty file as --no-clobber should not overwrite dest.txt + assert_eq!(file_contents(dirs.test().join("dest.txt")), "fake data"); + }); +} + +#[test] +fn test_cp_debug_default() { + Playground::setup("ucp_test_30", |dirs, _| { + let src = dirs.fixtures.join("cp").join(TEST_HELLO_WORLD_SOURCE); + + let actual = nu!( + cwd: dirs.root(), + "ucp --debug {} ucp_test_30/{}", + src.display(), + TEST_HELLO_WORLD_DEST + ); + #[cfg(target_os = "macos")] + if !actual + .out + .contains("copy offload: unknown, reflink: unsupported, sparse detection: unsupported") + { + panic!("{}", format!("Failure: stdout was \n{}", actual.out)); + } + #[cfg(target_os = "linux")] + if !actual + .out + .contains("copy offload: unknown, reflink: unsupported, sparse detection: no") + { + panic!("{}", format!("Failure: stdout was \n{}", actual.out)); + } + + #[cfg(windows)] + if !actual.out.contains( + "copy offload: unsupported, reflink: unsupported, sparse detection: unsupported", + ) { + panic!("{}", format!("Failure: stdout was \n{}", actual.out)); + } + }); +} + +#[test] +fn test_cp_verbose_default() { + Playground::setup("ucp_test_31", |dirs, _| { + let src = dirs.fixtures.join("cp").join(TEST_HELLO_WORLD_SOURCE); + + let actual = nu!( + cwd: dirs.root(), + "ucp --verbose {} ucp_test_31/{}", + src.display(), + TEST_HELLO_WORLD_DEST + ); + assert!(actual.out.contains( + format!( + "'{}' -> 'ucp_test_31/{}'", + src.display(), + TEST_HELLO_WORLD_DEST + ) + .as_str(), + )); + }); +} + +#[test] +fn test_cp_only_source_no_dest() { + Playground::setup("ucp_test_32", |dirs, _| { + let src = dirs.fixtures.join("cp").join(TEST_HELLO_WORLD_SOURCE); + let actual = nu!( + cwd: dirs.root(), + "ucp {}", + src.display(), + ); + assert!(actual + .err + .contains("Missing destination path operand after")); + assert!(actual.err.contains(TEST_HELLO_WORLD_SOURCE)); + }); +} diff --git a/tests/fixtures/cp/dir_with_10_files/0 b/tests/fixtures/cp/dir_with_10_files/0 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/fixtures/cp/dir_with_10_files/1 b/tests/fixtures/cp/dir_with_10_files/1 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/fixtures/cp/dir_with_10_files/2 b/tests/fixtures/cp/dir_with_10_files/2 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/fixtures/cp/dir_with_10_files/3 b/tests/fixtures/cp/dir_with_10_files/3 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/fixtures/cp/dir_with_10_files/4 b/tests/fixtures/cp/dir_with_10_files/4 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/fixtures/cp/dir_with_10_files/5 b/tests/fixtures/cp/dir_with_10_files/5 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/fixtures/cp/dir_with_10_files/6 b/tests/fixtures/cp/dir_with_10_files/6 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/fixtures/cp/dir_with_10_files/7 b/tests/fixtures/cp/dir_with_10_files/7 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/fixtures/cp/dir_with_10_files/8 b/tests/fixtures/cp/dir_with_10_files/8 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/fixtures/cp/dir_with_10_files/9 b/tests/fixtures/cp/dir_with_10_files/9 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/fixtures/cp/dir_with_mount/copy_me.txt b/tests/fixtures/cp/dir_with_mount/copy_me.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/fixtures/cp/dir_with_mount/copy_me/copy_me.txt b/tests/fixtures/cp/dir_with_mount/copy_me/copy_me.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/fixtures/cp/existing_file.txt b/tests/fixtures/cp/existing_file.txt new file mode 100644 index 0000000000..651b4c7b0b --- /dev/null +++ b/tests/fixtures/cp/existing_file.txt @@ -0,0 +1 @@ +Cogito ergo sum. diff --git a/tests/fixtures/cp/hello_dir/hello.txt b/tests/fixtures/cp/hello_dir/hello.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/fixtures/cp/hello_dir_with_file/hello_world.txt b/tests/fixtures/cp/hello_dir_with_file/hello_world.txt new file mode 100644 index 0000000000..8ab686eafe --- /dev/null +++ b/tests/fixtures/cp/hello_dir_with_file/hello_world.txt @@ -0,0 +1 @@ +Hello, World! diff --git a/tests/fixtures/cp/hello_world.txt b/tests/fixtures/cp/hello_world.txt new file mode 100644 index 0000000000..8ab686eafe --- /dev/null +++ b/tests/fixtures/cp/hello_world.txt @@ -0,0 +1 @@ +Hello, World! diff --git a/tests/fixtures/cp/how_are_you.txt b/tests/fixtures/cp/how_are_you.txt new file mode 100644 index 0000000000..d18c6b11fc --- /dev/null +++ b/tests/fixtures/cp/how_are_you.txt @@ -0,0 +1 @@ +How are you?