use std::ffi::OsString;
use std::time::{Duration, UNIX_EPOCH};
use xpct::{be_err, be_some, equal, expect, match_pattern, pattern};
use liteboxfs::{
Connection, CreateOptions, Error, FileBy, FileOrigin,
metadata::{Acl, AclMode, AclQualifier, FileKind, FileMode, Gid, Owner, Uid, Xattrs},
};
mod reading_metadata {
use super::*;
#[test]
fn metadata_returns_initial_values() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
let metadata = file.metadata()?;
expect!(metadata.user()).to(equal(Uid::ROOT));
expect!(metadata.group()).to(equal(Gid::ROOT));
let expected_mode = FileMode::OWNER_R
| FileMode::OWNER_W
| FileMode::GROUP_R
| FileMode::GROUP_W
| FileMode::OTHER_R;
expect!(metadata.mode()).to(equal(expected_mode));
expect!(metadata.created()).to(be_some());
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn xattrs_returns_empty_by_default() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
let xattrs = file.xattrs()?;
expect!(xattrs.iter().count()).to(equal(0));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn access_acl_returns_empty_by_default() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
let acl = file.access_acl()?;
expect!(acl.iter().count()).to(equal(0));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn default_acl_returns_empty_by_default() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let mut dir = fs.create("testdir", FileKind::Dir, Owner::ROOT)?;
let acl = dir.default_acl()?;
expect!(acl.iter().count()).to(equal(0));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
}
mod writing_mode {
use super::*;
#[test]
fn set_mode_updates_mode() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
let new_mode = FileMode::OWNER_RWX | FileMode::GROUP_R | FileMode::OTHER_R;
file.set_mode(new_mode)?;
let metadata = file.metadata()?;
expect!(metadata.mode()).to(equal(new_mode));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
}
mod writing_owner {
use super::*;
#[test]
fn set_user_updates_user() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
let new_uid = Uid::from_raw(1000);
file.set_user(new_uid)?;
let metadata = file.metadata()?;
expect!(metadata.user()).to(equal(new_uid));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn set_group_updates_group() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
let new_gid = Gid::from_raw(1000);
file.set_group(new_gid)?;
let metadata = file.metadata()?;
expect!(metadata.group()).to(equal(new_gid));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
}
mod writing_timestamps {
use super::*;
#[test]
fn set_accessed_updates_atime() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
let new_time = UNIX_EPOCH + Duration::from_secs(1_000_000);
file.set_accessed(new_time)?;
let metadata = file.metadata()?;
let diff = metadata
.accessed()
.duration_since(new_time)
.unwrap_or_else(|e| e.duration());
expect!(diff.as_millis()).to(equal(0u128));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn set_modified_updates_mtime() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
let new_time = UNIX_EPOCH + Duration::from_secs(2_000_000);
file.set_modified(new_time)?;
let metadata = file.metadata()?;
let diff = metadata
.modified()
.duration_since(new_time)
.unwrap_or_else(|e| e.duration());
expect!(diff.as_millis()).to(equal(0u128));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn set_changed_updates_ctime() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
let new_time = UNIX_EPOCH + Duration::from_secs(3_000_000);
file.set_changed(new_time)?;
let metadata = file.metadata()?;
let diff = metadata
.changed()
.duration_since(new_time)
.unwrap_or_else(|e| e.duration());
expect!(diff.as_millis()).to(equal(0u128));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn set_created_updates_btime() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
let new_time = UNIX_EPOCH + Duration::from_secs(500_000);
file.set_created(Some(new_time))?;
let metadata = file.metadata()?;
let btime = expect!(metadata.created()).to(be_some()).into_inner();
let diff = btime
.duration_since(new_time)
.unwrap_or_else(|e| e.duration());
expect!(diff.as_millis()).to(equal(0u128));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn set_created_to_none_clears_btime() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.set_created(None)?;
let metadata = file.metadata()?;
expect!(metadata.created().is_none()).to(equal(true));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
}
mod xattrs_roundtrip {
use super::*;
#[test]
fn set_and_get_xattrs() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
let mut xattrs = Xattrs::new();
xattrs.set(OsString::from("user.test1"), b"value1".to_vec());
xattrs.set(OsString::from("user.test2"), b"value2".to_vec());
file.set_xattrs(&xattrs)?;
let retrieved = file.xattrs()?;
expect!(retrieved.get("user.test1"))
.to(be_some())
.to(equal(b"value1".as_slice()));
expect!(retrieved.get("user.test2"))
.to(be_some())
.to(equal(b"value2".as_slice()));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn set_xattrs_replaces_existing() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
let mut xattrs1 = Xattrs::new();
xattrs1.set(OsString::from("user.old"), b"old_value".to_vec());
file.set_xattrs(&xattrs1)?;
let mut xattrs2 = Xattrs::new();
xattrs2.set(OsString::from("user.new"), b"new_value".to_vec());
file.set_xattrs(&xattrs2)?;
let retrieved = file.xattrs()?;
expect!(retrieved.get("user.old").is_none()).to(equal(true));
expect!(retrieved.get("user.new"))
.to(be_some())
.to(equal(b"new_value".as_slice()));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn empty_xattrs_clears_all() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
let mut xattrs = Xattrs::new();
xattrs.set(OsString::from("user.test"), b"value".to_vec());
file.set_xattrs(&xattrs)?;
file.set_xattrs(&Xattrs::new())?;
let retrieved = file.xattrs()?;
expect!(retrieved.iter().count()).to(equal(0));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
}
mod acl_roundtrip {
use super::*;
#[test]
fn set_and_get_access_acl() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
let mut acl = Acl::new();
acl.set(AclQualifier::OwningUser, AclMode::RWX);
acl.set(AclQualifier::OwningGroup, AclMode::R);
acl.set(AclQualifier::Other, AclMode::empty());
file.set_access_acl(&acl)?;
let retrieved = file.access_acl()?;
expect!(retrieved.get(AclQualifier::OwningUser))
.to(be_some())
.to(equal(AclMode::RWX));
expect!(retrieved.get(AclQualifier::OwningGroup))
.to(be_some())
.to(equal(AclMode::R));
expect!(retrieved.get(AclQualifier::Other))
.to(be_some())
.to(equal(AclMode::empty()));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn set_and_get_default_acl_on_directory() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let mut dir = fs.create("testdir", FileKind::Dir, Owner::ROOT)?;
let mut acl = Acl::new();
acl.set(AclQualifier::OwningUser, AclMode::RWX);
acl.set(
AclQualifier::User(Uid::from_raw(1000)),
AclMode::R | AclMode::X,
);
acl.set(AclQualifier::Mask, AclMode::RWX);
dir.set_default_acl(&acl)?;
let retrieved = dir.default_acl()?;
expect!(retrieved.get(AclQualifier::OwningUser))
.to(be_some())
.to(equal(AclMode::RWX));
expect!(retrieved.get(AclQualifier::User(Uid::from_raw(1000))))
.to(be_some())
.to(equal(AclMode::R | AclMode::X));
expect!(retrieved.get(AclQualifier::Mask))
.to(be_some())
.to(equal(AclMode::RWX));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn acl_with_named_user_and_group() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
let mut acl = Acl::new();
acl.set(
AclQualifier::User(Uid::from_raw(1000)),
AclMode::R | AclMode::W,
);
acl.set(AclQualifier::Group(Gid::from_raw(2000)), AclMode::R);
file.set_access_acl(&acl)?;
let retrieved = file.access_acl()?;
expect!(retrieved.get(AclQualifier::User(Uid::from_raw(1000))))
.to(be_some())
.to(equal(AclMode::R | AclMode::W));
expect!(retrieved.get(AclQualifier::Group(Gid::from_raw(2000))))
.to(be_some())
.to(equal(AclMode::R));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
}
mod umask {
use super::*;
#[test]
fn umask_determines_new_file_permissions() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
fs.set_umask(FileMode::OTHER_R | FileMode::OTHER_W);
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
let metadata = file.metadata()?;
expect!(metadata.mode()).to(equal(
FileMode::OWNER_R | FileMode::OWNER_W | FileMode::GROUP_R | FileMode::GROUP_W,
));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn umask_determines_new_dir_permissions() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
fs.set_umask(FileMode::OTHER_R | FileMode::OTHER_W | FileMode::OTHER_X);
let mut file = fs.create("dir", FileKind::Dir, Owner::ROOT)?;
let metadata = file.metadata()?;
expect!(metadata.mode()).to(equal(FileMode::OWNER_RWX | FileMode::GROUP_RWX));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
}
#[cfg(all(feature = "chunking", feature = "compression"))]
mod features {
use super::*;
use xpct::{be_false, be_true};
#[test]
fn check_compression_is_enabled() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new().compression(true))?;
conn.exec(|fs| {
expect!(fs.compression_enabled()).to(be_true());
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn check_compression_is_not_enabled() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
expect!(fs.compression_enabled()).to(be_false());
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn check_chunking_is_enabled() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new().chunking(true))?;
conn.exec(|fs| {
expect!(fs.chunking_enabled()).to(be_true());
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn check_chunking_is_not_enabled() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
expect!(fs.compression_enabled()).to(be_false());
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn set_compression_enables_compression() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
fs.set_compression(true);
expect!(fs.compression_enabled()).to(be_true());
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn set_compression_disables_compression() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new().compression(true))?;
conn.exec(|fs| {
fs.set_compression(false);
expect!(fs.compression_enabled()).to(be_false());
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn set_chunking_enables_chunking() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
fs.set_chunking(true);
expect!(fs.chunking_enabled()).to(be_true());
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn set_chunking_disables_chunking() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new().chunking(true))?;
conn.exec(|fs| {
fs.set_chunking(false);
expect!(fs.chunking_enabled()).to(be_false());
liteboxfs::Result::Ok(())
})?;
Ok(())
}
}
mod errors {
use super::*;
#[test]
fn metadata_on_deleted_file_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.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 set_mode_on_deleted_file_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.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 xattrs_on_deleted_file_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.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 access_acl_on_deleted_file_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.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 set_default_acl_on_regular_file_returns_not_a_directory() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
expect!(file.set_default_acl(&Acl::new()))
.to(be_err())
.to(match_pattern(pattern!(Error::NotADirectory { .. })));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn set_default_acl_on_symlink_returns_not_a_directory() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let mut symlink = fs.create(
"link",
FileKind::Symlink {
target: "/target".into(),
},
Owner::ROOT,
)?;
expect!(symlink.set_default_acl(&Acl::new()))
.to(be_err())
.to(match_pattern(pattern!(Error::NotADirectory { .. })));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn set_accessed_on_deleted_file_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.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 set_user_on_deleted_file_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.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(())
}
}