use camino::{Utf8Path, Utf8PathBuf};
use rstest::{fixture, rstest};
use tempfile::TempDir;
use super::AmbientPathProbe;
#[cfg(unix)]
use crate::select::policy::ProbeFailureKind;
use crate::select::{
extensions::ExtensionFilter,
policy::{CandidatePath, PathKind, PathProbe, select_files},
};
fn at(path: &str) -> CandidatePath { CandidatePath::new(path.to_owned()) }
fn as_path(directory: &TempDir) -> Utf8PathBuf {
Utf8PathBuf::from_path_buf(directory.path().to_path_buf()).expect("a UTF-8 temporary directory")
}
#[test_macros::allow_fixture_expansion_lints]
#[fixture]
fn temp_root() -> TempDir { tempfile::tempdir().expect("a temporary directory") }
#[cfg(unix)]
#[test_macros::allow_fixture_expansion_lints]
#[fixture]
fn elsewhere_root() -> TempDir { tempfile::tempdir().expect("a second temporary directory") }
fn write(root: &Utf8Path, name: &str, content: &str) {
let path = root.join(name);
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent).expect("create the fixture directory");
}
std::fs::write(&path, content).expect("write a fixture");
}
fn probe(root: &Utf8Path, path: &str) -> PathKind {
AmbientPathProbe::new(root)
.probe(&at(path))
.expect("every fixture path is one the probe can read")
}
fn selected_in(root: &Utf8Path, candidates: &[CandidatePath]) -> Vec<CandidatePath> {
select_files(
candidates,
&ExtensionFilter::default(),
&AmbientPathProbe::new(root),
)
.expect("every fixture candidate is one the probe can read")
}
#[rstest]
fn a_regular_file_is_identified_by_an_absolute_canonical_path(temp_root: TempDir) {
let root = as_path(&temp_root);
write(&root, "docs/guide.md", "|A|B|\n");
let PathKind::RegularFile(identity) = probe(&root, "docs/guide.md") else {
panic!("docs/guide.md is a regular file");
};
assert!(
Utf8Path::new(identity.as_str()).is_absolute(),
"{identity:?}"
);
assert!(
Utf8Path::new(identity.as_str()).ends_with(Utf8Path::new("docs").join("guide.md")),
"{identity:?}"
);
assert_eq!(
Utf8Path::new(identity.as_str()).file_name(),
Some("guide.md")
);
}
#[cfg(unix)]
#[rstest]
fn a_symlink_is_a_link_even_when_its_target_is_a_regular_file(temp_root: TempDir) {
let root = as_path(&temp_root);
write(&root, "src/lib.rs", "fn main() {}\n");
std::os::unix::fs::symlink("src/lib.rs", root.join("alias.md"))
.expect("create the fixture symlink");
assert!(
std::fs::metadata(root.join("alias.md"))
.expect("follow the fixture symlink")
.is_file(),
"the link must point at a regular file, or the case proves nothing"
);
assert_eq!(probe(&root, "alias.md"), PathKind::Symlink);
}
#[cfg(unix)]
#[rstest]
fn a_candidate_behind_a_symlinked_directory_is_outside_the_root(
temp_root: TempDir,
elsewhere_root: TempDir,
) {
let root = as_path(&temp_root);
let elsewhere = as_path(&elsewhere_root);
write(&elsewhere, "guide.md", "|A|B|\n");
std::os::unix::fs::symlink(&elsewhere, root.join("docs")).expect("link the fixture directory");
assert!(
elsewhere.join("guide.md").is_file(),
"the link must point at a regular file, or the case proves nothing"
);
assert_eq!(probe(&root, "docs/guide.md"), PathKind::OutsideRoot);
let selected = selected_in(&root, &[at("docs/guide.md")]);
assert!(
selected.is_empty(),
"a candidate that leaves the tree is not selected, got {selected:?}"
);
}
#[cfg(unix)]
#[rstest]
fn a_candidate_behind_an_in_tree_link_is_confined(temp_root: TempDir) {
let root = as_path(&temp_root);
write(&root, "real/guide.md", "|A|B|\n");
std::os::unix::fs::symlink("real", root.join("docs")).expect("link the fixture directory");
assert!(
matches!(probe(&root, "docs/guide.md"), PathKind::RegularFile(_)),
"a link to a directory inside the tree is still inside the tree"
);
}
#[cfg(unix)]
#[rstest]
fn a_root_reached_through_a_link_still_confines_its_candidates(temp_root: TempDir) {
let root = as_path(&temp_root);
write(&root, "real/docs/guide.md", "|A|B|\n");
std::os::unix::fs::symlink("real", root.join("link")).expect("link the fixture root");
assert!(
matches!(
probe(&root.join("link"), "docs/guide.md"),
PathKind::RegularFile(_)
),
"a root named through a link is the same tree as the one it names"
);
}
#[rstest]
fn an_absent_path_is_missing(temp_root: TempDir) {
let root = as_path(&temp_root);
assert_eq!(probe(&root, "docs/gone.md"), PathKind::Missing);
assert_eq!(
probe(&root, "docs/gone.md/deeper.md"),
PathKind::Missing,
"a path whose parent is absent is not a regular file"
);
}
#[rstest]
fn a_directory_is_neither_a_regular_file_nor_a_symlink(temp_root: TempDir) {
let root = as_path(&temp_root);
std::fs::create_dir_all(root.join("docs")).expect("create the fixture directory");
assert_eq!(probe(&root, "docs"), PathKind::Other);
}
#[cfg(unix)]
#[rstest]
fn a_symbolic_link_loop_is_an_error_rather_than_a_missing_file(temp_root: TempDir) {
let root = as_path(&temp_root);
std::os::unix::fs::symlink("b", root.join("a")).expect("create the fixture symlink");
std::os::unix::fs::symlink("a", root.join("b")).expect("create the fixture symlink");
let error = AmbientPathProbe::new(&root)
.probe(&at("a/guide.md"))
.expect_err("a link loop is not an absence, and not an answer");
assert_eq!(error.path, at("a/guide.md"));
assert_eq!(error.kind, ProbeFailureKind::Unreadable);
}
#[rstest]
fn an_absolute_candidate_is_probed_where_it_points(temp_root: TempDir) {
let root = as_path(&temp_root);
write(&root, "docs/guide.md", "|A|B|\n");
let absolute = root.join("docs/guide.md");
assert_eq!(
probe(&root, absolute.as_str()),
probe(&root, "docs/guide.md"),
"a candidate is not re-rooted when it is already absolute"
);
}
#[rstest]
fn selection_over_a_real_tree_takes_regular_files_alone(temp_root: TempDir) {
let root = as_path(&temp_root);
write(&root, "docs/guide.md", "|A|B|\n");
std::fs::create_dir_all(root.join("docs/subdir.md"))
.expect("create a directory named like a document");
let selected = selected_in(
&root,
&[
at("docs/guide.md"),
at("docs/subdir.md"),
at("docs/gone.md"),
at("draft.markdown"),
],
);
assert_eq!(
selected,
vec![at("docs/guide.md")],
"a directory named like a document, an absent file, and a name with a configured \
extension that does not exist are all excluded"
);
}
#[cfg(unix)]
#[rstest]
fn two_hard_links_are_two_identities_and_neither_is_dropped(temp_root: TempDir) {
use std::os::unix::fs::MetadataExt;
let root = as_path(&temp_root);
write(&root, "a.md", "|A|B|\n");
std::fs::hard_link(root.join("a.md"), root.join("b.md")).expect("create the hard link");
let first = std::fs::metadata(root.join("a.md")).expect("read the fixture");
let second = std::fs::metadata(root.join("b.md")).expect("read the fixture");
assert_eq!(
first.ino(),
second.ino(),
"the fixture must present one inode under two names"
);
assert_eq!(first.nlink(), 2, "the fixture must be a hard-link pair");
assert_ne!(
probe(&root, "a.md"),
probe(&root, "b.md"),
"two directory entries must keep two identities"
);
let selected = selected_in(&root, &[at("a.md"), at("b.md")]);
assert_eq!(
selected,
vec![at("a.md"), at("b.md")],
"neither link may be dropped, or formatting it would leave the other stale"
);
}