use std::io::{Read, Write};
use std::time::{Duration, SystemTime};
use xpct::{
any, approx_eq_time, be_err, be_ok, equal, every, expect, fields, have_len, match_fields,
match_pattern, pattern,
};
use liteboxfs::{
Connection, CreateOptions, Error, FileBy, FileOrigin,
metadata::{FileKind, Owner},
root::Root,
};
mod basic {
use super::*;
#[test]
fn snapshot_creates_new_root() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let default_root_id = fs.root_id();
let snapshot_id = expect!(fs.copy_root(Some("snapshot")))
.to(be_ok())
.map(|root| root.id)
.to_not(equal(default_root_id))
.into_inner();
expect!(fs.find_root("snapshot"))
.to(be_ok())
.map(|root| root.id)
.to(equal(snapshot_id));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn snapshot_without_name() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let default_root_id = fs.root_id();
expect!(fs.copy_root(None))
.to(be_ok())
.map(|root| root.id)
.to_not(equal(default_root_id));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn snapshot_copies_files() -> 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.write_all(b"content")?;
drop(file);
let snapshot = fs.copy_root(Some("snapshot"))?;
fs.switch_root(snapshot.id)?;
let mut file = fs.open("test.txt")?;
let mut content = String::new();
file.read_to_string(&mut content)?;
expect!(content).to(equal("content"));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn copy_root_after_deleting_previous_copy() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let snapshot = fs.copy_root(Some("snapshot"))?;
fs.delete_root(snapshot.id)?;
expect!(fs.copy_root(Some("snapshot"))).to(be_ok());
liteboxfs::Result::Ok(())
})?;
Ok(())
}
}
mod listing {
use super::*;
#[test]
fn snapshot_appears_in_list_roots() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let default_root_id = fs.root_id();
let snapshot = fs.copy_root(Some("my-snapshot"))?;
expect!(fs.list_roots().collect::<liteboxfs::Result<Vec<_>>>())
.to(be_ok())
.to(have_len(2))
.to(every(|| {
any(|ctx| {
ctx.cloned()
.to(match_fields(fields!(Root {
id: equal(default_root_id),
name: equal(Some(liteboxfs::DEFAULT_ROOT_NAME.to_string())),
created: approx_eq_time(SystemTime::now(), Duration::from_secs(1)),
})))
.to(match_fields(fields!(Root {
id: equal(snapshot.id),
name: equal(Some("my-snapshot".to_string())),
created: approx_eq_time(SystemTime::now(), Duration::from_secs(1)),
})))
.done()
})
}));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
}
mod directory_hierarchy {
use super::*;
#[test]
fn snapshot_preserves_nested_directories() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
fs.create("dir1", FileKind::Dir, Owner::ROOT)?;
fs.create("dir1/dir2", FileKind::Dir, Owner::ROOT)?;
let mut file = fs.create("dir1/dir2/nested.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(b"nested content")?;
drop(file);
let snapshot = fs.copy_root(Some("snapshot"))?;
fs.switch_root(snapshot.id)?;
let mut file = fs.open("dir1/dir2/nested.txt")?;
let mut content = String::new();
file.read_to_string(&mut content)?;
expect!(content).to(equal("nested content".to_string()));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
}
mod isolation {
use super::*;
#[test]
fn modifying_file_in_snapshot_does_not_affect_source() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
let mut original_content = String::new();
let mut snapshot_content = String::new();
conn.exec(|fs| {
let default_root_id = fs.root_id();
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(b"original")?;
drop(file);
let snapshot = fs.copy_root(Some("snapshot"))?;
fs.switch_root(snapshot.id)?;
let mut file = fs.open("test.txt")?;
file.write_all(b"modified in snapshot")?;
drop(file);
let mut file = fs.open("test.txt")?;
file.read_to_string(&mut snapshot_content)?;
drop(file);
fs.switch_root(default_root_id)?;
let mut file = fs.open("test.txt")?;
file.read_to_string(&mut original_content)?;
liteboxfs::Result::Ok(())
})?;
expect!(original_content).to(equal("original"));
expect!(snapshot_content).to(equal("modified in snapshot"));
Ok(())
}
#[test]
fn modifying_file_in_source_does_not_affect_snapshot() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
let mut original_content = String::new();
let mut snapshot_content = String::new();
conn.exec(|fs| {
let mut file = fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(b"original")?;
drop(file);
let snapshot = fs.copy_root(Some("snapshot"))?;
let mut file = fs.open("test.txt")?;
file.write_all(b"modified in original")?;
drop(file);
let mut file = fs.open("test.txt")?;
file.read_to_string(&mut original_content)?;
drop(file);
fs.switch_root(snapshot.id)?;
let mut file = fs.open("test.txt")?;
file.read_to_string(&mut snapshot_content)?;
liteboxfs::Result::Ok(())
})?;
expect!(original_content).to(equal("modified in original"));
expect!(snapshot_content).to(equal("original"));
Ok(())
}
#[test]
fn deleting_file_in_snapshot_does_not_affect_source() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let default_root_id = fs.root_id();
fs.create("test.txt", FileKind::Regular, Owner::ROOT)?;
let snapshot = fs.copy_root(Some("snapshot"))?;
fs.switch_root(snapshot.id)?;
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")),
));
fs.switch_root(default_root_id)?;
expect!(fs.open("test.txt")).to(be_ok());
liteboxfs::Result::Ok(())
})?;
Ok(())
}
}
mod errors {
use super::*;
#[test]
fn snapshot_with_duplicate_name_returns_root_already_exists() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
fs.copy_root(Some("snapshot"))?;
expect!(fs.copy_root(Some("snapshot")))
.to(be_err())
.to(match_pattern(
pattern!(Error::RootAlreadyExists { name } if name == "snapshot"),
));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
}