use std::path::{Path, PathBuf};
const TARGET_ROOTS: [&str; 4] = ["src", "tests", "examples", "benches"];
pub fn unit_reads_manifest_dir(pkg_dir: &Path, src_path: &Path, readers: &[PathBuf]) -> bool {
if readers.is_empty() {
return false;
}
let roots: Vec<PathBuf> = TARGET_ROOTS.iter().map(|d| pkg_dir.join(d)).collect();
if readers
.iter()
.any(|r| !roots.iter().any(|root| r.starts_with(root)))
{
return true;
}
let Some(scope) = TargetScope::of(pkg_dir, src_path) else {
return true;
};
readers.iter().any(|r| scope.contains(r))
}
enum TargetScope {
Library { src: PathBuf, bin: PathBuf },
File { file: PathBuf, dir: PathBuf },
}
impl TargetScope {
fn of(pkg_dir: &Path, src_path: &Path) -> Option<Self> {
let src = pkg_dir.join("src");
let bin = src.join("bin");
if src_path.starts_with(&bin) {
return Some(Self::sibling(src_path, &bin));
}
if src_path.starts_with(&src) {
return Some(Self::Library { src, bin });
}
for root in ["tests", "examples", "benches"] {
let root = pkg_dir.join(root);
if src_path.starts_with(&root) {
return Some(Self::sibling(src_path, &root));
}
}
None
}
fn sibling(src_path: &Path, root: &Path) -> Self {
let parent = src_path.parent().unwrap_or(root);
let dir = if parent == root {
root.join(src_path.file_stem().unwrap_or_default())
} else {
parent.to_path_buf()
};
Self::File {
file: src_path.to_path_buf(),
dir,
}
}
fn contains(&self, reader: &Path) -> bool {
match self {
Self::Library { src, bin } => reader.starts_with(src) && !reader.starts_with(bin),
Self::File { file, dir } => reader == file || reader.starts_with(dir),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn p(s: &str) -> PathBuf {
PathBuf::from(format!("/ws/pkg/{s}"))
}
#[test]
fn an_example_reader_locks_only_the_example() {
let readers = vec![p("examples/listing.rs")];
let pkg = Path::new("/ws/pkg");
assert!(!unit_reads_manifest_dir(pkg, &p("src/lib.rs"), &readers));
assert!(!unit_reads_manifest_dir(
pkg,
&p("tests/traversal.rs"),
&readers
));
assert!(unit_reads_manifest_dir(
pkg,
&p("examples/listing.rs"),
&readers
));
assert!(!unit_reads_manifest_dir(
pkg,
&p("examples/other.rs"),
&readers
));
}
#[test]
fn a_library_reader_locks_the_library_and_its_unit_tests_only() {
let readers = vec![p("src/util/paths.rs")];
let pkg = Path::new("/ws/pkg");
assert!(unit_reads_manifest_dir(pkg, &p("src/lib.rs"), &readers));
assert!(unit_reads_manifest_dir(pkg, &p("src/main.rs"), &readers));
assert!(!unit_reads_manifest_dir(pkg, &p("tests/t.rs"), &readers));
assert!(!unit_reads_manifest_dir(
pkg,
&p("src/bin/tool.rs"),
&readers
));
}
#[test]
fn module_directories_belong_to_their_root_file() {
let pkg = Path::new("/ws/pkg");
let readers = vec![p("tests/suite/helpers.rs")];
assert!(unit_reads_manifest_dir(
pkg,
&p("tests/suite/main.rs"),
&readers
));
assert!(unit_reads_manifest_dir(pkg, &p("tests/suite.rs"), &readers));
assert!(!unit_reads_manifest_dir(
pkg,
&p("tests/other.rs"),
&readers
));
let bin_readers = vec![p("src/bin/tool/cli.rs")];
assert!(unit_reads_manifest_dir(
pkg,
&p("src/bin/tool.rs"),
&bin_readers
));
assert!(!unit_reads_manifest_dir(
pkg,
&p("src/lib.rs"),
&bin_readers
));
}
#[test]
fn unknown_layouts_lock_everything() {
let pkg = Path::new("/ws/pkg");
let outside = vec![p("shared/paths.rs")];
assert!(unit_reads_manifest_dir(pkg, &p("src/lib.rs"), &outside));
assert!(unit_reads_manifest_dir(pkg, &p("tests/t.rs"), &outside));
let readers = vec![p("src/lib.rs")];
assert!(unit_reads_manifest_dir(
pkg,
&p("lib/custom_root.rs"),
&readers
));
assert!(!unit_reads_manifest_dir(pkg, &p("src/lib.rs"), &[]));
}
}