Skip to content

Commit 5676ea0

Browse files
committed
tree: Add unit tests for Directory
Co-authored-by: Gemini Code Assist (The code didn't compile at first but the changes were minor, some of the unit tests were plausible but didn't actually pass not due to bugs in the code, so I just deleted them) Signed-off-by: Colin Walters <[email protected]>
1 parent a13ab5e commit 5676ea0

File tree

1 file changed

+291
-0
lines changed

1 file changed

+291
-0
lines changed

src/tree.rs

Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,3 +437,294 @@ impl<ObjectID: FsVerityHashValue> FileSystem<ObjectID> {
437437
}
438438
}
439439
}
440+
441+
#[cfg(test)]
442+
mod tests {
443+
use super::*;
444+
use crate::fsverity::Sha256HashValue;
445+
use std::cell::RefCell;
446+
use std::collections::BTreeMap;
447+
use std::ffi::{OsStr, OsString};
448+
use std::rc::Rc;
449+
450+
// Helper to create a Stat with a specific mtime
451+
fn stat_with_mtime(mtime: i64) -> Stat {
452+
Stat {
453+
st_mode: 0o755,
454+
st_uid: 1000,
455+
st_gid: 1000,
456+
st_mtim_sec: mtime,
457+
xattrs: RefCell::new(BTreeMap::new()),
458+
}
459+
}
460+
461+
// Helper to create a simple Leaf (e.g., an empty inline file)
462+
fn new_leaf_file(mtime: i64) -> Rc<Leaf<Sha256HashValue>> {
463+
Rc::new(Leaf {
464+
stat: stat_with_mtime(mtime),
465+
content: LeafContent::Regular(RegularFile::Inline(Box::new([]))),
466+
})
467+
}
468+
469+
// Helper to create a simple Leaf (symlink)
470+
fn new_leaf_symlink(target: &str, mtime: i64) -> Rc<Leaf<Sha256HashValue>> {
471+
Rc::new(Leaf {
472+
stat: stat_with_mtime(mtime),
473+
content: LeafContent::Symlink(OsString::from(target).into_boxed_os_str()),
474+
})
475+
}
476+
477+
// Helper to create an empty Directory Inode with a specific mtime
478+
fn new_dir_inode(mtime: i64) -> Inode<Sha256HashValue> {
479+
Inode::Directory(Box::new(Directory {
480+
stat: stat_with_mtime(mtime),
481+
entries: BTreeMap::new(),
482+
}))
483+
}
484+
485+
// Helper to create a Directory Inode with specific stat
486+
fn new_dir_inode_with_stat(stat: Stat) -> Inode<Sha256HashValue> {
487+
Inode::Directory(Box::new(Directory {
488+
stat,
489+
entries: BTreeMap::new(),
490+
}))
491+
}
492+
493+
#[test]
494+
fn test_directory_default() {
495+
let dir = Directory::<Sha256HashValue>::default();
496+
assert_eq!(dir.stat.st_uid, 0);
497+
assert_eq!(dir.stat.st_gid, 0);
498+
assert_eq!(dir.stat.st_mode, 0o555);
499+
assert_eq!(dir.stat.st_mtim_sec, 0);
500+
assert!(dir.stat.xattrs.borrow().is_empty());
501+
assert!(dir.entries.is_empty());
502+
}
503+
504+
#[test]
505+
fn test_directory_new() {
506+
let stat = stat_with_mtime(123);
507+
let dir = Directory::<Sha256HashValue>::new(stat);
508+
assert_eq!(dir.stat.st_mtim_sec, 123);
509+
assert!(dir.entries.is_empty());
510+
}
511+
512+
#[test]
513+
fn test_insert_and_get_leaf() {
514+
let mut dir = Directory::<Sha256HashValue>::default();
515+
let leaf = new_leaf_file(10);
516+
dir.insert(OsStr::new("file.txt"), Inode::Leaf(Rc::clone(&leaf)));
517+
assert_eq!(dir.entries.len(), 1);
518+
519+
let retrieved_leaf_rc = dir.ref_leaf(OsStr::new("file.txt")).unwrap();
520+
assert!(Rc::ptr_eq(&retrieved_leaf_rc, &leaf));
521+
522+
let regular_file_content = dir.get_file(OsStr::new("file.txt")).unwrap();
523+
assert!(matches!(regular_file_content, RegularFile::Inline(_)));
524+
}
525+
526+
#[test]
527+
fn test_insert_and_get_directory() {
528+
let mut dir = Directory::<Sha256HashValue>::default();
529+
let sub_dir_inode = new_dir_inode(20);
530+
dir.insert(OsStr::new("subdir"), sub_dir_inode);
531+
assert_eq!(dir.entries.len(), 1);
532+
533+
let retrieved_subdir = dir.get_directory(OsStr::new("subdir")).unwrap();
534+
assert_eq!(retrieved_subdir.stat.st_mtim_sec, 20);
535+
536+
let retrieved_subdir_opt = dir
537+
.get_directory_opt(OsStr::new("subdir"))
538+
.unwrap()
539+
.unwrap();
540+
assert_eq!(retrieved_subdir_opt.stat.st_mtim_sec, 20);
541+
}
542+
543+
#[test]
544+
fn test_get_directory_errors() {
545+
let mut root = Directory::<Sha256HashValue>::default();
546+
root.insert(OsStr::new("dir1"), new_dir_inode(10));
547+
root.insert(OsStr::new("file1"), Inode::Leaf(new_leaf_file(30)));
548+
549+
match root.get_directory(OsStr::new("nonexistent")) {
550+
Err(ImageError::NotFound(name)) => assert_eq!(name.to_str().unwrap(), "nonexistent"),
551+
_ => panic!("Expected NotFound"),
552+
}
553+
assert!(root
554+
.get_directory_opt(OsStr::new("nonexistent"))
555+
.unwrap()
556+
.is_none());
557+
558+
match root.get_directory(OsStr::new("file1")) {
559+
Err(ImageError::NotADirectory(name)) => assert_eq!(name.to_str().unwrap(), "file1"),
560+
_ => panic!("Expected NotADirectory"),
561+
}
562+
}
563+
564+
#[test]
565+
fn test_get_file_errors() {
566+
let mut dir = Directory::<Sha256HashValue>::default();
567+
dir.insert(OsStr::new("subdir"), new_dir_inode(10));
568+
dir.insert(
569+
OsStr::new("link.txt"),
570+
Inode::Leaf(new_leaf_symlink("target", 20)),
571+
);
572+
573+
match dir.get_file(OsStr::new("nonexistent.txt")) {
574+
Err(ImageError::NotFound(name)) => {
575+
assert_eq!(name.to_str().unwrap(), "nonexistent.txt")
576+
}
577+
_ => panic!("Expected NotFound"),
578+
}
579+
assert!(dir
580+
.get_file_opt(OsStr::new("nonexistent.txt"))
581+
.unwrap()
582+
.is_none());
583+
584+
match dir.get_file(OsStr::new("subdir")) {
585+
Err(ImageError::IsADirectory(name)) => assert_eq!(name.to_str().unwrap(), "subdir"),
586+
_ => panic!("Expected IsADirectory"),
587+
}
588+
match dir.get_file(OsStr::new("link.txt")) {
589+
Err(ImageError::IsNotRegular(name)) => assert_eq!(name.to_str().unwrap(), "link.txt"),
590+
res => panic!("Expected IsNotRegular, got {:?}", res),
591+
}
592+
}
593+
594+
#[test]
595+
fn test_remove() {
596+
let mut dir = Directory::<Sha256HashValue>::default();
597+
dir.insert(OsStr::new("file1.txt"), Inode::Leaf(new_leaf_file(10)));
598+
dir.insert(OsStr::new("subdir"), new_dir_inode(20));
599+
assert_eq!(dir.entries.len(), 2);
600+
601+
dir.remove(OsStr::new("file1.txt"));
602+
assert_eq!(dir.entries.len(), 1);
603+
assert!(dir.entries.get(OsStr::new("file1.txt")).is_none());
604+
605+
dir.remove(OsStr::new("nonexistent")); // Should be no-op
606+
assert_eq!(dir.entries.len(), 1);
607+
}
608+
609+
#[test]
610+
fn test_merge() {
611+
let mut dir = Directory::<Sha256HashValue>::default();
612+
613+
// Merge Leaf onto empty
614+
dir.merge(OsStr::new("item"), Inode::Leaf(new_leaf_file(10)));
615+
assert_eq!(
616+
dir.entries
617+
.get(OsStr::new("item"))
618+
.unwrap()
619+
.stat()
620+
.st_mtim_sec,
621+
10
622+
);
623+
624+
// Merge Directory onto existing Directory
625+
let mut existing_dir_inode = new_dir_inode_with_stat(stat_with_mtime(80));
626+
if let Inode::Directory(ref mut ed_box) = existing_dir_inode {
627+
ed_box.insert(OsStr::new("inner_file"), Inode::Leaf(new_leaf_file(85)));
628+
}
629+
dir.insert(OsStr::new("merged_dir"), existing_dir_inode);
630+
631+
let new_merging_dir_inode = new_dir_inode_with_stat(stat_with_mtime(90));
632+
dir.merge(OsStr::new("merged_dir"), new_merging_dir_inode);
633+
634+
match dir.entries.get(OsStr::new("merged_dir")) {
635+
Some(Inode::Directory(d)) => {
636+
assert_eq!(d.stat.st_mtim_sec, 90); // Stat updated
637+
assert_eq!(d.entries.len(), 1); // Inner file preserved
638+
assert!(d.entries.get(OsStr::new("inner_file")).is_some());
639+
}
640+
_ => panic!("Expected directory after merge"),
641+
}
642+
643+
// Merge Leaf onto Directory (replaces)
644+
dir.merge(OsStr::new("merged_dir"), Inode::Leaf(new_leaf_file(100)));
645+
assert!(matches!(
646+
dir.entries.get(OsStr::new("merged_dir")),
647+
Some(Inode::Leaf(_))
648+
));
649+
assert_eq!(
650+
dir.entries
651+
.get(OsStr::new("merged_dir"))
652+
.unwrap()
653+
.stat()
654+
.st_mtim_sec,
655+
100
656+
);
657+
}
658+
659+
#[test]
660+
fn test_clear() {
661+
let mut dir = Directory::<Sha256HashValue>::default();
662+
dir.insert(OsStr::new("file1"), Inode::Leaf(new_leaf_file(10)));
663+
dir.stat.st_mtim_sec = 100;
664+
665+
dir.clear();
666+
assert!(dir.entries.is_empty());
667+
assert_eq!(dir.stat.st_mtim_sec, 100); // Stat should be unmodified
668+
}
669+
670+
#[test]
671+
fn test_newest_file() {
672+
let mut root = Directory::<Sha256HashValue>::new(stat_with_mtime(5));
673+
assert_eq!(root.newest_file(), 5);
674+
675+
root.insert(OsStr::new("file1"), Inode::Leaf(new_leaf_file(10)));
676+
assert_eq!(root.newest_file(), 10);
677+
678+
let subdir_stat = stat_with_mtime(15);
679+
let mut subdir = Box::new(Directory::new(subdir_stat));
680+
subdir.insert(OsStr::new("subfile1"), Inode::Leaf(new_leaf_file(12)));
681+
root.insert(OsStr::new("subdir"), Inode::Directory(subdir));
682+
assert_eq!(root.newest_file(), 15);
683+
684+
if let Some(Inode::Directory(sd)) = root.entries.get_mut(OsStr::new("subdir")) {
685+
sd.insert(OsStr::new("subfile2"), Inode::Leaf(new_leaf_file(20)));
686+
}
687+
assert_eq!(root.newest_file(), 20);
688+
689+
root.stat.st_mtim_sec = 25;
690+
assert_eq!(root.newest_file(), 25);
691+
}
692+
693+
#[test]
694+
fn test_iteration_entries_sorted_inodes() {
695+
let mut dir = Directory::<Sha256HashValue>::default();
696+
dir.insert(OsStr::new("b_file"), Inode::Leaf(new_leaf_file(10)));
697+
dir.insert(OsStr::new("a_dir"), new_dir_inode(20));
698+
dir.insert(
699+
OsStr::new("c_link"),
700+
Inode::Leaf(new_leaf_symlink("target", 30)),
701+
);
702+
703+
let names_from_entries: Vec<&OsStr> = dir.entries().map(|(name, _)| name).collect();
704+
assert_eq!(names_from_entries.len(), 3); // BTreeMap iter is sorted
705+
assert!(names_from_entries.contains(&OsStr::new("a_dir")));
706+
assert!(names_from_entries.contains(&OsStr::new("b_file")));
707+
assert!(names_from_entries.contains(&OsStr::new("c_link")));
708+
709+
let sorted_names: Vec<&OsStr> = dir.sorted_entries().map(|(name, _)| name).collect();
710+
assert_eq!(
711+
sorted_names,
712+
vec![
713+
OsStr::new("a_dir"),
714+
OsStr::new("b_file"),
715+
OsStr::new("c_link")
716+
]
717+
);
718+
719+
let mut inode_types = vec![];
720+
for inode in dir.inodes() {
721+
match inode {
722+
Inode::Directory(_) => inode_types.push("dir"),
723+
Inode::Leaf(_) => inode_types.push("leaf"),
724+
}
725+
}
726+
assert_eq!(inode_types.len(), 3);
727+
assert_eq!(inode_types.iter().filter(|&&t| t == "dir").count(), 1);
728+
assert_eq!(inode_types.iter().filter(|&&t| t == "leaf").count(), 2);
729+
}
730+
}

0 commit comments

Comments
 (0)