#![allow(clippy::unwrap_used, clippy::expect_used)]
#![cfg(feature = "localfs")]
mod common;
use std::fs;
use common::kernel_at;
#[cfg(unix)]
struct RestorePerms(std::path::PathBuf);
#[cfg(unix)]
impl Drop for RestorePerms {
fn drop(&mut self) {
use std::os::unix::fs::PermissionsExt;
let _ = fs::set_permissions(&self.0, fs::Permissions::from_mode(0o755));
}
}
#[cfg(unix)]
fn make_fixture(root: &std::path::Path) -> Option<(std::path::PathBuf, RestorePerms)> {
use std::os::unix::fs::PermissionsExt;
let parent = root.join("parent");
fs::create_dir_all(parent.join("good")).expect("mkdir good");
fs::write(parent.join("good/visible.txt"), "hi").expect("write visible.txt");
let secret = parent.join("secret");
fs::create_dir(&secret).expect("mkdir secret");
fs::write(secret.join("hidden.txt"), "nope").expect("write hidden.txt");
fs::set_permissions(&secret, fs::Permissions::from_mode(0o000)).expect("chmod 000");
if fs::read_dir(&secret).is_ok() {
fs::set_permissions(&secret, fs::Permissions::from_mode(0o755)).expect("restore perms");
return None;
}
let guard = RestorePerms(secret);
Some((parent, guard))
}
#[cfg(unix)]
#[tokio::test]
async fn ls_recursive_reports_unreadable_dir_and_keeps_readable_entries() {
let tmp = tempfile::tempdir().unwrap();
let Some((_parent, _guard)) = make_fixture(tmp.path()) else {
eprintln!("skipping: mode-000 directory is still readable in this environment (root?)");
return;
};
let kernel = kernel_at(tmp.path());
let result = kernel.execute("ls -R parent").await.expect("kernel execute");
assert_ne!(result.code, 0, "unreadable dir in the walk must be a nonzero exit: {result:?}");
assert!(
result.err.contains("secret"),
"stderr should name the unreadable directory: {:?}",
result.err
);
let out = result.text_out();
assert!(out.contains("visible.txt"), "readable sibling entries must still list: {out:?}");
assert!(out.contains("good"), "readable sibling directory must still list: {out:?}");
}
#[cfg(unix)]
#[tokio::test]
async fn ls_recursive_fully_readable_tree_exits_zero_no_diagnostics() {
let tmp = tempfile::tempdir().unwrap();
fs::create_dir_all(tmp.path().join("parent/good")).unwrap();
fs::write(tmp.path().join("parent/good/visible.txt"), "hi").unwrap();
let kernel = kernel_at(tmp.path());
let result = kernel.execute("ls -R parent").await.expect("kernel execute");
assert_eq!(result.code, 0, "fully readable tree must exit 0: {result:?}");
assert!(result.err.is_empty(), "no diagnostics expected: {:?}", result.err);
assert!(result.text_out().contains("visible.txt"));
}
#[cfg(unix)]
#[tokio::test]
async fn tree_marks_unreadable_dir_inline_and_exits_nonzero() {
let tmp = tempfile::tempdir().unwrap();
let Some((_parent, _guard)) = make_fixture(tmp.path()) else {
eprintln!("skipping: mode-000 directory is still readable in this environment (root?)");
return;
};
let kernel = kernel_at(tmp.path());
let result = kernel.execute("tree parent").await.expect("kernel execute");
assert_ne!(result.code, 0, "unreadable dir in the walk must be a nonzero exit: {result:?}");
assert!(
result.err.contains("secret"),
"stderr should name the unreadable directory: {:?}",
result.err
);
let out = result.text_out();
assert!(
out.contains("secret") && out.contains("[error opening dir]"),
"the node must be marked inline, not rendered as a childless (i.e. \
indistinguishable from empty) directory: {out:?}"
);
assert!(out.contains("visible.txt"), "readable sibling entries must still render: {out:?}");
}
#[cfg(unix)]
#[tokio::test]
async fn tree_traditional_marks_unreadable_dir_inline() {
let tmp = tempfile::tempdir().unwrap();
let Some((_parent, _guard)) = make_fixture(tmp.path()) else {
eprintln!("skipping: mode-000 directory is still readable in this environment (root?)");
return;
};
let kernel = kernel_at(tmp.path());
let result = kernel.execute("tree --traditional parent").await.expect("kernel execute");
assert_ne!(result.code, 0, "unreadable dir in the walk must be a nonzero exit: {result:?}");
let out = result.text_out();
assert!(
out.contains("[error opening dir]"),
"traditional format must also mark the unreadable node: {out:?}"
);
assert!(out.contains("visible.txt"), "readable sibling entries must still render: {out:?}");
}
#[cfg(unix)]
#[tokio::test]
async fn tree_fully_readable_tree_exits_zero_no_diagnostics() {
let tmp = tempfile::tempdir().unwrap();
fs::create_dir_all(tmp.path().join("parent/good")).unwrap();
fs::write(tmp.path().join("parent/good/visible.txt"), "hi").unwrap();
let kernel = kernel_at(tmp.path());
let result = kernel.execute("tree parent").await.expect("kernel execute");
assert_eq!(result.code, 0, "fully readable tree must exit 0: {result:?}");
assert!(result.err.is_empty(), "no diagnostics expected: {:?}", result.err);
assert!(!result.text_out().contains("[error opening dir]"));
}