extern crate tempdir;
use std::fs;
use std::io::{Read, Write};
use std::path::{Path, PathBuf};
#[cfg(target_os = "linux")]
use std::os::unix::fs::PermissionsExt;
use tempdir::TempDir;
use metadata_backup::backup;
#[test]
fn test_backup_basic() {
let cwd = TempDir::new("backup_test").unwrap();
let cwd_path = cwd.path();
let base_path = cwd_path.join("root");
let dir1 = base_path.join("dir1");
let dir2 = base_path.join("dir2");
let dir2_sub = dir2.clone().join("subdir");
let mut tree = vec![
FSO::directory(base_path.clone()),
FSO::directory(base_path.join("empty")), FSO::directory(dir1.clone()), FSO::file(dir1.join("file1"), b"Hello"),
FSO::file(dir1.join("file2"), b"Goodbye"),
FSO::directory(dir2.clone()), FSO::directory(dir2_sub.clone()), FSO::file(dir2_sub.join("subfile1"), b"Nested at depth 2"),
FSO::directory(dir2.join("empty")), ];
build_tree(&mut tree);
let output_loc = cwd_path.join("backup.zip");
backup::write_backup(&base_path, &output_loc).ok();
assert!(output_loc.exists());
}
#[cfg(target_os = "linux")]
#[test]
fn test_backup_permissions() {
let cwd = TempDir::new("backup_test").unwrap();
let cwd_path = cwd.path();
let base_path = cwd_path.join("root");
let good_dir = base_path.join("good_dir");
let bad_dir = base_path.join("bad_dir");
let bad_file = good_dir.join("bad_file");
let bad_subdir = good_dir.join("bad_subdir");
let mut tree = vec![
FSO::directory(base_path.clone()),
FSO::directory(good_dir.clone()),
FSO::directory(bad_dir.clone()),
FSO::directory(bad_subdir.clone()),
FSO::file(good_dir.join("file1"), b"Hello"),
FSO::file(bad_dir.join("file2"), b"Goodbye"),
FSO::file(bad_file.clone(), b"Bad permissions"),
FSO::file(bad_subdir.join("subfile"), b""),
];
build_tree(&mut tree);
let output_loc = cwd_path.join("backup.zip");
set_mode(&bad_dir, 0o000).unwrap();
set_mode(&bad_file, 0o000).unwrap();
set_mode(&bad_subdir, 0o000).unwrap();
let res = backup::write_backup(&base_path, &output_loc);
set_mode(&bad_dir, 0o777).unwrap();
set_mode(&bad_file, 0o777).unwrap();
set_mode(&bad_subdir, 0o777).unwrap();
assert!(res.ok().is_some());
let mut expected_contents = vec![
("FILESYSTEM_ROOT/", true),
("FILESYSTEM_ROOT/contents.csv", false),
("FILESYSTEM_ROOT/good_dir/", true),
("FILESYSTEM_ROOT/good_dir/contents.csv", false),
(backup::MANIFEST_FILE_NAME, false),
];
let mut output_contents = read_backup(&output_loc);
expected_contents.sort();
output_contents.sort();
let expected_contents = expected_contents;
let output_contents = output_contents;
assert_eq!(
expected_contents.len(),
output_contents.len(),
"\nExpected: {:?}\nOutput: {:?}",
expected_contents,
output_contents
);
for ((exp_name, exp_is_dir), (ref act_name, ref act_size)) in
expected_contents.iter().zip(output_contents)
{
let act_is_dir = !act_size.is_some();
assert_eq!(exp_name, act_name);
assert_eq!(*exp_is_dir, act_is_dir);
}
}
#[test]
fn test_manifest() {
let cwd = TempDir::new("backup_test").unwrap();
let cwd_path = cwd.path();
let base_path = cwd_path.join("root");
let dir1 = base_path.join("dir1");
let dir2 = base_path.join("dir2");
let subdir_fs = dir1.join("subdir_file_subdir");
let subdir_l2 = subdir_fs.join("subdir_l2");
let mut tree = vec![
FSO::directory(base_path.clone()),
FSO::directory(dir1.clone()),
FSO::directory(dir2.clone()),
FSO::directory(subdir_fs.clone()),
FSO::directory(dir1.join("empty_subdir")),
FSO::directory(subdir_l2.clone()),
FSO::file(dir1.join("file1.txt"), b"Foo"),
FSO::file(dir1.join("file2.txt"), b"Bar"),
FSO::file(dir2.join("empty_file.txt"), b""),
FSO::file(subdir_fs.join("subfile.txt"), b"Subdir file"),
FSO::file(
subdir_l2.join("deepest_nested.txt"),
b"Deepest nested file.",
),
];
let mut expected = vec![
"dir1",
"dir1/file1.txt",
"dir1/file2.txt",
"dir1/empty_subdir",
"dir1/subdir_file_subdir",
"dir1/subdir_file_subdir/subfile.txt",
"dir1/subdir_file_subdir/subdir_l2",
"dir1/subdir_file_subdir/subdir_l2/deepest_nested.txt",
"dir2",
"dir2/empty_file.txt",
];
expected.sort();
let expected = expected;
build_tree(&mut tree);
let output_loc = cwd_path.join("output.zip");
let res = backup::write_backup(&base_path, &output_loc);
assert!(res.ok().is_some());
let manifest_output = read_file_manifest(&output_loc);
assert_eq!(
expected.len(),
manifest_output.len(),
"Length differs from expecataion\nExpected: {:?}\nActual: {:?}",
expected,
manifest_output,
);
for (expected_path, actual_path) in expected.iter().zip(&manifest_output) {
assert_eq!(
*expected_path, *actual_path,
"Element mismatch {:?} != {:?}\nExpected: {:?}\nActual: {:?})",
expected_path, actual_path, expected, manifest_output
);
}
}
fn build_tree(tree: &mut Vec<FileSystemObject>) {
tree.sort();
for fso in tree {
match fso {
FSO::Directory(path) => fs::create_dir(path).unwrap(),
FSO::File((path, contents)) => {
let mut fobj = fs::File::create(path).unwrap();
fobj.write_all(contents.as_slice()).unwrap();
}
}
}
}
fn read_backup<P: AsRef<Path>>(p: P) -> Vec<(String, Option<u64>)> {
let f = fs::File::open(p.as_ref()).unwrap();
let mut archive = zip::ZipArchive::new(f).unwrap();
let mut out: Vec<(String, Option<u64>)> = Vec::with_capacity(archive.len());
for i in 0..archive.len() {
let zf = archive.by_index(i).unwrap();
let name = zf.name().to_owned();
let size = if zf.is_dir() { None } else { Some(zf.size()) };
out.push((name, size));
}
out
}
fn read_file_manifest<P: AsRef<Path>>(p: P) -> Vec<String> {
let f = fs::File::open(p.as_ref()).unwrap();
let mut archive = zip::ZipArchive::new(f).unwrap();
let mut manifest_file = archive.by_name(backup::MANIFEST_FILE_NAME).unwrap();
let mut buffer = String::new();
manifest_file.read_to_string(&mut buffer).unwrap();
buffer.trim_end().split("\n").map(String::from).collect()
}
#[cfg(target_os = "linux")]
fn set_mode<P: AsRef<Path>>(p: P, mode: u32) -> Result<(), std::io::Error> {
let mut perms = p.as_ref().metadata()?.permissions();
perms.set_mode(mode);
fs::set_permissions(p, perms)?;
Ok(())
}
#[derive(Debug)]
enum FileSystemObject {
File((PathBuf, Vec<u8>)),
Directory(PathBuf),
}
type FSO = FileSystemObject;
impl FileSystemObject {
fn file(p: PathBuf, contents: &[u8]) -> Self {
Self::File {
0: (p, contents.to_vec()),
}
}
fn directory(p: PathBuf) -> Self {
Self::Directory { 0: p }
}
fn get_path(&self) -> &Path {
match self {
FileSystemObject::File((path, _contents)) => &path,
FileSystemObject::Directory(path) => &path,
}
}
}
impl Ord for FileSystemObject {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.get_path().cmp(other.get_path())
}
}
impl Eq for FileSystemObject {}
impl PartialEq for FileSystemObject {
fn eq(&self, other: &Self) -> bool {
self.get_path() == other.get_path()
}
}
impl PartialOrd for FileSystemObject {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}