use std::fs::{create_dir, metadata, read, write, DirEntry, FileType};
use std::io;
use std::path::{Path, PathBuf};
use tempfile::TempDir;
use cp_r::*;
#[test]
fn basic_copy() {
let src = tempfile::tempdir().unwrap();
let dest = tempfile::tempdir().unwrap();
let file_content = b"hello world\n";
let file_name = "a file";
let src_file_path = src.path().join(file_name);
write(&src_file_path, file_content).unwrap();
let stats = CopyOptions::default()
.copy_tree(src.path(), dest.path())
.unwrap();
let dest_file_path = &dest.path().join(file_name);
assert_eq!(read(dest_file_path).unwrap(), file_content);
assert_eq!(stats.files, 1);
assert_eq!(stats.file_bytes, file_content.len() as u64);
let src_mtime = metadata(&src_file_path).unwrap().modified().unwrap();
let dest_mtime = metadata(dest_file_path).unwrap().modified().unwrap();
dbg!(src_mtime, dest_mtime);
let d = dest_mtime
.duration_since(src_mtime)
.or_else(|_| src_mtime.duration_since(dest_mtime))
.unwrap();
dbg!(d);
assert!(d.as_micros() < 1000);
}
#[test]
fn subdirs() {
let src = TempDir::new().unwrap();
let dest = TempDir::new().unwrap();
create_dir(src.path().join("a")).unwrap();
create_dir(src.path().join("a").join("aa")).unwrap();
create_dir(src.path().join("b")).unwrap();
create_dir(src.path().join("b/bb")).unwrap();
let file_content = b"some file content\n";
write(src.path().join("a/aa/aaafile"), file_content).unwrap();
let stats = CopyOptions::default().copy_tree(&src, &dest).unwrap();
assert_eq!(
read(dest.path().join("a/aa/aaafile")).unwrap(),
file_content
);
assert!(metadata(dest.path().join("b/bb"))
.unwrap()
.file_type()
.is_dir());
assert_eq!(stats.files, 1);
assert_eq!(stats.file_bytes, file_content.len() as u64);
assert_eq!(stats.dirs, 4);
}
#[test]
fn clean_error_on_nonexistent_source() {
let dest = tempfile::tempdir().unwrap();
let err = CopyOptions::new().copy_tree("nothing", &dest).unwrap_err();
println!("err = {:#?}", err);
assert!(err.path().starts_with("nothing"));
assert_eq!(err.kind(), ErrorKind::ReadDir);
assert_eq!(err.io_error().unwrap().kind(), io::ErrorKind::NotFound);
}
#[test]
fn create_destination_by_default() {
let empty_src = tempfile::tempdir().unwrap();
let dest_parent = tempfile::tempdir().unwrap();
let dest = dest_parent.path().join("nonexistent_child");
let stats = CopyOptions::new()
.copy_tree(empty_src.path(), &dest)
.unwrap();
assert!(dest.is_dir());
assert_eq!(stats.dirs, 1);
assert_eq!(stats.files, 0);
}
#[test]
fn create_destination_when_requested() {
let empty_src = tempfile::tempdir().unwrap();
let dest_parent = tempfile::tempdir().unwrap();
let dest = dest_parent.path().join("nonexistent_child");
let stats = CopyOptions::new()
.create_destination(true)
.copy_tree(&empty_src, &dest)
.unwrap();
assert!(dest.is_dir());
assert_eq!(stats.dirs, 1);
assert_eq!(stats.files, 0);
}
#[test]
fn optionally_destination_must_exist() {
let dest_parent = tempfile::tempdir().unwrap();
let dest = dest_parent.path().join("nonexistent_child");
let err = CopyOptions::new()
.create_destination(false)
.copy_tree("src", &dest)
.unwrap_err();
println!("err = {:#?}", err);
assert_eq!(err.kind(), ErrorKind::DestinationDoesNotExist);
assert!(
err.path().starts_with(&dest),
"path in the error relates to the destination"
);
assert!(err.io_error().is_none(), "no underlying io::Error");
}
#[cfg(unix)]
#[test]
fn clean_error_failing_to_copy_devices() {
let dest = tempfile::tempdir().unwrap();
let err = CopyOptions::new()
.copy_tree("/dev", dest.path())
.unwrap_err();
println!("{:#?}", err);
let kind = err.kind();
assert!(
kind == ErrorKind::UnsupportedFileType || kind == ErrorKind::CopyFile,
"unexpected ErrorKind {:?}",
kind
);
assert!(err.path().strip_prefix("/dev/").is_ok());
let formatted = format!("{}", err);
assert!(
formatted.starts_with("unsupported file type: /dev/")
|| formatted.contains(
"the source path is neither a regular file nor a symlink to a regular file"
),
"unexpected string format: {:?}",
formatted
);
}
#[cfg(unix)]
#[test]
fn copy_dangling_symlink() {
let src = tempfile::tempdir().unwrap();
let dest = tempfile::tempdir().unwrap();
std::os::unix::fs::symlink("dangling target", src.path().join("a_link")).unwrap();
let stats = CopyOptions::new()
.copy_tree(src.path(), dest.path())
.unwrap();
println!("{:#?}", stats);
assert_eq!(
stats,
CopyStats {
files: 0,
dirs: 0,
symlinks: 1,
file_bytes: 0,
filtered_out: 0,
}
);
}
#[test]
fn filter_by_path() {
let src = tempfile::tempdir().unwrap();
let dest = tempfile::tempdir().unwrap();
create_dir(src.path().join("a")).unwrap();
create_dir(src.path().join("b")).unwrap();
create_dir(src.path().join("b/bb")).unwrap();
create_dir(src.path().join("a").join("aa")).unwrap();
let file_content = b"some file content\n";
write(src.path().join("a/aa/aaafile"), file_content).unwrap();
fn not_b(path: &Path, _: &DirEntry) -> cp_r::Result<bool> {
Ok(path != Path::new("b"))
}
let stats = CopyOptions::new()
.filter(not_b)
.copy_tree(src.path(), dest.path())
.unwrap();
assert_eq!(
read(dest.path().join("a/aa/aaafile")).unwrap(),
file_content
);
assert!(!dest.path().join("b").exists());
assert_eq!(
stats,
CopyStats {
files: 1,
file_bytes: file_content.len() as u64,
dirs: 2,
symlinks: 0,
filtered_out: 1,
}
);
}
const AAA_CONTENT: &[u8] = b"some file content\n";
fn setup_a_b_src() -> tempfile::TempDir {
let src = tempfile::tempdir().unwrap();
create_dir(src.path().join("a")).unwrap();
create_dir(src.path().join("b")).unwrap();
create_dir(src.path().join("b/bb")).unwrap();
create_dir(src.path().join("a").join("aa")).unwrap();
let file_content = AAA_CONTENT;
write(src.path().join("a/aa/aaafile"), file_content).unwrap();
src
}
#[test]
fn filter_by_mut_closure() {
let src = setup_a_b_src();
let dest = tempfile::tempdir().unwrap();
let mut filter_seen_paths: Vec<String> = Vec::new();
let stats = CopyOptions::default()
.filter(|path: &Path, _de| {
filter_seen_paths.push(path.to_str().unwrap().replace('\\', "/"));
Ok(path != Path::new("b"))
})
.copy_tree(src.path(), dest.path())
.unwrap();
assert_eq!(read(dest.path().join("a/aa/aaafile")).unwrap(), AAA_CONTENT,);
assert!(!dest.path().join("b").exists());
assert_eq!(
stats,
CopyStats {
files: 1,
file_bytes: AAA_CONTENT.len() as u64,
dirs: 2,
symlinks: 0,
filtered_out: 1,
}
);
filter_seen_paths.sort_unstable();
assert_eq!(filter_seen_paths, ["a", "a/aa", "a/aa/aaafile", "b"]);
}
#[test]
fn after_entry_copied_callback() {
let src = setup_a_b_src();
let dest = tempfile::tempdir().unwrap();
let mut progress_seen: Vec<(PathBuf, FileType)> = Vec::new();
let mut last_stats = CopyStats::default();
let final_stats = CopyOptions::new()
.after_entry_copied(|p, ft, stats| {
assert!(
!progress_seen.iter().any(|(pp, _)| pp == p),
"filename has not been seen before"
);
progress_seen.push((p.to_owned(), *ft));
if ft.is_file() {
assert_eq!(stats.files, last_stats.files + 1);
} else if ft.is_dir() {
assert_eq!(stats.dirs, last_stats.dirs + 1);
} else {
panic!("unexpected file type {:?}", ft);
}
last_stats = stats.clone();
Ok(())
})
.copy_tree(src.path(), dest.path())
.unwrap();
assert_eq!(
last_stats, final_stats,
"progress after the final copy include stats equal to the overall final stats"
);
}
#[test]
fn after_entry_callback_error_terminates_copy() {
let src = setup_a_b_src();
let dest = tempfile::tempdir().unwrap();
let options = CopyOptions::new().after_entry_copied(|p, ft, _stats| {
if ft.is_file() {
Err(Error::new(ErrorKind::Interrupted, p))
} else {
Ok(())
}
});
let result = options.copy_tree(src.path(), dest.path());
let err = result.unwrap_err();
let err_str = err.to_string();
assert!(
err_str.starts_with("interrupted"),
"unexpected err_str: {:?}",
err_str
);
let err_debug = format!("{:?}", err);
assert!(
err_debug.starts_with("Error") && err_debug.contains("kind: Interrupted"),
"unexpected err_debug: {:?}",
err_debug
);
}