Skip to content

Commit 6693ae8

Browse files
wip: add more tests
1 parent 826b607 commit 6693ae8

File tree

1 file changed

+327
-9
lines changed
  • rs/cli/src/commands/registry/helpers

1 file changed

+327
-9
lines changed

rs/cli/src/commands/registry/helpers/dump.rs

Lines changed: 327 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::ctx::DreContext;
22
use ic_canisters::IcAgentCanisterClient;
33
use ic_canisters::governance::GovernanceCanisterWrapper;
4+
use ic_registry_common_proto::pb::local_store::v1::ChangelogEntry;
45
use ic_management_backend::health::HealthStatusQuerier;
56
use ic_management_backend::lazy_registry::LazyRegistry;
67
use ic_management_types::{HealthStatus, Network};
@@ -20,6 +21,7 @@ use prost::Message;
2021
use serde::Serialize;
2122
use std::{
2223
collections::{BTreeMap, HashMap},
24+
ffi::OsStr,
2325
net::Ipv6Addr,
2426
str::FromStr,
2527
sync::Arc,
@@ -259,7 +261,7 @@ async fn get_node_operators(local_registry: &Arc<dyn LazyRegistry>, network: &Ne
259261

260262
pub(crate) async fn get_sorted_versions_from_local(
261263
ctx: &DreContext,
262-
) -> anyhow::Result<(Vec<u64>, Vec<(u64, ic_registry_common_proto::pb::local_store::v1::ChangelogEntry)>)> {
264+
) -> anyhow::Result<(Vec<u64>, Vec<(u64, ChangelogEntry)>)> {
263265
let base_dirs = get_dirs_from_ctx(ctx)?;
264266

265267
let entries = load_first_available_entries(&base_dirs)?;
@@ -522,18 +524,15 @@ async fn _get_nodes(
522524

523525
pub(crate) fn load_first_available_entries(
524526
base_dirs: &[std::path::PathBuf],
525-
) -> anyhow::Result<Vec<(u64, ic_registry_common_proto::pb::local_store::v1::ChangelogEntry)>> {
526-
use ic_registry_common_proto::pb::local_store::v1::ChangelogEntry as PbChangelogEntry;
527-
use std::ffi::OsStr;
528-
529-
let mut entries: Vec<(u64, PbChangelogEntry)> = Vec::new();
527+
) -> anyhow::Result<Vec<(u64, ChangelogEntry)>> {
528+
let mut entries: Vec<(u64, ChangelogEntry)> = Vec::new();
530529
for base_dir in base_dirs.iter() {
531-
let mut local: Vec<(u64, PbChangelogEntry)> = Vec::new();
530+
let mut local: Vec<(u64, ChangelogEntry)> = Vec::new();
532531
collect_pb_files(base_dir, &mut |path| {
533532
if path.extension() == Some(OsStr::new("pb")) {
534533
if let Some(v) = extract_version_from_registry_path(base_dir, path) {
535534
let bytes = std::fs::read(path).unwrap_or_else(|_| panic!("Failed reading {}", path.display()));
536-
let entry = PbChangelogEntry::decode(bytes.as_slice()).unwrap_or_else(|_| panic!("Failed decoding {}", path.display()));
535+
let entry = ChangelogEntry::decode(bytes.as_slice()).unwrap_or_else(|_| panic!("Failed decoding {}", path.display()));
537536
local.push((v, entry));
538537
}
539538
}
@@ -589,8 +588,327 @@ fn extract_version_from_registry_path(base_dir: &std::path::Path, full_path: &st
589588

590589
#[cfg(test)]
591590
mod test {
592-
use super::extract_version_from_registry_path;
591+
use super::*;
593592
use std::path::PathBuf;
593+
use std::collections::HashSet;
594+
use std::time::{SystemTime, UNIX_EPOCH};
595+
use ic_registry_common_proto::pb::local_store::v1::{ChangelogEntry, KeyMutation, MutationType};
596+
use prost::Message;
597+
598+
#[test]
599+
fn test_load_first_available_entries() {
600+
struct TestCase {
601+
description: String,
602+
setup: Box<dyn Fn(&PathBuf) -> Vec<PathBuf>>,
603+
expected_result: Result<usize, bool>, // Ok(count) or Err(should_error)
604+
}
605+
606+
// Generate unique test directory name
607+
let test_id = SystemTime::now()
608+
.duration_since(UNIX_EPOCH)
609+
.unwrap()
610+
.as_nanos();
611+
let base_test_dir = PathBuf::from(format!("/tmp/dre_test_load_entries_{}", test_id));
612+
613+
let test_cases = vec![
614+
TestCase {
615+
description: "all directories empty".to_string(),
616+
setup: Box::new(|base| {
617+
let dir1 = base.join("dir1");
618+
let dir2 = base.join("dir2");
619+
fs_err::create_dir_all(&dir1).unwrap();
620+
fs_err::create_dir_all(&dir2).unwrap();
621+
vec![dir1, dir2]
622+
}),
623+
expected_result: Err(true), // Should error
624+
},
625+
TestCase {
626+
description: "multiple directories, first empty, second has entries".to_string(),
627+
setup: Box::new(|base| {
628+
let dir1 = base.join("dir1");
629+
let dir2 = base.join("dir2");
630+
fs_err::create_dir_all(&dir1).unwrap();
631+
fs_err::create_dir_all(&dir2).unwrap();
632+
let entry = ChangelogEntry {
633+
key_mutations: vec![KeyMutation {
634+
key: "test_key".to_string(),
635+
value: b"test_value".to_vec(),
636+
mutation_type: MutationType::Set as i32,
637+
}],
638+
};
639+
let hex_str = format!("{:019x}", 1);
640+
let dir_path = dir2
641+
.join(&hex_str[0..10])
642+
.join(&hex_str[10..12])
643+
.join(&hex_str[12..14]);
644+
fs_err::create_dir_all(&dir_path).unwrap();
645+
let file_path = dir_path.join(format!("{}.pb", &hex_str[14..]));
646+
fs_err::write(&file_path, entry.encode_to_vec()).unwrap();
647+
vec![dir1, dir2]
648+
}),
649+
expected_result: Ok(1),
650+
},
651+
TestCase {
652+
description: "multiple directories, first has entries, second ignored".to_string(),
653+
setup: Box::new(|base| {
654+
let dir1 = base.join("dir1");
655+
let dir2 = base.join("dir2");
656+
fs_err::create_dir_all(&dir1).unwrap();
657+
fs_err::create_dir_all(&dir2).unwrap();
658+
let entry1 = ChangelogEntry {
659+
key_mutations: vec![KeyMutation {
660+
key: "test_key1".to_string(),
661+
value: b"test_value1".to_vec(),
662+
mutation_type: MutationType::Set as i32,
663+
}],
664+
};
665+
let entry2 = ChangelogEntry {
666+
key_mutations: vec![KeyMutation {
667+
key: "test_key2".to_string(),
668+
value: b"test_value2".to_vec(),
669+
mutation_type: MutationType::Set as i32,
670+
}],
671+
};
672+
// Create entry in dir1
673+
let hex_str1 = format!("{:019x}", 1);
674+
let dir_path1 = dir1
675+
.join(&hex_str1[0..10])
676+
.join(&hex_str1[10..12])
677+
.join(&hex_str1[12..14]);
678+
fs_err::create_dir_all(&dir_path1).unwrap();
679+
let file_path1 = dir_path1.join(format!("{}.pb", &hex_str1[14..]));
680+
fs_err::write(&file_path1, entry1.encode_to_vec()).unwrap();
681+
// Create entry in dir2 (should be ignored)
682+
let hex_str2 = format!("{:019x}", 2);
683+
let dir_path2 = dir2
684+
.join(&hex_str2[0..10])
685+
.join(&hex_str2[10..12])
686+
.join(&hex_str2[12..14]);
687+
fs_err::create_dir_all(&dir_path2).unwrap();
688+
let file_path2 = dir_path2.join(format!("{}.pb", &hex_str2[14..]));
689+
fs_err::write(&file_path2, entry2.encode_to_vec()).unwrap();
690+
vec![dir1, dir2]
691+
}),
692+
expected_result: Ok(1), // Should only return entries from dir1
693+
},
694+
];
695+
696+
// Cleanup function
697+
let cleanup = |path: &PathBuf| {
698+
if path.exists() {
699+
let _ = fs_err::remove_dir_all(path);
700+
}
701+
};
702+
703+
for test_case in test_cases {
704+
let base_dirs = (test_case.setup)(&base_test_dir);
705+
706+
let result = load_first_available_entries(&base_dirs);
707+
708+
match test_case.expected_result {
709+
Ok(expected_count) => {
710+
assert!(
711+
result.is_ok(),
712+
"{}: load_first_available_entries should succeed",
713+
test_case.description
714+
);
715+
let entries = result.unwrap();
716+
assert_eq!(
717+
entries.len(),
718+
expected_count,
719+
"{}: should load {} entries, got {}",
720+
test_case.description,
721+
expected_count,
722+
entries.len()
723+
);
724+
}
725+
Err(should_error) => {
726+
if should_error {
727+
assert!(
728+
result.is_err(),
729+
"{}: load_first_available_entries should return error",
730+
test_case.description
731+
);
732+
} else {
733+
assert!(
734+
result.is_ok(),
735+
"{}: load_first_available_entries should succeed",
736+
test_case.description
737+
);
738+
}
739+
}
740+
}
741+
742+
// Cleanup after each test case
743+
for dir in &base_dirs {
744+
cleanup(dir);
745+
}
746+
}
747+
748+
// Final cleanup of base test directory
749+
cleanup(&base_test_dir);
750+
}
751+
752+
#[test]
753+
fn test_collect_pb_files() {
754+
use std::time::{SystemTime, UNIX_EPOCH};
755+
756+
struct TestCase {
757+
description: String,
758+
setup: Box<dyn Fn(&PathBuf) -> (PathBuf, HashSet<PathBuf>)>,
759+
expected_count: usize,
760+
}
761+
762+
// Generate unique test directory name
763+
let test_id = SystemTime::now()
764+
.duration_since(UNIX_EPOCH)
765+
.unwrap()
766+
.as_nanos();
767+
let base_test_dir = PathBuf::from(format!("/tmp/dre_test_collect_pb_{}", test_id));
768+
769+
let test_cases = vec![
770+
TestCase {
771+
description: "empty directory".to_string(),
772+
setup: Box::new(|base| {
773+
let test_dir = base.join("empty");
774+
fs_err::create_dir_all(&test_dir).unwrap();
775+
(test_dir, HashSet::new())
776+
}),
777+
expected_count: 0,
778+
},
779+
TestCase {
780+
description: "single file in root".to_string(),
781+
setup: Box::new(|base| {
782+
let test_dir = base.join("single_file");
783+
fs_err::create_dir_all(&test_dir).unwrap();
784+
let file1 = test_dir.join("file1.pb");
785+
fs_err::write(&file1, b"test").unwrap();
786+
let mut expected = HashSet::new();
787+
expected.insert(file1.clone());
788+
(test_dir, expected)
789+
}),
790+
expected_count: 1,
791+
},
792+
TestCase {
793+
description: "multiple files in root".to_string(),
794+
setup: Box::new(|base| {
795+
let test_dir = base.join("multiple_files");
796+
fs_err::create_dir_all(&test_dir).unwrap();
797+
let file1 = test_dir.join("file1.pb");
798+
let file2 = test_dir.join("file2.pb");
799+
let file3 = test_dir.join("file3.txt");
800+
fs_err::write(&file1, b"test1").unwrap();
801+
fs_err::write(&file2, b"test2").unwrap();
802+
fs_err::write(&file3, b"test3").unwrap();
803+
let mut expected = HashSet::new();
804+
expected.insert(file1.clone());
805+
expected.insert(file2.clone());
806+
expected.insert(file3.clone());
807+
(test_dir, expected)
808+
}),
809+
expected_count: 3,
810+
},
811+
TestCase {
812+
description: "nested directory structure".to_string(),
813+
setup: Box::new(|base| {
814+
let test_dir = base.join("nested");
815+
let subdir = test_dir.join("subdir");
816+
fs_err::create_dir_all(&subdir).unwrap();
817+
let file1 = test_dir.join("file1.pb");
818+
let file2 = subdir.join("file2.pb");
819+
let file3 = subdir.join("file3.pb");
820+
fs_err::write(&file1, b"test1").unwrap();
821+
fs_err::write(&file2, b"test2").unwrap();
822+
fs_err::write(&file3, b"test3").unwrap();
823+
let mut expected = HashSet::new();
824+
expected.insert(file1.clone());
825+
expected.insert(file2.clone());
826+
expected.insert(file3.clone());
827+
(test_dir, expected)
828+
}),
829+
expected_count: 3,
830+
},
831+
TestCase {
832+
description: "deeply nested directory structure".to_string(),
833+
setup: Box::new(|base| {
834+
let test_dir = base.join("deeply_nested");
835+
let deep_dir = test_dir.join("level1").join("level2").join("level3");
836+
fs_err::create_dir_all(&deep_dir).unwrap();
837+
let file1 = test_dir.join("file1.pb");
838+
let file2 = deep_dir.join("file2.pb");
839+
fs_err::write(&file1, b"test1").unwrap();
840+
fs_err::write(&file2, b"test2").unwrap();
841+
let mut expected = HashSet::new();
842+
expected.insert(file1.clone());
843+
expected.insert(file2.clone());
844+
(test_dir, expected)
845+
}),
846+
expected_count: 2,
847+
},
848+
TestCase {
849+
description: "non-existent directory".to_string(),
850+
setup: Box::new(|base| {
851+
let test_dir = base.join("non_existent");
852+
(test_dir, HashSet::new())
853+
}),
854+
expected_count: 0,
855+
},
856+
TestCase {
857+
description: "directory with only subdirectories (no files)".to_string(),
858+
setup: Box::new(|base| {
859+
let test_dir = base.join("only_subdirs");
860+
let subdir1 = test_dir.join("subdir1");
861+
let subdir2 = test_dir.join("subdir2");
862+
fs_err::create_dir_all(&subdir1).unwrap();
863+
fs_err::create_dir_all(&subdir2).unwrap();
864+
(test_dir, HashSet::new())
865+
}),
866+
expected_count: 0,
867+
},
868+
];
869+
870+
// Cleanup function
871+
let cleanup = |path: &PathBuf| {
872+
if path.exists() {
873+
let _ = fs_err::remove_dir_all(path);
874+
}
875+
};
876+
877+
for test_case in test_cases {
878+
let (test_dir, expected_files) = (test_case.setup)(&base_test_dir);
879+
let base_path = test_dir.clone();
880+
881+
let mut collected_files = HashSet::new();
882+
let result = collect_pb_files(&base_path, &mut |path| {
883+
collected_files.insert(path.to_path_buf());
884+
});
885+
886+
assert!(result.is_ok(), "{}: collect_pb_files should succeed", test_case.description);
887+
assert_eq!(
888+
collected_files.len(),
889+
test_case.expected_count,
890+
"{}: should collect {} files, got {}",
891+
test_case.description,
892+
test_case.expected_count,
893+
collected_files.len()
894+
);
895+
896+
for expected_file in &expected_files {
897+
assert!(
898+
collected_files.contains(expected_file),
899+
"{}: should collect {:?}",
900+
test_case.description,
901+
expected_file
902+
);
903+
}
904+
905+
// Cleanup after each test case
906+
cleanup(&test_dir);
907+
}
908+
909+
// Final cleanup of base test directory
910+
cleanup(&base_test_dir);
911+
}
594912

595913
#[test]
596914
fn test_extract_version_from_registry_path() {

0 commit comments

Comments
 (0)