use std::{
cell::RefCell,
collections::BTreeMap,
ffi::OsStr,
io::Write,
process::{Command, Stdio},
rc::Rc,
};
use similar_asserts::assert_eq;
use tempfile::NamedTempFile;
use composefs::{
dumpfile::write_dumpfile,
erofs::{debug::debug_img, writer::mkfs_erofs},
fsverity::{FsVerityHashValue, Sha256HashValue},
tree::{Directory, FileSystem, Inode, Leaf, LeafContent, RegularFile, Stat},
};
fn debug_fs(mut fs: FileSystem<impl FsVerityHashValue>) -> String {
fs.ensure_root_stat();
let image = mkfs_erofs(&fs);
let mut output = vec![];
debug_img(&mut output, &image).unwrap();
String::from_utf8(output).unwrap()
}
fn empty(_fs: &mut FileSystem<impl FsVerityHashValue>) {}
#[test]
fn test_empty() {
let mut fs = FileSystem::<Sha256HashValue>::default();
empty(&mut fs);
insta::assert_snapshot!(debug_fs(fs));
}
fn add_leaf<ObjectID: FsVerityHashValue>(
dir: &mut Directory<ObjectID>,
name: impl AsRef<OsStr>,
content: LeafContent<ObjectID>,
) {
dir.insert(
name.as_ref(),
Inode::Leaf(Rc::new(Leaf {
content,
stat: Stat {
st_gid: 0,
st_uid: 0,
st_mode: 0,
st_mtim_sec: 0,
xattrs: RefCell::new(BTreeMap::new()),
},
})),
);
}
fn simple(fs: &mut FileSystem<Sha256HashValue>) {
let ext_id = Sha256HashValue::from_hex(
"5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a",
)
.unwrap();
add_leaf(&mut fs.root, "fifo", LeafContent::Fifo);
add_leaf(
&mut fs.root,
"regular-inline",
LeafContent::Regular(RegularFile::Inline((*b"hihi").into())),
);
add_leaf(
&mut fs.root,
"regular-external",
LeafContent::Regular(RegularFile::External(ext_id, 1234)),
);
add_leaf(&mut fs.root, "chrdev", LeafContent::CharacterDevice(123));
add_leaf(&mut fs.root, "blkdev", LeafContent::BlockDevice(123));
add_leaf(&mut fs.root, "socket", LeafContent::Socket);
add_leaf(
&mut fs.root,
"symlink",
LeafContent::Symlink(OsStr::new("/target").into()),
);
}
#[test]
fn test_simple() {
let mut fs = FileSystem::<Sha256HashValue>::default();
simple(&mut fs);
insta::assert_snapshot!(debug_fs(fs));
}
fn foreach_case(f: fn(&FileSystem<Sha256HashValue>)) {
for case in [empty, simple] {
let mut fs = FileSystem::default();
case(&mut fs);
fs.ensure_root_stat();
f(&fs);
}
}
#[test_with::executable(fsck.erofs)]
fn test_fsck() {
foreach_case(|fs| {
let mut tmp = NamedTempFile::new().unwrap();
tmp.write_all(&mkfs_erofs(fs)).unwrap();
let mut fsck = Command::new("fsck.erofs").arg(tmp.path()).spawn().unwrap();
assert!(fsck.wait().unwrap().success());
});
}
fn dump_image(img: &[u8]) -> String {
let mut dump = vec![];
debug_img(&mut dump, img).unwrap();
String::from_utf8(dump).unwrap()
}
#[should_panic]
#[test_with::executable(mkcomposefs)]
fn test_vs_mkcomposefs() {
foreach_case(|fs| {
let image = mkfs_erofs(fs);
let mut mkcomposefs = Command::new("mkcomposefs")
.args(["--min-version=3", "--from-file", "-", "-"])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.unwrap();
let mut stdin = mkcomposefs.stdin.take().unwrap();
write_dumpfile(&mut stdin, fs).unwrap();
drop(stdin);
let output = mkcomposefs.wait_with_output().unwrap();
assert!(output.status.success());
let mkcomposefs_image = output.stdout.into_boxed_slice();
if image != mkcomposefs_image {
let dump = dump_image(&image);
let mkcomposefs_dump = dump_image(&mkcomposefs_image);
assert_eq!(mkcomposefs_dump, dump);
}
assert_eq!(image, mkcomposefs_image); });
}