#![cfg(all(feature = "fs", target_os = "linux"))]
use std::{ffi::OsStr, path::Path, path::PathBuf};
use xpct::{be_empty, be_none, be_ok, consist_of, equal, expect, match_pattern, pattern};
use crate::{
Connection, Error, FileOrigin,
file_metadata::{Device, FileKind, Owner},
settings::Settings,
};
fn open() -> crate::Result<Connection> {
Connection::open_for_testing(&Settings::default())
}
#[test]
fn empty_directory_returns_no_entries() -> crate::Result<()> {
let mut conn = open()?;
conn.exec(|fs| {
fs.create("/dir", FileKind::Dir, Owner::ROOT)?;
expect!(fs.children("/dir"))
.to(be_ok())
.iter_map(|entry| entry.name().to_string_lossy().into_owned())
.map(|iter| iter.collect::<Vec<_>>())
.to(be_empty());
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn lists_immediate_children() -> crate::Result<()> {
let mut conn = open()?;
conn.exec(|fs| {
fs.create("/dir", FileKind::Dir, Owner::ROOT)?;
fs.create("/dir/a.txt", FileKind::Regular, Owner::ROOT)?;
fs.create("/dir/b.txt", FileKind::Regular, Owner::ROOT)?;
fs.create("/dir/sub", FileKind::Dir, Owner::ROOT)?;
expect!(fs.children("/dir"))
.to(be_ok())
.iter_map(|entry| entry.name().to_string_lossy().into_owned())
.map(|iter| iter.collect::<Vec<_>>())
.to(consist_of([
"a.txt".to_string(),
"b.txt".to_string(),
"sub".to_string(),
]));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn does_not_include_nested_descendants() -> crate::Result<()> {
let mut conn = open()?;
conn.exec(|fs| {
fs.create("/dir", FileKind::Dir, Owner::ROOT)?;
fs.create("/dir/sub", FileKind::Dir, Owner::ROOT)?;
fs.create("/dir/sub/nested.txt", FileKind::Regular, Owner::ROOT)?;
expect!(fs.children("/dir"))
.to(be_ok())
.iter_map(|entry| entry.name().to_string_lossy().into_owned())
.map(|iter| iter.collect::<Vec<_>>())
.to(consist_of(["sub".to_string()]));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn lists_children_of_root() -> crate::Result<()> {
let mut conn = open()?;
conn.exec(|fs| {
fs.create("/a.txt", FileKind::Regular, Owner::ROOT)?;
fs.create("/b.txt", FileKind::Regular, Owner::ROOT)?;
expect!(fs.children("/"))
.to(be_ok())
.iter_map(|entry| entry.name().to_string_lossy().into_owned())
.map(|iter| iter.collect::<Vec<_>>())
.to(consist_of(["a.txt".to_string(), "b.txt".to_string()]));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn fails_when_path_does_not_exist() -> crate::Result<()> {
let mut conn = open()?;
conn.exec(|fs| {
expect!(fs.children("/nonexistent"))
.to(match_pattern(pattern!(Err(Error::FileNotFound { .. }))));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn fails_when_path_is_not_a_directory() -> crate::Result<()> {
let mut conn = open()?;
conn.exec(|fs| {
fs.create("/file.txt", FileKind::Regular, Owner::ROOT)?;
expect!(fs.children("/file.txt")).to(match_pattern(pattern!(Err(Error::NotADirectory {
file: FileOrigin::Litebox { .. },
},))));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn entry_path_is_absolute_path_under_parent() -> crate::Result<()> {
let mut conn = open()?;
conn.exec(|fs| {
fs.create("/dir", FileKind::Dir, Owner::ROOT)?;
fs.create("/dir/a.txt", FileKind::Regular, Owner::ROOT)?;
let entry = expect!(fs.children("/dir"))
.to(be_ok())
.map(|mut iter| iter.next().expect("expected at least one entry"))
.into_inner();
expect!(entry.path()).to(equal(Path::new("/dir/a.txt")));
expect!(entry.name()).to(equal(OsStr::new("a.txt")));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn entry_reports_correct_kind() -> crate::Result<()> {
let mut conn = open()?;
conn.exec(|fs| {
fs.create("/dir", FileKind::Dir, Owner::ROOT)?;
fs.create("/dir/file.txt", FileKind::Regular, Owner::ROOT)?;
fs.create("/dir/sub", FileKind::Dir, Owner::ROOT)?;
expect!(fs.children("/dir"))
.to(be_ok())
.iter_map(|entry| {
(
entry.name().to_string_lossy().into_owned(),
entry.kind().clone(),
)
})
.map(|iter| iter.collect::<Vec<_>>())
.to(consist_of([
("file.txt".to_string(), FileKind::Regular),
("sub".to_string(), FileKind::Dir),
]));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn entry_file_id_matches_open_file_id() -> crate::Result<()> {
let mut conn = open()?;
conn.exec(|fs| {
fs.create("/dir", FileKind::Dir, Owner::ROOT)?;
let file = fs.create("/dir/a.txt", FileKind::Regular, Owner::ROOT)?;
let expected_id = file.file_id();
drop(file);
expect!(fs.children("/dir"))
.to(be_ok())
.iter_map(|entry| entry.file_id())
.map(|iter| iter.collect::<Vec<_>>())
.to(consist_of([expected_id]));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn entry_metadata_matches_file_metadata() -> crate::Result<()> {
let mut conn = open()?;
conn.exec(|fs| {
fs.create("/dir", FileKind::Dir, Owner::ROOT)?;
let mut file = fs.create("/dir/a.txt", FileKind::Regular, Owner::ROOT)?;
let expected_mode = file.metadata()?.mode();
let expected_user = file.metadata()?.user();
let expected_group = file.metadata()?.group();
drop(file);
let entry = expect!(fs.children("/dir"))
.to(be_ok())
.map(|mut iter| iter.next().expect("expected at least one entry"))
.into_inner();
let metadata = entry.metadata();
expect!(metadata.mode()).to(equal(expected_mode));
expect!(metadata.user()).to(equal(expected_user));
expect!(metadata.group()).to(equal(expected_group));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn iterator_size_hint_matches_remaining() -> crate::Result<()> {
let mut conn = open()?;
conn.exec(|fs| {
fs.create("/dir", FileKind::Dir, Owner::ROOT)?;
fs.create("/dir/a.txt", FileKind::Regular, Owner::ROOT)?;
fs.create("/dir/b.txt", FileKind::Regular, Owner::ROOT)?;
fs.create("/dir/c.txt", FileKind::Regular, Owner::ROOT)?;
let mut iter = expect!(fs.children("/dir")).to(be_ok()).into_inner();
expect!(iter.size_hint()).to(equal((3usize, Some(3usize))));
expect!(iter.len()).to(equal(3));
iter.next();
expect!(iter.size_hint()).to(equal((2usize, Some(2usize))));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn fused_iterator_returns_none_after_exhaustion() -> crate::Result<()> {
let mut conn = open()?;
conn.exec(|fs| {
fs.create("/dir", FileKind::Dir, Owner::ROOT)?;
let mut iter = expect!(fs.children("/dir")).to(be_ok()).into_inner();
expect!(iter.next()).to(be_none());
expect!(iter.next()).to(be_none());
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn hard_links_appear_under_each_name() -> crate::Result<()> {
let mut conn = open()?;
conn.exec(|fs| {
fs.create("/dir", FileKind::Dir, Owner::ROOT)?;
let mut file = fs.create("/dir/a.txt", FileKind::Regular, Owner::ROOT)?;
file.link("/dir/b.txt")?;
let expected_id = file.file_id();
drop(file);
expect!(fs.children("/dir"))
.to(be_ok())
.iter_map(|entry| entry.file_id())
.map(|iter| iter.collect::<Vec<_>>())
.to(consist_of([expected_id, expected_id]));
crate::Result::Ok(())
})?;
Ok(())
}
#[test]
fn reports_all_special_file_kinds() -> crate::Result<()> {
let mut conn = open()?;
conn.exec(|fs| {
fs.create("/dir", FileKind::Dir, Owner::ROOT)?;
fs.create("/dir/regular", FileKind::Regular, Owner::ROOT)?;
fs.create("/dir/subdir", FileKind::Dir, Owner::ROOT)?;
fs.create(
"/dir/symlink",
FileKind::Symlink {
target: PathBuf::from("/target"),
},
Owner::ROOT,
)?;
fs.create(
"/dir/blockdev",
FileKind::Block {
dev: Device::new(8, 1),
},
Owner::ROOT,
)?;
fs.create(
"/dir/chardev",
FileKind::Char {
dev: Device::new(1, 3),
},
Owner::ROOT,
)?;
fs.create("/dir/fifo", FileKind::Pipe, Owner::ROOT)?;
expect!(fs.children("/dir"))
.to(be_ok())
.iter_map(|entry| {
(
entry.name().to_string_lossy().into_owned(),
entry.kind().clone(),
)
})
.map(|iter| iter.collect::<Vec<_>>())
.to(consist_of([
("regular".to_string(), FileKind::Regular),
("subdir".to_string(), FileKind::Dir),
(
"symlink".to_string(),
FileKind::Symlink {
target: PathBuf::from("/target"),
},
),
(
"blockdev".to_string(),
FileKind::Block {
dev: Device::new(8, 1),
},
),
(
"chardev".to_string(),
FileKind::Char {
dev: Device::new(1, 3),
},
),
("fifo".to_string(), FileKind::Pipe),
]));
crate::Result::Ok(())
})?;
Ok(())
}