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_regular_file() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let mut file = fs.create("original.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(b"content")?;
drop(file);
expect!(fs.copy("original.txt", "copied.txt", fs.root_id())).to(be_ok());
let mut original = fs.open("original.txt")?;
let mut original_content = String::new();
original.read_to_string(&mut original_content)?;
drop(original);
let mut copied = fs.open("copied.txt")?;
let mut copied_content = String::new();
copied.read_to_string(&mut copied_content)?;
drop(copied);
expect!(original_content).to(equal("content"));
expect!(copied_content).to(equal("content"));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn copy_empty_directory() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
fs.create("dir1", FileKind::Dir, Owner::ROOT)?;
expect!(fs.copy("dir1", "dir2", fs.root_id())).to(be_ok());
expect!(fs.open("dir1")).to(be_ok());
expect!(fs.open("dir2")).to(be_ok());
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn copy_to_different_directory() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let mut file = fs.create("original.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(b"content")?;
drop(file);
fs.create("dir", FileKind::Dir, Owner::ROOT)?;
expect!(fs.copy("original.txt", "dir/copied.txt", fs.root_id())).to(be_ok());
let mut copied = fs.open("dir/copied.txt")?;
let mut copied_content = String::new();
copied.read_to_string(&mut copied_content)?;
expect!(copied_content).to(equal("content"));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn copy_symlink() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
fs.create(
"link",
FileKind::Symlink {
target: PathBuf::from("/target/path"),
},
Owner::ROOT,
)?;
expect!(fs.copy("link", "link_copy", fs.root_id())).to(be_ok());
expect!(fs.open("link")).to(be_ok());
expect!(fs.open("link_copy")).to(be_ok());
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn copy_root_directory() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let new_root = fs.create_root(None)?;
expect!(fs.copy("/", "dest", new_root.id)).to(be_ok());
expect!(fs.open("/")).to(be_ok());
fs.switch_root(new_root.id)?;
expect!(fs.open("dest")).to(be_ok());
liteboxfs::Result::Ok(())
})?;
Ok(())
}
}
mod cross_root {
use super::*;
#[test]
fn copy_to_different_root() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let mut file = fs.create("original.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(b"content")?;
drop(file);
let other_root = fs.create_root(Some("other"))?;
expect!(fs.copy("original.txt", "copied.txt", other_root.id)).to(be_ok());
expect!(fs.open("original.txt")).to(be_ok());
fs.switch_root(other_root.id)?;
let mut copied = fs.open("copied.txt")?;
let mut copied_content = String::new();
copied.read_to_string(&mut copied_content)?;
expect!(copied_content).to(equal("content"));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn copy_with_path_change_to_different_root() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let default_root_id = fs.root_id();
let mut file = fs.create("original.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(b"content")?;
drop(file);
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("original.txt", "dir/renamed.txt", other_root.id)).to(be_ok());
fs.switch_root(other_root.id)?;
let mut copied = fs.open("dir/renamed.txt")?;
let mut copied_content = String::new();
copied.read_to_string(&mut copied_content)?;
expect!(copied_content).to(equal("content"));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn copy_to_root_by_name() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let mut file = fs.create("original.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(b"content")?;
drop(file);
let other_root = fs.create_root(Some("other"))?;
expect!(fs.copy("original.txt", "copied.txt", "other")).to(be_ok());
fs.switch_root(other_root.id)?;
let mut copied = fs.open("copied.txt")?;
let mut copied_content = String::new();
copied.read_to_string(&mut copied_content)?;
expect!(copied_content).to(equal("content"));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn copy_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)?;
let mut file = fs.create("original.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(b"content")?;
drop(file);
expect!(fs.copy("original.txt", "copied.txt", RootBy::Default)).to(be_ok());
fs.switch_root(default_root_id)?;
let mut copied = fs.open("copied.txt")?;
let mut copied_content = String::new();
copied.read_to_string(&mut copied_content)?;
expect!(copied_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| {
let mut file = fs.create("original.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(b"original")?;
drop(file);
fs.copy("original.txt", "copied.txt", fs.root_id())?;
let mut copied = fs.open("copied.txt")?;
copied.write_all(b"modified")?;
drop(copied);
let mut original = fs.open("original.txt")?;
let mut original_content = String::new();
original.read_to_string(&mut original_content)?;
drop(original);
let mut copied = fs.open("copied.txt")?;
let mut copied_content = String::new();
copied.read_to_string(&mut copied_content)?;
drop(copied);
expect!(original_content).to(equal("original"));
expect!(copied_content).to(equal("modified"));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn modifying_original_does_not_affect_copy() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let mut file = fs.create("original.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(b"original")?;
drop(file);
fs.copy("original.txt", "copied.txt", fs.root_id())?;
let mut original = fs.open("original.txt")?;
original.write_all(b"modified")?;
drop(original);
let mut original = fs.open("original.txt")?;
let mut original_content = String::new();
original.read_to_string(&mut original_content)?;
drop(original);
let mut copied = fs.open("copied.txt")?;
let mut copied_content = String::new();
copied.read_to_string(&mut copied_content)?;
drop(copied);
expect!(original_content).to(equal("modified"));
expect!(copied_content).to(equal("original"));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn deleting_copy_does_not_affect_original() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
let mut file = fs.create("original.txt", FileKind::Regular, Owner::ROOT)?;
file.write_all(b"content")?;
drop(file);
fs.copy("original.txt", "copied.txt", fs.root_id())?;
fs.delete("copied.txt")?;
let mut original = fs.open("original.txt")?;
let mut content = String::new();
original.read_to_string(&mut content)?;
expect!(content).to(equal("content"));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
}
mod errors {
use liteboxfs::RootBy;
use super::*;
#[test]
fn copy_source_not_found() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
expect!(fs.copy("nonexistent.txt", "dest.txt", fs.root_id()))
.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 copy_dest_already_exists() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
fs.create("original.txt", FileKind::Regular, Owner::ROOT)?;
fs.create("existing.txt", FileKind::Regular, Owner::ROOT)?;
expect!(fs.copy("original.txt", "existing.txt", fs.root_id()))
.to(be_err())
.to(match_pattern(
pattern!(Error::FileAlreadyExists { path: FileOrigin::Litebox { locator, .. }, .. } if locator == &PathBuf::from("/existing.txt")),
));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn copy_dest_parent_not_found() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
fs.create("original.txt", FileKind::Regular, Owner::ROOT)?;
expect!(fs.copy("original.txt", "nonexistent/dest.txt", fs.root_id()))
.to(be_err())
.to(match_pattern(
pattern!(Error::NoParentDirectory { file: FileOrigin::Litebox { locator, .. }, .. } if locator == &FileBy::from("/nonexistent/dest.txt")),
));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn copy_dest_parent_not_a_directory() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
fs.create("original.txt", FileKind::Regular, Owner::ROOT)?;
fs.create("file.txt", FileKind::Regular, Owner::ROOT)?;
expect!(fs.copy("original.txt", "file.txt/dest.txt", fs.root_id()))
.to(be_err())
.to(match_pattern(
pattern!(Error::NotADirectory { file: FileOrigin::Litebox { locator, .. }, .. } if locator == &FileBy::from("/file.txt")),
));
liteboxfs::Result::Ok(())
})?;
Ok(())
}
#[test]
fn copy_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("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_invalid_dest_root() -> liteboxfs::Result<()> {
let mut conn = Connection::open_in_memory(&CreateOptions::new())?;
conn.exec(|fs| {
fs.create("original.txt", FileKind::Regular, Owner::ROOT)?;
let invalid_root_id = "00000000-0000-0000-0000-000000000000".parse().unwrap();
expect!(fs.copy("original.txt", "dest.txt", 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_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("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(())
}
}