Tests: findfiles tests remove the folder trees they make

tree() in crates/engine/src/cmd/findfiles/tests.rs returns a Tree guard
that holds the canonical path and removes the folder with everything in
it when it is dropped, also when the test panics. When the removal
fails, the guard gives the owner read, write and search permission on
every folder in the tree (Unix; links are not followed) and removes it
again, so a test that panics while a folder is locked leaves nothing
behind. A removal that still fails is ignored.

tree() empties the folder temp_dir returns in place instead of removing
and remaking it, so on Unix the folder keeps the owner-only mode that
temp_dir sets.
This commit is contained in:
dotCooCoo
2026-11-12 10:38:06 -08:00
parent a15a4abc5c
commit c7780becb3
+103 -6
View File
@@ -43,12 +43,62 @@ impl Visitor for Blocked {
}
}
/// A test's folder tree, removed with everything in it when dropped, also when the test panics,
/// and also when the test locked a folder in it. A removal that fails (a walker still holding a
/// file open) is ignored.
struct Tree(PathBuf);
impl std::ops::Deref for Tree {
type Target = PathBuf;
fn deref(&self) -> &PathBuf {
&self.0
}
}
impl Drop for Tree {
fn drop(&mut self) {
if std::fs::remove_dir_all(&self.0).is_err() {
unlock(&self.0);
let _ = std::fs::remove_dir_all(&self.0);
}
}
}
/// Gives the owner read, write and search permission on `folder` and on every folder in it, so
/// that a tree holding a folder a test locked can be removed. Links are not followed.
#[cfg(unix)]
fn unlock(folder: &Path) {
use std::os::unix::fs::PermissionsExt;
let Ok(meta) = std::fs::symlink_metadata(folder) else { return };
if !meta.is_dir() {
return;
}
let mode = meta.permissions().mode() & 0o7777;
if mode & 0o700 != 0o700 {
let _ = std::fs::set_permissions(folder, std::fs::Permissions::from_mode(mode | 0o700));
}
let Ok(entries) = std::fs::read_dir(folder) else { return };
for entry in entries.flatten() {
if entry.file_type().is_ok_and(|t| t.is_dir()) {
unlock(&entry.path());
}
}
}
/// Folders on other systems are removed as they are.
#[cfg(not(unix))]
fn unlock(_: &Path) {}
/// A fresh folder (canonical, as a search reports it) holding `paths`: files, and folders where
/// they end in `/`.
fn tree(tag: &str, paths: &[&str]) -> PathBuf {
let root = vectorcraft_testkit::temp_dir(&format!("findfiles-{tag}"));
let _ = std::fs::remove_dir_all(&root);
std::fs::create_dir_all(&root).unwrap();
/// they end in `/`. The folder is removed when the returned [`Tree`] is dropped.
fn tree(tag: &str, paths: &[&str]) -> Tree {
let mut root = Tree(vectorcraft_testkit::temp_dir(&format!("findfiles-{tag}")));
// Empties what a tree whose removal failed left, keeping the folder as `temp_dir` made it
// (owner-only on Unix).
for entry in std::fs::read_dir(&root.0).unwrap().flatten() {
let path = entry.path();
let _ = if entry.file_type().is_ok_and(|t| t.is_dir()) { std::fs::remove_dir_all(&path) } else { std::fs::remove_file(&path) };
}
for p in paths {
let path = root.join(p);
if p.ends_with('/') {
@@ -58,7 +108,54 @@ fn tree(tag: &str, paths: &[&str]) -> PathBuf {
std::fs::write(&path, b"font").unwrap();
}
}
plain(std::fs::canonicalize(&root).unwrap())
root.0 = plain(std::fs::canonicalize(&root.0).unwrap());
root
}
#[test]
fn a_tree_is_removed_when_its_test_ends() {
let path = {
let root = tree("removed", &["a/b/one.ttf", "c/"]);
assert!(root.join("a/b/one.ttf").is_file() && root.join("c").is_dir());
root.to_path_buf()
};
assert!(!path.exists(), "{} is left", path.display());
}
#[test]
fn a_tree_is_removed_when_its_test_panics() {
let mut path = PathBuf::new();
let r = vectorcraft_testkit::catch_quiet::<()>(|| {
let root = tree("unwound", &["a/b/one.ttf"]);
path = root.to_path_buf();
panic!("a failed assertion");
});
assert_eq!(r.err().as_deref(), Some("a failed assertion"));
assert!(!path.as_os_str().is_empty() && !path.exists(), "{} is left", path.display());
}
#[cfg(unix)]
#[test]
fn a_tree_is_private_to_this_user() {
use std::os::unix::fs::PermissionsExt;
let root = tree("private", &["a/one.ttf"]);
let mode = std::fs::metadata(&*root).unwrap().permissions().mode();
assert_eq!(mode & 0o077, 0, "{}: mode {mode:o}", root.display());
}
#[cfg(unix)]
#[test]
fn a_tree_with_a_locked_folder_is_removed_when_its_test_panics() {
use std::os::unix::fs::PermissionsExt;
let mut path = PathBuf::new();
let r = vectorcraft_testkit::catch_quiet::<()>(|| {
let root = tree("locked", &["a/locked/b/one.ttf"]);
path = root.to_path_buf();
std::fs::set_permissions(root.join("a/locked"), std::fs::Permissions::from_mode(0o000)).unwrap();
panic!("a failed assertion");
});
assert_eq!(r.err().as_deref(), Some("a failed assertion"));
assert!(!path.as_os_str().is_empty() && !path.exists(), "{} is left", path.display());
}
/// The rules of a search of `root`: its `Users/me` is the home folder, `refused` an app's folder.