use std::io::{Read, Write};
use std::path::PathBuf;
use xpct::{be_err, be_ok, equal, expect, match_pattern, pattern};
use liteboxfs::{
Connection, CreateOptions, Error, FileBy, FileOrigin,
metadata::{FileKind, Owner},
};
mod basic {
use super::*;
#[test]
fn copy_tree_directory_with_files() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
fs.create("dir", FileKind::Dir, Owner::ROOT)?;
let mut file1 = fs.create("dir/file1.txt", FileKind::Regular, Owner::ROOT)?;
file1.write_all(b"content1")?;
drop(file1);
let mut file2 = fs.create("dir/file2.txt", FileKind::Regular, Owner::ROOT)?;
file2.write_all(b"content2")?;
drop(file2);
expect!(fs.copy_tree("dir", "dir_copy", fs.root_id())).to(be_ok());
expect!(fs.open("dir")).to(be_ok());
expect!(fs.open("dir/file1.txt")).to(be_ok());
expect!(fs.open("dir/file2.txt")).to(be_ok());
expect!(fs.open("dir_copy")).to(be_ok());
let mut copied1 = fs.open("dir_copy/file1.txt")?;
let mut content1 = String::new();
copied1.read_to_string(&mut content1)?;
drop(copied1);
expect!(content1).to(equal("content1"));
let mut copied2 = fs.open("dir_copy/file2.txt")?;
let mut content2 = String::new();
copied2.read_to_string(&mut content2)?;
drop(copied2);
expect!(content2).to(equal("content2"));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn copy_tree_nested_directories() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
fs.create("dir", FileKind::Dir, Owner::ROOT)?;
fs.create("dir/sub1", FileKind::Dir, Owner::ROOT)?;
fs.create("dir/sub1/sub2", FileKind::Dir, Owner::ROOT)?;
let mut file = fs.create("dir/sub1/sub2/file.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(b"nested content")?;
drop(file);
expect!(fs.copy_tree("dir", "copy", fs.root_id())).to(be_ok());
expect!(fs.open("copy")).to(be_ok());
expect!(fs.open("copy/sub1")).to(be_ok());
expect!(fs.open("copy/sub1/sub2")).to(be_ok());
let mut copied = fs.open("copy/sub1/sub2/file.txt")?;
let mut content = String::new();
copied.read_to_string(&mut content)?;
expect!(content).to(equal("nested content"));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn copy_tree_single_file() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let mut file = fs.create("file.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(b"content")?;
drop(file);
expect!(fs.copy_tree("file.txt", "file_copy.txt", fs.root_id())).to(be_ok());
let mut copied = fs.open("file_copy.txt")?;
let mut content = String::new();
copied.read_to_string(&mut content)?;
expect!(content).to(equal("content"));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn copy_root_directory() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
fs.create("one.txt", FileKind::Regular, Owner::ROOT)?;
fs.create("two.txt", FileKind::Regular, Owner::ROOT)?;
let new_root = fs.create_root(None)?;
expect!(fs.copy_tree("/", "dest", new_root.id)).to(be_ok());
expect!(fs.open("one.txt")).to(be_ok());
expect!(fs.open("two.txt")).to(be_ok());
fs.switch_root(new_root.id)?;
expect!(fs.open("dest/one.txt")).to(be_ok());
expect!(fs.open("dest/two.txt")).to(be_ok());
liteboxfs::Result::Ok(())
})?;
Ok(())
}
}
mod cross_root {
use super::*;
#[test]
fn copy_tree_to_different_root() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
fs.create("dir", FileKind::Dir, Owner::ROOT)?;
let mut file = fs.create("dir/file.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(b"content")?;
drop(file);
let other_root = fs.create_root(Some("other"))?;
expect!(fs.copy_tree("dir", "copied_dir", other_root.id)).to(be_ok());
expect!(fs.open("dir")).to(be_ok());
expect!(fs.open("dir/file.txt")).to(be_ok());
fs.switch_root(other_root.id)?;
expect!(fs.open("copied_dir")).to(be_ok());
let mut copied = fs.open("copied_dir/file.txt")?;
let mut content = String::new();
copied.read_to_string(&mut content)?;
expect!(content).to(equal("content"));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn copy_tree_to_root_by_name() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
fs.create("dir", FileKind::Dir, Owner::ROOT)?;
let mut file = fs.create("dir/file.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(b"content")?;
drop(file);
let other_root = fs.create_root(Some("other"))?;
expect!(fs.copy_tree("dir", "copied_dir", "other")).to(be_ok());
fs.switch_root(other_root.id)?;
let mut copied = fs.open("copied_dir/file.txt")?;
let mut content = String::new();
copied.read_to_string(&mut content)?;
expect!(content).to(equal("content"));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn copy_tree_to_default_root_by_selector() -> liteboxfs::Result<()> {
use liteboxfs::RootBy;
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let default_root_id = fs.root_id();
let other_root = fs.create_root(Some("other"))?;
fs.switch_root(other_root.id)?;
fs.create("dir", FileKind::Dir, Owner::ROOT)?;
let mut file = fs.create("dir/file.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(b"content")?;
drop(file);
expect!(fs.copy_tree("dir", "copied_dir", RootBy::Default)).to(be_ok());
fs.switch_root(default_root_id)?;
let mut copied = fs.open("copied_dir/file.txt")?;
let mut content = String::new();
copied.read_to_string(&mut content)?;
expect!(content).to(equal("content"));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
}
mod isolation {
use super::*;
#[test]
fn modifying_copy_does_not_affect_original() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
fs.create("dir", FileKind::Dir, Owner::ROOT)?;
let mut file = fs.create("dir/file.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(b"original")?;
drop(file);
fs.copy_tree("dir", "dir_copy", fs.root_id())?;
let mut copied = fs.open("dir_copy/file.txt")?;
copied.write_all(b"modified")?;
drop(copied);
let mut original = fs.open("dir/file.txt")?;
let mut content = String::new();
original.read_to_string(&mut content)?;
expect!(content).to(equal("original"));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
}
mod errors {
use liteboxfs::RootBy;
use super::*;
#[test]
fn copy_tree_source_not_found() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
expect!(fs.copy_tree("nonexistent", "dest", fs.root_id()))
.to(be_err())
.to(match_pattern(
pattern!(Error::FileNotFound { file: FileOrigin::Litebox { locator, .. }, .. } if locator == &FileBy::from("/nonexistent")),
));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn copy_tree_dest_already_exists() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
fs.create("source", FileKind::Dir, Owner::ROOT)?;
fs.create("dest", FileKind::Dir, Owner::ROOT)?;
expect!(fs.copy_tree("source", "dest", fs.root_id()))
.to(be_err())
.to(match_pattern(
pattern!(Error::FileAlreadyExists { path: FileOrigin::Litebox { locator, .. }, .. } if locator == &PathBuf::from("/dest")),
));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn copy_tree_dest_parent_not_found() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
fs.create("source", FileKind::Dir, Owner::ROOT)?;
expect!(fs.copy_tree("source", "nonexistent/dest", fs.root_id()))
.to(be_err())
.to(match_pattern(
pattern!(Error::NoParentDirectory { file: FileOrigin::Litebox { locator, .. }, .. } if locator == &FileBy::from("/nonexistent/dest")),
));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn copy_tree_dest_not_a_directory() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
fs.create("source", FileKind::Dir, Owner::ROOT)?;
let mut file = fs.create("regular.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(b"data")?;
drop(file);
expect!(fs.copy_tree("source", "regular.txt/dest", fs.root_id()))
.to(be_err())
.to(match_pattern(
pattern!(Error::NotADirectory { file: FileOrigin::Litebox { locator, .. }, .. } if locator == &FileBy::from("/regular.txt")),
));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn copy_tree_dest_is_descendant_of_source() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
fs.create("dir", FileKind::Dir, Owner::ROOT)?;
expect!(fs.copy_tree("dir", "dir/subdir", fs.root_id()))
.to(be_err())
.to(match_pattern(
pattern!(Error::InvalidPath { path: FileOrigin::Litebox { locator, .. }, .. } if locator == &PathBuf::from("dir/subdir")),
));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn copy_tree_invalid_dest_root() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
fs.create("source", FileKind::Dir, Owner::ROOT)?;
let invalid_root_id = "00000000-0000-0000-0000-000000000000".parse().unwrap();
expect!(fs.copy_tree("source", "dest", invalid_root_id))
.to(be_err())
.to(match_pattern(
pattern!(Error::RootNotFound { root } if root == &RootBy::Id(invalid_root_id)),
));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn copy_tree_dest_descendant_allowed_when_cross_root() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let default_root_id = fs.root_id();
fs.create("dir", FileKind::Dir, Owner::ROOT)?;
let other_root = fs.create_root(Some("other"))?;
fs.switch_root(other_root.id)?;
fs.create("dir", FileKind::Dir, Owner::ROOT)?;
fs.switch_root(default_root_id)?;
expect!(fs.copy_tree("dir", "dir/subdir", other_root.id)).to(be_ok());
fs.switch_root(other_root.id)?;
expect!(fs.open("dir/subdir")).to(be_ok());
liteboxfs::Result::Ok(())
})?;
Ok(())
}
}