use std::io::{Read, Seek, SeekFrom, Write};
use std::path::PathBuf;
use xpct::{be_err, be_ok, eq_diff, expect, match_pattern, pattern};
use liteboxfs::{
Connection, CreateOptions, Error, FileBy, FileOrigin,
metadata::{FileKind, Owner},
};
#[test]
fn deleted_file_is_not_openable_by_its_path() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
fs.delete("test.txt")?;
expect!(fs.open("test.txt")).to(be_err()).to(match_pattern(
pattern!(Error::FileNotFound { file: FileOrigin::Litebox { locator, .. }, .. } if locator == &FileBy::from("/test.txt")),
));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn deleted_file_is_not_openable_by_its_id() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
let file_id = file.file_id();
drop(file);
fs.delete("test.txt")?;
expect!(fs.open(file_id)).to(be_err()).to(match_pattern(
pattern!(Error::FileNotFound { file: FileOrigin::Litebox { locator, .. }, .. } if locator == &FileBy::from(file_id)),
));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn delete_with_hard_link_preserves_link() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
let expected_content = "text";
let mut link_content = String::new();
conn.exec(|fs| {
let mut file = fs.create("original.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(expected_content.as_bytes())?;
drop(file);
fs.open("original.txt")?.link("link.txt")?;
fs.delete("original.txt")?;
expect!(fs.open("original.txt")).to(be_err()).to(match_pattern(
pattern!(Error::FileNotFound { file: FileOrigin::Litebox { locator, .. }, .. } if locator == &FileBy::from("/original.txt")),
));
let mut link = fs.open("link.txt")?;
link.read_to_string(&mut link_content)?;
liteboxfs::Result::Ok(())
})?;
expect!(link_content).to(eq_diff(expected_content));
Ok(())
}
#[test]
fn unlinked_file_is_not_openable_by_its_path() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
fs.unlink("test.txt")?;
expect!(fs.open("test.txt")).to(be_err()).to(match_pattern(
pattern!(Error::FileNotFound { file: FileOrigin::Litebox { locator, .. }, .. } if locator == &FileBy::from("/test.txt")),
));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn unlinked_file_is_openable_by_its_id() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
let file_id = file.file_id();
let fd = file.leak_fd()?;
fs.unlink("test.txt")?;
expect!(fs.open(file_id)).to(be_ok());
fs.release(fd)?;
expect!(fs.open(file_id)).to(be_err()).to(match_pattern(
pattern!(Error::FileNotFound { file: FileOrigin::Litebox { locator, .. }, .. } if locator == &FileBy::from(file_id)),
));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn unlink_without_open_handles_deletes_immediately() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let file_id = fs
.create("test.txt", FileKind::Regular, Owner::ROOT)?
.file_id();
fs.unlink("test.txt")?;
expect!(fs.open(file_id)).to(be_err()).to(match_pattern(
pattern!(Error::FileNotFound { file: FileOrigin::Litebox { locator, .. }, .. } if locator == &FileBy::from(file_id)),
));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn unlink_allows_continued_read_write() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
let expected = "hello world";
let mut actual = String::new();
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(expected.as_bytes())?;
file.seek(SeekFrom::Start(0))?;
file.read_to_string(&mut actual)?;
drop(file);
fs.unlink("test.txt")?;
expect!(fs.open("test.txt")).to(be_err()).to(match_pattern(
pattern!(Error::FileNotFound { file: FileOrigin::Litebox { locator, .. }, .. } if locator == &FileBy::from("/test.txt")),
));
liteboxfs::Result::Ok(())
})?;
expect!(actual).to(eq_diff(expected));
Ok(())
}
#[test]
fn unlink_with_hard_link_preserves_link() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
let expected_content = "text";
let mut link_content = String::new();
conn.exec(|fs| {
let mut file = fs.create("original.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(expected_content.as_bytes())?;
drop(file);
fs.open("original.txt")?.link("link.txt")?;
fs.unlink("original.txt")?;
expect!(fs.open("original.txt")).to(be_err()).to(match_pattern(
pattern!(Error::FileNotFound { file: FileOrigin::Litebox { locator, .. }, .. } if locator == &FileBy::from("/original.txt")),
));
let mut link = fs.open("link.txt")?;
link.read_to_string(&mut link_content)?;
liteboxfs::Result::Ok(())
})?;
expect!(link_content).to(eq_diff(expected_content));
Ok(())
}
#[test]
fn delete_tree_removes_file() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
fs.delete_tree("test.txt")?;
expect!(fs.open("test.txt")).to(be_err()).to(match_pattern(
pattern!(Error::FileNotFound { file: FileOrigin::Litebox { locator, .. }, .. } if locator == &FileBy::from("/test.txt")),
));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn delete_tree_removes_directory_and_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/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.delete_tree("dir")?;
expect!(fs.open("dir"))
.to(be_err())
.to(match_pattern(pattern!(Error::FileNotFound { .. })));
expect!(fs.open("dir/a.txt"))
.to(be_err())
.to(match_pattern(pattern!(Error::FileNotFound { .. })));
expect!(fs.open("dir/sub"))
.to(be_err())
.to(match_pattern(pattern!(Error::FileNotFound { .. })));
expect!(fs.open("dir/sub/b.txt"))
.to(be_err())
.to(match_pattern(pattern!(Error::FileNotFound { .. })));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn delete_tree_preserves_hard_links() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
let expected_content = "text";
let mut link_content = String::new();
conn.exec(|fs| {
fs.create("dir", FileKind::Dir, Owner::ROOT)?;
let mut file = fs.create("dir/original.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(expected_content.as_bytes())?;
drop(file);
fs.open("dir/original.txt")?.link("external-link.txt")?;
fs.delete_tree("dir")?;
let mut link = fs.open("external-link.txt")?;
link.read_to_string(&mut link_content)?;
liteboxfs::Result::Ok(())
})?;
expect!(link_content).to(eq_diff(expected_content));
Ok(())
}
mod errors {
use super::*;
#[test]
fn delete_twice_returns_file_not_found() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
fs.delete("test.txt")?;
expect!(fs.delete("test.txt")).to(be_err()).to(match_pattern(
pattern!(Error::FileNotFound { file: FileOrigin::Litebox { locator, .. }, .. } if locator == &FileBy::from("/test.txt")),
));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn unlink_twice_returns_file_not_found() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
fs.unlink("test.txt")?;
expect!(fs.unlink("test.txt")).to(be_err()).to(match_pattern(
pattern!(Error::FileNotFound { file: FileOrigin::Litebox { locator, .. }, .. } if locator == &FileBy::from("/test.txt")),
));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn delete_root_directory() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
expect!(fs.delete("/")).to(be_err()).to(match_pattern(
pattern!(Error::InvalidPath { path: FileOrigin::Litebox { locator, .. }, .. } if locator == &PathBuf::from("/")),
));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn unlink_root_directory() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
expect!(fs.unlink("/")).to(be_err()).to(match_pattern(
pattern!(Error::NotARegularFile { file: FileOrigin::Litebox { locator, .. }, .. } if locator == &FileBy::from("/")),
));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn delete_non_empty_directory() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
fs.create("dir", FileKind::Dir, Owner::ROOT)?;
fs.create("dir/child.txt", FileKind::Regular, Owner::ROOT)?;
expect!(fs.delete("dir")).to(be_err()).to(match_pattern(
pattern!(Error::DirectoryNotEmpty { path: FileOrigin::Litebox { locator, .. }, .. } if locator == &PathBuf::from("/dir")),
));
expect!(fs.open("dir")).to(be_ok());
expect!(fs.open("dir/child.txt")).to(be_ok());
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn unlink_directory() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
fs.create("dir", FileKind::Dir, Owner::ROOT)?;
expect!(fs.unlink("dir")).to(be_err()).to(match_pattern(
pattern!(Error::NotARegularFile { file: FileOrigin::Litebox { locator, .. }, .. } if locator == &FileBy::from("/dir")),
));
expect!(fs.open("dir")).to(be_ok());
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn unlink_symlink() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
fs.create(
"link",
FileKind::Symlink {
target: PathBuf::from("/target"),
},
Owner::ROOT,
)?;
expect!(fs.unlink("link")).to(be_err()).to(match_pattern(
pattern!(Error::NotARegularFile { file: FileOrigin::Litebox { locator, .. }, .. } if locator == &FileBy::from("/link")),
));
expect!(fs.open("link")).to(be_ok());
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn delete_empty_directory() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
fs.create("dir", FileKind::Dir, Owner::ROOT)?;
expect!(fs.delete("dir")).to(be_ok());
expect!(fs.open("dir"))
.to(be_err())
.to(match_pattern(pattern!(Error::FileNotFound { .. })));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn delete_tree_root_directory() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
expect!(fs.delete_tree("/")).to(be_err()).to(match_pattern(
pattern!(Error::InvalidPath { path: FileOrigin::Litebox { locator, .. }, .. } if locator == &PathBuf::from("/")),
));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn delete_tree_nonexistent_path() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
expect!(fs.delete_tree("nonexistent.txt")).to(be_err()).to(match_pattern(
pattern!(Error::FileNotFound { file: FileOrigin::Litebox { locator, .. }, .. } if locator == &FileBy::from("/nonexistent.txt")),
));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn delete_tree_twice() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
fs.create("dir", FileKind::Dir, Owner::ROOT)?;
fs.create("dir/child.txt", FileKind::Regular, Owner::ROOT)?;
fs.delete_tree("dir")?;
expect!(fs.delete_tree("dir")).to(be_err()).to(match_pattern(
pattern!(Error::FileNotFound { file: FileOrigin::Litebox { locator, .. }, .. } if locator == &FileBy::from("/dir")),
));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn leak_fd_directory() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let file = fs.create("dir", FileKind::Dir, Owner::ROOT)?;
let file_id = file.file_id();
expect!(file.leak_fd()).to(be_err()).to(match_pattern(
pattern!(Error::NotARegularFile { file: FileOrigin::Litebox { locator, .. }, .. } if locator == &FileBy::from(file_id)),
));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn leak_fd_symlink() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let file = fs.create(
"link",
FileKind::Symlink {
target: PathBuf::from("/target"),
},
Owner::ROOT,
)?;
let file_id = file.file_id();
expect!(file.leak_fd()).to(be_err()).to(match_pattern(
pattern!(Error::NotARegularFile { file: FileOrigin::Litebox { locator, .. }, .. } if locator == &FileBy::from(file_id)),
));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
}