use super::*;
use crate::fs_beneath::{identity_at, open_root};
use crate::test_support::{fixture, remove};
fn put(root: &Path, path: &str, bytes: &[u8]) {
let full: PathBuf = root.join(path);
std::fs::create_dir_all(full.parent().expect("parent")).expect("directories");
std::fs::write(full, bytes).expect("file");
}
#[test]
fn a_tree_with_a_read_only_directory_and_a_link_is_removed_without_touching_the_link_target() {
let root: PathBuf = fixture("trash-remove-tree");
put(root.as_path(), "top/a/file", b"a");
put(root.as_path(), "top/a/b/deep", b"b");
put(root.as_path(), "top/locked/file", b"locked");
put(root.as_path(), "precious/data", b"keep");
std::os::unix::fs::symlink(root.join("precious"), root.join("top/a/link")).expect("link");
std::fs::set_permissions(root.join("top/locked"), std::os::unix::fs::PermissionsExt::from_mode(0o555)).expect("read-only directory");
let held: std::fs::File = open_root(root.as_path()).expect("root");
let removal: Removal = remove_tree_beneath(root.as_path(), &held, std::ffi::OsStr::new("top"), None).expect("removed");
assert_eq!(removal, Removal::Removed);
assert!(!root.join("top").exists(), "the whole tree is gone");
assert!(root.join("precious/data").exists(), "what the link pointed at is untouched");
remove(root.as_path());
}
#[test]
fn a_name_with_nothing_at_it_is_absent() {
let root: PathBuf = fixture("trash-remove-absent");
let held: std::fs::File = open_root(root.as_path()).expect("root");
assert_eq!(remove_tree_beneath(root.as_path(), &held, std::ffi::OsStr::new("nothing"), None), Ok(Removal::Absent));
remove(root.as_path());
}
#[test]
fn a_link_at_the_name_is_refused_and_not_followed() {
let root: PathBuf = fixture("trash-remove-link");
put(root.as_path(), "precious/data", b"keep");
std::os::unix::fs::symlink(root.join("precious"), root.join("top")).expect("link");
let held: std::fs::File = open_root(root.as_path()).expect("root");
let reason: String = remove_tree_beneath(root.as_path(), &held, std::ffi::OsStr::new("top"), None).expect_err("a link is no directory to empty");
assert!(reason.contains("top"), "{reason}");
assert!(root.join("precious/data").exists());
remove(root.as_path());
}
#[test]
fn a_directory_that_is_not_the_expected_object_is_left_alone() {
let root: PathBuf = fixture("trash-remove-identity");
put(root.as_path(), "first/file", b"first");
put(root.as_path(), "second/file", b"second");
let held: std::fs::File = open_root(root.as_path()).expect("root");
let first = identity_at(&held, std::ffi::OsStr::new("first")).expect("identity");
let reason: String = remove_tree_beneath(root.as_path(), &held, std::ffi::OsStr::new("second"), Some(first)).expect_err("another object");
assert!(reason.contains("second") && reason.contains(first.encode().as_str()), "{reason}");
assert!(root.join("second/file").exists(), "the other directory is untouched");
assert_eq!(remove_tree_beneath(root.as_path(), &held, std::ffi::OsStr::new("first"), Some(first)), Ok(Removal::Removed));
remove(root.as_path());
}
#[test]
fn a_very_deep_chain_of_directories_is_removed() {
let root: PathBuf = fixture("trash-remove-deep");
let held: std::fs::File = open_root(root.as_path()).expect("root");
let mut current: std::fs::File = held.try_clone().expect("clone");
crate::fs_beneath::make_directory_at(¤t, std::ffi::OsStr::new("top"), 0o755).expect("top");
current = crate::fs_beneath::open_directory_at(¤t, std::ffi::OsStr::new("top")).expect("open top");
for _ in 0..300 {
crate::fs_beneath::make_directory_at(¤t, std::ffi::OsStr::new("d"), 0o755).expect("level");
current = crate::fs_beneath::open_directory_at(¤t, std::ffi::OsStr::new("d")).expect("open level");
}
drop(current);
assert_eq!(remove_tree_beneath(root.as_path(), &held, std::ffi::OsStr::new("top"), None), Ok(Removal::Removed));
assert!(!root.join("top").exists());
remove(root.as_path());
}