From b07b27d13fe7d5675555691e7a77eed80fd230ab Mon Sep 17 00:00:00 2001 From: vip892766gma <275402320+vip892766gma@users.noreply.github.com> Date: Thu, 4 Jun 2026 21:28:34 +0200 Subject: [PATCH] chore: improve findutils maintenance path (#687) --- src/find/matchers/empty.rs | 71 ++++++++++++++++++++++++++++++++++++ src/find/matchers/size.rs | 74 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 145 insertions(+) diff --git a/src/find/matchers/empty.rs b/src/find/matchers/empty.rs index 6ce3d13..9770dff 100644 --- a/src/find/matchers/empty.rs +++ b/src/find/matchers/empty.rs @@ -62,6 +62,8 @@ mod tests { use super::*; use crate::find::matchers::tests::get_dir_entry_for; use crate::find::tests::FakeDependencies; + #[cfg(unix)] + use std::os::unix::fs::PermissionsExt; #[test] fn empty_files() { @@ -96,4 +98,73 @@ mod tests { let file_info = get_dir_entry_for(&temp_dir_path, subdir_name); assert!(!matcher.matches(&file_info, &mut deps.new_matcher_io())); } + + #[test] + fn empty_file_vs_empty_directory() { + let empty_file_info = get_dir_entry_for("test_data/simple", "abbbc"); + + let temp_dir = Builder::new() + .prefix("empty_file_vs_empty_directory") + .tempdir() + .unwrap(); + let temp_dir_path = temp_dir.path().to_string_lossy(); + let subdir_name = "empty_subdir"; + std::fs::create_dir(temp_dir.path().join(subdir_name)).unwrap(); + + let matcher = EmptyMatcher::new(); + let deps = FakeDependencies::new(); + + // Both an empty file and an empty directory should match. + assert!( + matcher.matches(&empty_file_info, &mut deps.new_matcher_io()), + "empty file should match" + ); + let empty_dir_info = get_dir_entry_for(&temp_dir_path, subdir_name); + assert!( + matcher.matches(&empty_dir_info, &mut deps.new_matcher_io()), + "empty directory should match" + ); + } + + #[test] + #[cfg(unix)] + fn non_readable_directory() { + let temp_dir = Builder::new() + .prefix("non_readable_directory") + .tempdir() + .unwrap(); + let temp_dir_path = temp_dir.path().to_string_lossy(); + let subdir_name = "unreadable"; + std::fs::create_dir(temp_dir.path().join(subdir_name)).unwrap(); + + let mut perms = std::fs::metadata(temp_dir.path().join(subdir_name)) + .unwrap() + .permissions(); + perms.set_mode(0o000); + std::fs::set_permissions(temp_dir.path().join(subdir_name), perms).unwrap(); + + // If we can still read the directory, we're likely running as root. + if std::fs::read_dir(temp_dir.path().join(subdir_name)).is_ok() { + // Restore permissions before skipping. + let mut perms = std::fs::metadata(temp_dir.path().join(subdir_name)) + .unwrap() + .permissions(); + perms.set_mode(0o755); + std::fs::set_permissions(temp_dir.path().join(subdir_name), perms).unwrap(); + return; + } + + let matcher = EmptyMatcher::new(); + let deps = FakeDependencies::new(); + + let file_info = get_dir_entry_for(&temp_dir_path, subdir_name); + assert!(!matcher.matches(&file_info, &mut deps.new_matcher_io())); + + // Restore permissions so tempdir can be cleaned up. + let mut perms = std::fs::metadata(temp_dir.path().join(subdir_name)) + .unwrap() + .permissions(); + perms.set_mode(0o755); + std::fs::set_permissions(temp_dir.path().join(subdir_name), perms).unwrap(); + } } diff --git a/src/find/matchers/size.rs b/src/find/matchers/size.rs index 5d2d061..af2909f 100644 --- a/src/find/matchers/size.rs +++ b/src/find/matchers/size.rs @@ -170,4 +170,78 @@ mod tests { "512-byte file should match size of 1 block" ); } + + #[test] + fn size_matcher_zero_byte_file() { + let file_info = get_dir_entry_for("./test_data/simple", "abbbc"); + let deps = FakeDependencies::new(); + + let equal_to_0 = SizeMatcher::new(ComparableValue::EqualTo(0), "c").unwrap(); + assert!( + equal_to_0.matches(&file_info, &mut deps.new_matcher_io()), + "zero-byte file should match size of 0 bytes" + ); + + let more_than_0 = SizeMatcher::new(ComparableValue::MoreThan(0), "c").unwrap(); + assert!( + !more_than_0.matches(&file_info, &mut deps.new_matcher_io()), + "zero-byte file should not match size of >0 bytes" + ); + + let less_than_1 = SizeMatcher::new(ComparableValue::LessThan(1), "c").unwrap(); + assert!( + less_than_1.matches(&file_info, &mut deps.new_matcher_io()), + "zero-byte file should match size of <1 byte" + ); + } + + #[test] + fn size_matcher_boundary_values() { + let file_info = get_dir_entry_for("./test_data/size", "512bytes"); + let deps = FakeDependencies::new(); + + // bytes (c): 512 bytes = 512 + let eq_512_c = SizeMatcher::new(ComparableValue::EqualTo(512), "c").unwrap(); + assert!(eq_512_c.matches(&file_info, &mut deps.new_matcher_io())); + let gt_511_c = SizeMatcher::new(ComparableValue::MoreThan(511), "c").unwrap(); + assert!(gt_511_c.matches(&file_info, &mut deps.new_matcher_io())); + let lt_513_c = SizeMatcher::new(ComparableValue::LessThan(513), "c").unwrap(); + assert!(lt_513_c.matches(&file_info, &mut deps.new_matcher_io())); + + // words (w): 512 bytes = 256 two-byte words + let eq_256_w = SizeMatcher::new(ComparableValue::EqualTo(256), "w").unwrap(); + assert!(eq_256_w.matches(&file_info, &mut deps.new_matcher_io())); + let gt_255_w = SizeMatcher::new(ComparableValue::MoreThan(255), "w").unwrap(); + assert!(gt_255_w.matches(&file_info, &mut deps.new_matcher_io())); + + // blocks (b): 512 bytes = 1 block + let eq_1_b = SizeMatcher::new(ComparableValue::EqualTo(1), "b").unwrap(); + assert!(eq_1_b.matches(&file_info, &mut deps.new_matcher_io())); + let gt_1_b = SizeMatcher::new(ComparableValue::MoreThan(1), "b").unwrap(); + assert!(!gt_1_b.matches(&file_info, &mut deps.new_matcher_io())); + let lt_1_b = SizeMatcher::new(ComparableValue::LessThan(1), "b").unwrap(); + assert!(!lt_1_b.matches(&file_info, &mut deps.new_matcher_io())); + let gt_0_b = SizeMatcher::new(ComparableValue::MoreThan(0), "b").unwrap(); + assert!(gt_0_b.matches(&file_info, &mut deps.new_matcher_io())); + + // kibibytes (k): 512 bytes = 1k (rounded up) + let eq_1_k = SizeMatcher::new(ComparableValue::EqualTo(1), "k").unwrap(); + assert!(eq_1_k.matches(&file_info, &mut deps.new_matcher_io())); + let gt_1_k = SizeMatcher::new(ComparableValue::MoreThan(1), "k").unwrap(); + assert!(!gt_1_k.matches(&file_info, &mut deps.new_matcher_io())); + let lt_2_k = SizeMatcher::new(ComparableValue::LessThan(2), "k").unwrap(); + assert!(lt_2_k.matches(&file_info, &mut deps.new_matcher_io())); + + // mebibytes (M): 512 bytes = 1M (rounded up) + let eq_1_m = SizeMatcher::new(ComparableValue::EqualTo(1), "M").unwrap(); + assert!(eq_1_m.matches(&file_info, &mut deps.new_matcher_io())); + let gt_1_m = SizeMatcher::new(ComparableValue::MoreThan(1), "M").unwrap(); + assert!(!gt_1_m.matches(&file_info, &mut deps.new_matcher_io())); + + // gibibytes (G): 512 bytes = 1G (rounded up) + let eq_1_g = SizeMatcher::new(ComparableValue::EqualTo(1), "G").unwrap(); + assert!(eq_1_g.matches(&file_info, &mut deps.new_matcher_io())); + let gt_1_g = SizeMatcher::new(ComparableValue::MoreThan(1), "G").unwrap(); + assert!(!gt_1_g.matches(&file_info, &mut deps.new_matcher_io())); + } }