use std::path::PathBuf;
use xpct::{be_empty, be_err, be_lt, be_ok, consist_of, expect, match_pattern, pattern};
use liteboxfs::{
Connection, CreateOptions, Device, Error, FileBy, FileOrigin,
metadata::{FileKind, Owner},
};
#[test]
fn empty_directory_has_no_descendants() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
fs.create("dir", FileKind::Dir, Owner::ROOT)?;
expect!(fs.descendants("dir"))
.to(be_ok())
.map(|entries| entries.collect::<Vec<_>>())
.to(be_empty());
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn returns_direct_children_with_correct_paths_and_kinds() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
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)?;
fs.create(
"dir/link",
FileKind::Symlink {
target: PathBuf::from("/target"),
},
Owner::ROOT,
)?;
let entries: Vec<(PathBuf, FileKind)> = fs
.descendants("dir")?
.map(|e| (e.path().to_owned(), e.kind().clone()))
.collect();
expect!(entries).to(consist_of([
(PathBuf::from("/dir/file.txt"), FileKind::Regular),
(PathBuf::from("/dir/sub"), FileKind::Dir),
(
PathBuf::from("/dir/link"),
FileKind::Symlink {
target: PathBuf::from("/target"),
},
),
]));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn returns_deeply_nested_descendants_with_correct_paths() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
fs.create("dir", FileKind::Dir, Owner::ROOT)?;
fs.create("dir/a.txt", FileKind::Regular, Owner::ROOT)?;
fs.create("dir/sub", FileKind::Dir, Owner::ROOT)?;
fs.create("dir/sub/b.txt", FileKind::Regular, Owner::ROOT)?;
fs.create("dir/sub/deep", FileKind::Dir, Owner::ROOT)?;
fs.create("dir/sub/deep/c.txt", FileKind::Regular, Owner::ROOT)?;
let paths: Vec<PathBuf> = fs
.descendants("dir")?
.map(|e| e.path().to_owned())
.collect();
expect!(paths).to(consist_of([
PathBuf::from("/dir/a.txt"),
PathBuf::from("/dir/sub"),
PathBuf::from("/dir/sub/b.txt"),
PathBuf::from("/dir/sub/deep"),
PathBuf::from("/dir/sub/deep/c.txt"),
]));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn parents_come_before_children() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
fs.create("dir", FileKind::Dir, Owner::ROOT)?;
fs.create("dir/sub_a", FileKind::Dir, Owner::ROOT)?;
fs.create("dir/sub_a/file.txt", FileKind::Regular, Owner::ROOT)?;
fs.create("dir/sub_b", FileKind::Dir, Owner::ROOT)?;
fs.create("dir/sub_b/nested", FileKind::Dir, Owner::ROOT)?;
fs.create("dir/sub_b/nested/file.txt", FileKind::Regular, Owner::ROOT)?;
let paths: Vec<PathBuf> = fs
.descendants("dir")?
.map(|e| e.path().to_owned())
.collect();
let index_of = |path: &str| {
paths
.iter()
.position(|p| p == &PathBuf::from(path))
.unwrap()
};
expect!(index_of("/dir/sub_a")).to(be_lt(index_of("/dir/sub_a/file.txt")));
expect!(index_of("/dir/sub_b")).to(be_lt(index_of("/dir/sub_b/nested")));
expect!(index_of("/dir/sub_b/nested")).to(be_lt(index_of("/dir/sub_b/nested/file.txt")));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn reports_all_special_file_kinds() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
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.descendants("dir"))
.to(be_ok())
.map(|entries| {
entries
.map(|e| (e.name().to_string_lossy().into_owned(), e.kind().clone()))
.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),
]));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn can_walk_a_subdirectory() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
fs.create("root", FileKind::Dir, Owner::ROOT)?;
fs.create("root/other.txt", FileKind::Regular, Owner::ROOT)?;
fs.create("root/sub", FileKind::Dir, Owner::ROOT)?;
fs.create("root/sub/file.txt", FileKind::Regular, Owner::ROOT)?;
let paths: Vec<PathBuf> = fs
.descendants("root/sub")?
.map(|e| e.path().to_owned())
.collect();
expect!(paths).to(consist_of([PathBuf::from("/root/sub/file.txt")]));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
mod errors {
use super::*;
#[test]
fn nonexistent_path_returns_file_not_found() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
expect!(fs.descendants("nonexistent")).to(be_err()).to(match_pattern(
pattern!(Error::FileNotFound { file: FileOrigin::Litebox { locator, .. }, .. } if locator == &FileBy::from("/nonexistent")),
));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn non_directory_returns_not_a_directory() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
fs.create("file.txt", FileKind::Regular, Owner::ROOT)?;
expect!(fs.descendants("file.txt")).to(be_err()).to(match_pattern(
pattern!(Error::NotADirectory { file: FileOrigin::Litebox { locator, .. }, .. } if locator == &FileBy::from("/file.txt")),
));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
}