1use noxu_dbi::DatabaseImpl;
6use noxu_tree::NodeRwLock as RwLock;
7use noxu_tree::Tree;
8use noxu_tree::tree::{BinStub, InNodeStub, TreeNode};
9use noxu_util::{Lsn, NULL_LSN};
10use std::collections::HashSet;
11use std::fmt;
12use std::sync::Arc;
13
14#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct VerifyResult {
17 pub errors: Vec<VerifyError>,
19 pub warnings: Vec<String>,
21 pub databases_verified: u32,
23 pub records_verified: u64,
25 pub passed: bool,
27}
28
29impl VerifyResult {
30 pub fn new() -> Self {
32 Self {
33 errors: Vec::new(),
34 warnings: Vec::new(),
35 databases_verified: 0,
36 records_verified: 0,
37 passed: true,
38 }
39 }
40
41 pub fn with_errors(errors: Vec<VerifyError>) -> Self {
43 Self {
44 passed: errors.is_empty(),
45 errors,
46 warnings: Vec::new(),
47 databases_verified: 0,
48 records_verified: 0,
49 }
50 }
51
52 pub fn add_error(&mut self, error: VerifyError) {
54 self.errors.push(error);
55 self.passed = false;
56 }
57
58 pub fn add_warning(&mut self, warning: String) {
60 self.warnings.push(warning);
61 }
62
63 pub fn is_passed(&self) -> bool {
65 self.passed
66 }
67
68 pub fn error_count(&self) -> usize {
70 self.errors.len()
71 }
72
73 pub fn warning_count(&self) -> usize {
75 self.warnings.len()
76 }
77}
78
79impl Default for VerifyResult {
80 fn default() -> Self {
81 Self::new()
82 }
83}
84
85impl fmt::Display for VerifyResult {
86 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87 writeln!(f, "Verification Result")?;
88 writeln!(f, "===================")?;
89 writeln!(
90 f,
91 "Status: {}",
92 if self.passed { "PASSED" } else { "FAILED" }
93 )?;
94 writeln!(f, "Databases verified: {}", self.databases_verified)?;
95 writeln!(f, "Records verified: {}", self.records_verified)?;
96 writeln!(f)?;
97
98 if !self.errors.is_empty() {
99 writeln!(f, "Errors ({}):", self.errors.len())?;
100 for error in &self.errors {
101 writeln!(f, " - {}", error)?;
102 }
103 writeln!(f)?;
104 }
105
106 if !self.warnings.is_empty() {
107 writeln!(f, "Warnings ({}):", self.warnings.len())?;
108 for warning in &self.warnings {
109 writeln!(f, " - {}", warning)?;
110 }
111 writeln!(f)?;
112 }
113
114 if self.errors.is_empty() && self.warnings.is_empty() {
115 writeln!(f, "No errors or warnings found.")?;
116 }
117
118 Ok(())
119 }
120}
121
122#[derive(Debug, Clone, PartialEq, Eq)]
124pub enum VerifyError {
125 BtreeError { db_name: String, description: String },
127 LogError { file_number: u32, description: String },
129 DataInconsistency { description: String },
131 ChecksumError { location: String, description: String },
133 InvalidNodeReference { node_id: u64, description: String },
135 MetadataError { db_name: String, description: String },
137}
138
139impl fmt::Display for VerifyError {
140 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
141 match self {
142 VerifyError::BtreeError { db_name, description } => {
143 write!(f, "B-tree error in '{}': {}", db_name, description)
144 }
145 VerifyError::LogError { file_number, description } => {
146 write!(
147 f,
148 "Log file {:08x}.ndb error: {}",
149 file_number, description
150 )
151 }
152 VerifyError::DataInconsistency { description } => {
153 write!(f, "Data inconsistency: {}", description)
154 }
155 VerifyError::ChecksumError { location, description } => {
156 write!(f, "Checksum error at {}: {}", location, description)
157 }
158 VerifyError::InvalidNodeReference { node_id, description } => {
159 write!(
160 f,
161 "Invalid node reference (ID {}): {}",
162 node_id, description
163 )
164 }
165 VerifyError::MetadataError { db_name, description } => {
166 write!(f, "Metadata error in '{}': {}", db_name, description)
167 }
168 }
169 }
170}
171
172#[derive(Debug, Clone, PartialEq, Eq)]
174pub struct VerifyConfig {
175 pub verify_btree: bool,
177 pub verify_log: bool,
179 pub verify_data_checksums: bool,
181 pub repair: bool,
183 pub max_errors: u32,
185 pub verbose: bool,
187 pub database_name: Option<String>,
189}
190
191impl VerifyConfig {
192 pub fn new() -> Self {
194 Self::default()
195 }
196
197 pub fn with_btree_verification(mut self, enabled: bool) -> Self {
199 self.verify_btree = enabled;
200 self
201 }
202
203 pub fn with_log_verification(mut self, enabled: bool) -> Self {
205 self.verify_log = enabled;
206 self
207 }
208
209 pub fn with_checksum_verification(mut self, enabled: bool) -> Self {
211 self.verify_data_checksums = enabled;
212 self
213 }
214
215 pub fn with_repair(mut self, enabled: bool) -> Self {
217 self.repair = enabled;
218 self
219 }
220
221 pub fn with_max_errors(mut self, max: u32) -> Self {
223 self.max_errors = max;
224 self
225 }
226
227 pub fn with_verbose(mut self, enabled: bool) -> Self {
229 self.verbose = enabled;
230 self
231 }
232
233 pub fn for_database(mut self, name: String) -> Self {
235 self.database_name = Some(name);
236 self
237 }
238}
239
240impl Default for VerifyConfig {
241 fn default() -> Self {
242 VerifyConfig {
243 verify_btree: true,
244 verify_log: true,
245 verify_data_checksums: true,
246 repair: false,
247 max_errors: 100,
248 verbose: false,
249 database_name: None,
250 }
251 }
252}
253
254pub fn verify_tree(
272 tree: &Tree,
273 db_name: &str,
274 config: &VerifyConfig,
275) -> VerifyResult {
276 let mut result = VerifyResult::new();
277
278 if !config.verify_btree {
279 return result;
280 }
281
282 let root = match tree.get_root() {
283 Some(r) => r,
284 None => {
285 result.databases_verified = 1;
287 return result;
288 }
289 };
290
291 let mut records: u64 = 0;
292 verify_node(&root, None, db_name, config, &mut result, &mut records);
293 result.records_verified = records;
294 result.databases_verified = 1;
295 result
296}
297
298fn verify_node(
300 node_arc: &Arc<RwLock<TreeNode>>,
301 parent_key: Option<&[u8]>,
302 db_name: &str,
303 config: &VerifyConfig,
304 result: &mut VerifyResult,
305 records: &mut u64,
306) {
307 let guard = node_arc.read();
308
309 match &*guard {
310 TreeNode::Internal(in_node) => {
311 verify_internal_node(
312 in_node, parent_key, db_name, config, result, records,
313 );
314 }
315 TreeNode::Bottom(bin_stub) => {
316 verify_bin_stub(
317 bin_stub, parent_key, db_name, config, result, records,
318 );
319 }
320 }
321}
322
323fn verify_internal_node(
328 in_node: &InNodeStub,
329 _parent_key: Option<&[u8]>,
330 db_name: &str,
331 config: &VerifyConfig,
332 result: &mut VerifyResult,
333 records: &mut u64,
334) {
335 if in_node.entries.is_empty() {
336 return;
339 }
340
341 for (i, entry) in in_node.entries.iter().enumerate() {
343 let child_owned = in_node.get_child(i);
344 let child_arc = match &child_owned {
345 Some(c) => c,
346 None => {
347 result.add_error(VerifyError::BtreeError {
348 db_name: db_name.to_string(),
349 description: format!(
350 "IN node (id={}) entry {} has null child reference",
351 in_node.node_id, i
352 ),
353 });
354 if result.error_count() >= config.max_errors as usize {
355 return;
356 }
357 continue;
358 }
359 };
360
361 let expected_parent_key: Option<&[u8]> =
365 if i == 0 { None } else { Some(entry.key.as_slice()) };
366
367 verify_node(
368 child_arc,
369 expected_parent_key,
370 db_name,
371 config,
372 result,
373 records,
374 );
375
376 if result.error_count() >= config.max_errors as usize {
377 return;
378 }
379 }
380}
381
382fn verify_bin_stub(
388 bin: &BinStub,
389 parent_key: Option<&[u8]>,
390 db_name: &str,
391 config: &VerifyConfig,
392 result: &mut VerifyResult,
393 records: &mut u64,
394) {
395 if let Some(pk) = parent_key
397 && !bin.entries.is_empty()
398 {
399 let first_full = bin.get_full_key(0);
400 if let Some(ref first_key) = first_full
401 && first_key.as_slice() < pk
402 {
403 result.add_error(VerifyError::BtreeError {
404 db_name: db_name.to_string(),
405 description: format!(
406 "BIN (id={}) first key {:?} is less than parent routing key {:?}",
407 bin.node_id, first_key, pk
408 ),
409 });
410 }
411 }
412
413 for (i, entry) in bin.entries.iter().enumerate() {
415 if !entry.known_deleted && entry.lsn == NULL_LSN {
417 result.add_error(VerifyError::BtreeError {
418 db_name: db_name.to_string(),
419 description: format!(
420 "BIN (id={}) slot {} has NULL LSN but is not known-deleted",
421 bin.node_id, i
422 ),
423 });
424 if result.error_count() >= config.max_errors as usize {
425 return;
426 }
427 }
428
429 if !entry.known_deleted {
430 *records += 1;
431 }
432 }
433}
434
435pub fn verify_database_impl(
466 db_impl: &DatabaseImpl,
467 config: &VerifyConfig,
468) -> VerifyResult {
469 let db_name = db_impl.get_name();
470 match db_impl.get_real_tree() {
471 Some(tree) => verify_tree(&tree, db_name, config),
472 None => {
473 VerifyResult {
475 errors: Vec::new(),
476 warnings: Vec::new(),
477 databases_verified: 1,
478 records_verified: 0,
479 passed: true,
480 }
481 }
482 }
483}
484
485pub fn gather_tree_lsns(tree: &Tree) -> HashSet<Lsn> {
496 let mut lsns = HashSet::new();
497 if let Some(root) = tree.get_root() {
498 gather_node_lsns(&root, &mut lsns);
499 }
500 lsns
501}
502
503fn gather_node_lsns(node_arc: &Arc<RwLock<TreeNode>>, lsns: &mut HashSet<Lsn>) {
504 let guard = node_arc.read();
505 match &*guard {
506 TreeNode::Internal(in_node) => {
507 for child in in_node.resident_children() {
508 gather_node_lsns(&child, lsns);
509 }
510 }
511 TreeNode::Bottom(bin) => {
512 for entry in &bin.entries {
513 if !entry.known_deleted && entry.lsn != NULL_LSN {
515 lsns.insert(entry.lsn);
516 }
517 }
518 }
519 }
520}
521
522pub fn check_lsns_against_tracker(
532 db_impl: &DatabaseImpl,
533 tracker: &noxu_cleaner::UtilizationTracker,
534 result: &mut VerifyResult,
535) {
536 let tree = match db_impl.get_real_tree() {
537 Some(t) => t,
538 None => return,
539 };
540 let live = gather_tree_lsns(&tree);
541 let check = noxu_cleaner::check_lsns(live, tracker);
542 for lsn in check.obsolete_contains_live {
543 result.add_error(VerifyError::DataInconsistency {
544 description: format!(
545 "Obsolete LSN set contains valid LSN {} in database '{}' \
546 (VerifyUtils.checkLsns: live tree LSN recorded obsolete)",
547 lsn,
548 db_impl.get_name()
549 ),
550 });
551 }
552}
553
554#[cfg(test)]
555mod tests {
556 use super::*;
557
558 #[test]
559 fn test_verify_result_new() {
560 let result = VerifyResult::new();
561 assert!(result.passed);
562 assert_eq!(result.errors.len(), 0);
563 assert_eq!(result.warnings.len(), 0);
564 assert_eq!(result.databases_verified, 0);
565 assert_eq!(result.records_verified, 0);
566 }
567
568 #[test]
569 fn test_verify_result_default() {
570 let result = VerifyResult::default();
571 assert!(result.passed);
572 assert!(result.errors.is_empty());
573 }
574
575 #[test]
576 fn test_verify_result_with_errors() {
577 let errors = vec![VerifyError::BtreeError {
578 db_name: "test".to_string(),
579 description: "Invalid node".to_string(),
580 }];
581 let result = VerifyResult::with_errors(errors);
582 assert!(!result.passed);
583 assert_eq!(result.errors.len(), 1);
584 }
585
586 #[test]
587 fn test_verify_result_with_no_errors() {
588 let errors = vec![];
589 let result = VerifyResult::with_errors(errors);
590 assert!(result.passed);
591 assert_eq!(result.errors.len(), 0);
592 }
593
594 #[test]
595 fn test_add_error() {
596 let mut result = VerifyResult::new();
597 assert!(result.passed);
598
599 result.add_error(VerifyError::DataInconsistency {
600 description: "Test error".to_string(),
601 });
602
603 assert!(!result.passed);
604 assert_eq!(result.errors.len(), 1);
605 }
606
607 #[test]
608 fn test_add_warning() {
609 let mut result = VerifyResult::new();
610 result.add_warning("Test warning".to_string());
611
612 assert!(result.passed); assert_eq!(result.warnings.len(), 1);
614 }
615
616 #[test]
617 fn test_error_count() {
618 let mut result = VerifyResult::new();
619 assert_eq!(result.error_count(), 0);
620
621 result.add_error(VerifyError::DataInconsistency {
622 description: "Error 1".to_string(),
623 });
624 result.add_error(VerifyError::DataInconsistency {
625 description: "Error 2".to_string(),
626 });
627
628 assert_eq!(result.error_count(), 2);
629 }
630
631 #[test]
632 fn test_warning_count() {
633 let mut result = VerifyResult::new();
634 assert_eq!(result.warning_count(), 0);
635
636 result.add_warning("Warning 1".to_string());
637 result.add_warning("Warning 2".to_string());
638
639 assert_eq!(result.warning_count(), 2);
640 }
641
642 #[test]
643 fn test_is_passed() {
644 let result = VerifyResult::new();
645 assert!(result.is_passed());
646
647 let mut failed_result = VerifyResult::new();
648 failed_result.add_error(VerifyError::DataInconsistency {
649 description: "Error".to_string(),
650 });
651 assert!(!failed_result.is_passed());
652 }
653
654 #[test]
655 fn test_verify_error_btree() {
656 let error = VerifyError::BtreeError {
657 db_name: "mydb".to_string(),
658 description: "Invalid child reference".to_string(),
659 };
660 let s = format!("{}", error);
661 assert!(s.contains("B-tree error"));
662 assert!(s.contains("mydb"));
663 assert!(s.contains("Invalid child reference"));
664 }
665
666 #[test]
667 fn test_verify_error_log() {
668 let error = VerifyError::LogError {
669 file_number: 42,
670 description: "Corrupted entry".to_string(),
671 };
672 let s = format!("{}", error);
673 assert!(s.contains("Log file"));
674 assert!(s.contains("0000002a.ndb"));
675 assert!(s.contains("Corrupted entry"));
676 }
677
678 #[test]
679 fn test_verify_error_data_inconsistency() {
680 let error = VerifyError::DataInconsistency {
681 description: "Mismatched LSN".to_string(),
682 };
683 let s = format!("{}", error);
684 assert!(s.contains("Data inconsistency"));
685 assert!(s.contains("Mismatched LSN"));
686 }
687
688 #[test]
689 fn test_verify_error_checksum() {
690 let error = VerifyError::ChecksumError {
691 location: "file 10, offset 1024".to_string(),
692 description: "CRC mismatch".to_string(),
693 };
694 let s = format!("{}", error);
695 assert!(s.contains("Checksum error"));
696 assert!(s.contains("file 10, offset 1024"));
697 assert!(s.contains("CRC mismatch"));
698 }
699
700 #[test]
701 fn test_verify_error_invalid_node_reference() {
702 let error = VerifyError::InvalidNodeReference {
703 node_id: 12345,
704 description: "Node not found".to_string(),
705 };
706 let s = format!("{}", error);
707 assert!(s.contains("Invalid node reference"));
708 assert!(s.contains("12345"));
709 assert!(s.contains("Node not found"));
710 }
711
712 #[test]
713 fn test_verify_error_metadata() {
714 let error = VerifyError::MetadataError {
715 db_name: "testdb".to_string(),
716 description: "Invalid format version".to_string(),
717 };
718 let s = format!("{}", error);
719 assert!(s.contains("Metadata error"));
720 assert!(s.contains("testdb"));
721 assert!(s.contains("Invalid format version"));
722 }
723
724 #[test]
725 fn test_verify_config_default() {
726 let config = VerifyConfig::default();
727 assert!(config.verify_btree);
728 assert!(config.verify_log);
729 assert!(config.verify_data_checksums);
730 assert!(!config.repair);
731 assert_eq!(config.max_errors, 100);
732 assert!(!config.verbose);
733 assert!(config.database_name.is_none());
734 }
735
736 #[test]
737 fn test_verify_config_new() {
738 let config = VerifyConfig::new();
739 assert_eq!(config, VerifyConfig::default());
740 }
741
742 #[test]
743 fn test_verify_config_builder() {
744 let config = VerifyConfig::new()
745 .with_btree_verification(false)
746 .with_log_verification(true)
747 .with_checksum_verification(false)
748 .with_repair(true)
749 .with_max_errors(50)
750 .with_verbose(true)
751 .for_database("mydb".to_string());
752
753 assert!(!config.verify_btree);
754 assert!(config.verify_log);
755 assert!(!config.verify_data_checksums);
756 assert!(config.repair);
757 assert_eq!(config.max_errors, 50);
758 assert!(config.verbose);
759 assert_eq!(config.database_name, Some("mydb".to_string()));
760 }
761
762 #[test]
763 fn test_verify_result_display_passed() {
764 let result = VerifyResult {
765 errors: Vec::new(),
766 warnings: Vec::new(),
767 databases_verified: 5,
768 records_verified: 1000,
769 passed: true,
770 };
771
772 let output = format!("{}", result);
773 assert!(output.contains("PASSED"));
774 assert!(output.contains("Databases verified: 5"));
775 assert!(output.contains("Records verified: 1000"));
776 assert!(output.contains("No errors or warnings"));
777 }
778
779 #[test]
780 fn test_verify_result_display_with_errors() {
781 let mut result = VerifyResult::new();
782 result.add_error(VerifyError::BtreeError {
783 db_name: "test".to_string(),
784 description: "Bad node".to_string(),
785 });
786 result.databases_verified = 2;
787 result.records_verified = 500;
788
789 let output = format!("{}", result);
790 assert!(output.contains("FAILED"));
791 assert!(output.contains("Errors (1)"));
792 assert!(output.contains("B-tree error"));
793 }
794
795 #[test]
796 fn test_verify_result_display_with_warnings() {
797 let mut result = VerifyResult::new();
798 result.add_warning("Low cache utilization".to_string());
799 result.databases_verified = 3;
800
801 let output = format!("{}", result);
802 assert!(output.contains("PASSED"));
803 assert!(output.contains("Warnings (1)"));
804 assert!(output.contains("Low cache utilization"));
805 }
806
807 #[test]
808 fn test_verify_result_clone() {
809 let mut result = VerifyResult::new();
810 result.add_error(VerifyError::DataInconsistency {
811 description: "Test".to_string(),
812 });
813
814 let cloned = result.clone();
815 assert_eq!(cloned.errors.len(), result.errors.len());
816 assert_eq!(cloned.passed, result.passed);
817 }
818
819 #[test]
820 fn test_verify_error_equality() {
821 let error1 = VerifyError::BtreeError {
822 db_name: "db1".to_string(),
823 description: "error".to_string(),
824 };
825 let error2 = VerifyError::BtreeError {
826 db_name: "db1".to_string(),
827 description: "error".to_string(),
828 };
829 let error3 = VerifyError::BtreeError {
830 db_name: "db2".to_string(),
831 description: "error".to_string(),
832 };
833
834 assert_eq!(error1, error2);
835 assert_ne!(error1, error3);
836 }
837
838 #[test]
839 fn test_verify_config_equality() {
840 let config1 = VerifyConfig::default();
841 let config2 = VerifyConfig::default();
842 let config3 = VerifyConfig::new().with_repair(true);
843
844 assert_eq!(config1, config2);
845 assert_ne!(config1, config3);
846 }
847
848 #[test]
852 fn test_verify_tree_empty() {
853 use noxu_dbi::{DatabaseConfig, DatabaseId, DatabaseImpl, DbType};
854 use noxu_sync::RwLock;
855 use std::sync::Arc;
856
857 let db_id = DatabaseId::new(1);
858 let config = DatabaseConfig::default();
859 let db_impl = DatabaseImpl::new(
860 db_id,
861 "verify_test".to_string(),
862 DbType::User,
863 &config,
864 );
865 let db = Arc::new(RwLock::new(db_impl));
866 let guard = db.read();
867 let cfg = VerifyConfig::default();
868
869 if let Some(t) = guard.get_real_tree() {
870 let result = verify_tree(&t, "verify_test", &cfg);
871 assert!(
872 result.passed,
873 "empty tree should pass: {:?}",
874 result.errors
875 );
876 assert_eq!(result.databases_verified, 1);
877 }
878 }
880
881 #[test]
886 fn test_verify_tree_populated() {
887 use noxu_dbi::{
888 CursorImpl, DatabaseConfig, DatabaseId, DatabaseImpl, DbType,
889 PutMode,
890 };
891 use noxu_log::{FileManager, LogManager};
892 use noxu_sync::RwLock;
893 use std::sync::Arc;
894 use tempfile::TempDir;
895
896 let dir = TempDir::new().unwrap();
897 let fm = Arc::new(
898 FileManager::new(dir.path(), false, 64 * 1024 * 1024, 100).unwrap(),
899 );
900 let lm =
901 Arc::new(LogManager::new(Arc::clone(&fm), 3, 1024 * 1024, 65536));
902
903 let db_id = DatabaseId::new(2);
904 let config = DatabaseConfig::default();
905 let db_impl = DatabaseImpl::new(
906 db_id,
907 "pop_test".to_string(),
908 DbType::User,
909 &config,
910 );
911 let db = Arc::new(RwLock::new(db_impl));
912
913 {
914 let mut cursor = CursorImpl::with_log_manager(
915 Arc::clone(&db),
916 1,
917 Arc::clone(&lm),
918 );
919 cursor.put(b"alpha", b"1", PutMode::Overwrite).unwrap();
920 cursor.put(b"beta", b"2", PutMode::Overwrite).unwrap();
921 cursor.put(b"gamma", b"3", PutMode::Overwrite).unwrap();
922 }
923
924 let guard = db.read();
925 let cfg = VerifyConfig::default();
926
927 if let Some(t) = guard.get_real_tree() {
928 let result = verify_tree(&t, "pop_test", &cfg);
929 assert!(
930 result.passed,
931 "populated tree should pass: {:?}",
932 result.errors
933 );
934 assert_eq!(result.databases_verified, 1);
935 }
936 }
937
938 #[test]
946 fn test_verify_tree_detects_null_lsn() {
947 use noxu_dbi::{
948 CursorImpl, DatabaseConfig, DatabaseId, DatabaseImpl, DbType,
949 PutMode,
950 };
951 use noxu_log::{FileManager, LogManager};
952 use noxu_sync::RwLock;
953 use noxu_tree::tree::TreeNode;
954 use std::sync::Arc;
955 use tempfile::TempDir;
956
957 let dir = TempDir::new().unwrap();
958 let fm = Arc::new(
959 FileManager::new(dir.path(), false, 64 * 1024 * 1024, 100).unwrap(),
960 );
961 let lm =
962 Arc::new(LogManager::new(Arc::clone(&fm), 3, 1024 * 1024, 65536));
963
964 let db_id = DatabaseId::new(3);
965 let config = DatabaseConfig::default();
966 let db_impl = DatabaseImpl::new(
967 db_id,
968 "corrupt_test".to_string(),
969 DbType::User,
970 &config,
971 );
972 let db = Arc::new(RwLock::new(db_impl));
973
974 {
975 let mut cursor = CursorImpl::with_log_manager(
976 Arc::clone(&db),
977 1,
978 Arc::clone(&lm),
979 );
980 cursor.put(b"alpha", b"1", PutMode::Overwrite).unwrap();
981 cursor.put(b"beta", b"2", PutMode::Overwrite).unwrap();
982 cursor.put(b"gamma", b"3", PutMode::Overwrite).unwrap();
983 }
984
985 let guard = db.read();
986 let t = guard
987 .get_real_tree()
988 .expect("invariant: populated db has a real tree");
989
990 let corrupted = corrupt_first_bin_slot(&t, NULL_LSN);
992 assert!(corrupted, "test setup: expected at least one BIN slot");
993
994 let cfg = VerifyConfig::default();
995 let result = verify_tree(&t, "corrupt_test", &cfg);
996 assert!(
997 !result.passed,
998 "verifier must detect the NULL-LSN corruption, got passed=true"
999 );
1000 assert!(
1001 result.errors.iter().any(|e| matches!(
1002 e,
1003 VerifyError::BtreeError { description, .. }
1004 if description.contains("NULL LSN")
1005 )),
1006 "expected a NULL-LSN BtreeError, got: {:?}",
1007 result.errors
1008 );
1009
1010 fn corrupt_first_bin_slot(
1012 tree: &noxu_tree::Tree,
1013 null_lsn: noxu_util::Lsn,
1014 ) -> bool {
1015 fn recurse(
1016 node: &Arc<noxu_tree::NodeRwLock<TreeNode>>,
1017 null_lsn: noxu_util::Lsn,
1018 ) -> bool {
1019 let mut guard = node.write();
1020 match &mut *guard {
1021 TreeNode::Bottom(bin) => {
1022 if let Some(entry) = bin.entries.first_mut() {
1023 entry.known_deleted = false;
1024 entry.lsn = null_lsn;
1025 return true;
1026 }
1027 false
1028 }
1029 TreeNode::Internal(in_node) => {
1030 for child in in_node.resident_children() {
1031 if recurse(&child, null_lsn) {
1032 return true;
1033 }
1034 }
1035 false
1036 }
1037 }
1038 }
1039 match tree.get_root() {
1040 Some(root) => recurse(&root, null_lsn),
1041 None => false,
1042 }
1043 }
1044 }
1045}