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 && bin.get_lsn(i) == 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 (i, entry) in bin.entries.iter().enumerate() {
513 let lsn = bin.get_lsn(i);
515 if !entry.known_deleted && lsn != NULL_LSN {
516 lsns.insert(lsn);
517 }
518 }
519 }
520 }
521}
522
523pub fn check_lsns_against_tracker(
533 db_impl: &DatabaseImpl,
534 tracker: &noxu_cleaner::UtilizationTracker,
535 result: &mut VerifyResult,
536) {
537 let tree = match db_impl.get_real_tree() {
538 Some(t) => t,
539 None => return,
540 };
541 let live = gather_tree_lsns(&tree);
542 let check = noxu_cleaner::check_lsns(live, tracker);
543 for lsn in check.obsolete_contains_live {
544 result.add_error(VerifyError::DataInconsistency {
545 description: format!(
546 "Obsolete LSN set contains valid LSN {} in database '{}' \
547 (VerifyUtils.checkLsns: live tree LSN recorded obsolete)",
548 lsn,
549 db_impl.get_name()
550 ),
551 });
552 }
553}
554
555#[cfg(test)]
556mod tests {
557 use super::*;
558
559 #[test]
560 fn test_verify_result_new() {
561 let result = VerifyResult::new();
562 assert!(result.passed);
563 assert_eq!(result.errors.len(), 0);
564 assert_eq!(result.warnings.len(), 0);
565 assert_eq!(result.databases_verified, 0);
566 assert_eq!(result.records_verified, 0);
567 }
568
569 #[test]
570 fn test_verify_result_default() {
571 let result = VerifyResult::default();
572 assert!(result.passed);
573 assert!(result.errors.is_empty());
574 }
575
576 #[test]
577 fn test_verify_result_with_errors() {
578 let errors = vec![VerifyError::BtreeError {
579 db_name: "test".to_string(),
580 description: "Invalid node".to_string(),
581 }];
582 let result = VerifyResult::with_errors(errors);
583 assert!(!result.passed);
584 assert_eq!(result.errors.len(), 1);
585 }
586
587 #[test]
588 fn test_verify_result_with_no_errors() {
589 let errors = vec![];
590 let result = VerifyResult::with_errors(errors);
591 assert!(result.passed);
592 assert_eq!(result.errors.len(), 0);
593 }
594
595 #[test]
596 fn test_add_error() {
597 let mut result = VerifyResult::new();
598 assert!(result.passed);
599
600 result.add_error(VerifyError::DataInconsistency {
601 description: "Test error".to_string(),
602 });
603
604 assert!(!result.passed);
605 assert_eq!(result.errors.len(), 1);
606 }
607
608 #[test]
609 fn test_add_warning() {
610 let mut result = VerifyResult::new();
611 result.add_warning("Test warning".to_string());
612
613 assert!(result.passed); assert_eq!(result.warnings.len(), 1);
615 }
616
617 #[test]
618 fn test_error_count() {
619 let mut result = VerifyResult::new();
620 assert_eq!(result.error_count(), 0);
621
622 result.add_error(VerifyError::DataInconsistency {
623 description: "Error 1".to_string(),
624 });
625 result.add_error(VerifyError::DataInconsistency {
626 description: "Error 2".to_string(),
627 });
628
629 assert_eq!(result.error_count(), 2);
630 }
631
632 #[test]
633 fn test_warning_count() {
634 let mut result = VerifyResult::new();
635 assert_eq!(result.warning_count(), 0);
636
637 result.add_warning("Warning 1".to_string());
638 result.add_warning("Warning 2".to_string());
639
640 assert_eq!(result.warning_count(), 2);
641 }
642
643 #[test]
644 fn test_is_passed() {
645 let result = VerifyResult::new();
646 assert!(result.is_passed());
647
648 let mut failed_result = VerifyResult::new();
649 failed_result.add_error(VerifyError::DataInconsistency {
650 description: "Error".to_string(),
651 });
652 assert!(!failed_result.is_passed());
653 }
654
655 #[test]
656 fn test_verify_error_btree() {
657 let error = VerifyError::BtreeError {
658 db_name: "mydb".to_string(),
659 description: "Invalid child reference".to_string(),
660 };
661 let s = format!("{}", error);
662 assert!(s.contains("B-tree error"));
663 assert!(s.contains("mydb"));
664 assert!(s.contains("Invalid child reference"));
665 }
666
667 #[test]
668 fn test_verify_error_log() {
669 let error = VerifyError::LogError {
670 file_number: 42,
671 description: "Corrupted entry".to_string(),
672 };
673 let s = format!("{}", error);
674 assert!(s.contains("Log file"));
675 assert!(s.contains("0000002a.ndb"));
676 assert!(s.contains("Corrupted entry"));
677 }
678
679 #[test]
680 fn test_verify_error_data_inconsistency() {
681 let error = VerifyError::DataInconsistency {
682 description: "Mismatched LSN".to_string(),
683 };
684 let s = format!("{}", error);
685 assert!(s.contains("Data inconsistency"));
686 assert!(s.contains("Mismatched LSN"));
687 }
688
689 #[test]
690 fn test_verify_error_checksum() {
691 let error = VerifyError::ChecksumError {
692 location: "file 10, offset 1024".to_string(),
693 description: "CRC mismatch".to_string(),
694 };
695 let s = format!("{}", error);
696 assert!(s.contains("Checksum error"));
697 assert!(s.contains("file 10, offset 1024"));
698 assert!(s.contains("CRC mismatch"));
699 }
700
701 #[test]
702 fn test_verify_error_invalid_node_reference() {
703 let error = VerifyError::InvalidNodeReference {
704 node_id: 12345,
705 description: "Node not found".to_string(),
706 };
707 let s = format!("{}", error);
708 assert!(s.contains("Invalid node reference"));
709 assert!(s.contains("12345"));
710 assert!(s.contains("Node not found"));
711 }
712
713 #[test]
714 fn test_verify_error_metadata() {
715 let error = VerifyError::MetadataError {
716 db_name: "testdb".to_string(),
717 description: "Invalid format version".to_string(),
718 };
719 let s = format!("{}", error);
720 assert!(s.contains("Metadata error"));
721 assert!(s.contains("testdb"));
722 assert!(s.contains("Invalid format version"));
723 }
724
725 #[test]
726 fn test_verify_config_default() {
727 let config = VerifyConfig::default();
728 assert!(config.verify_btree);
729 assert!(config.verify_log);
730 assert!(config.verify_data_checksums);
731 assert!(!config.repair);
732 assert_eq!(config.max_errors, 100);
733 assert!(!config.verbose);
734 assert!(config.database_name.is_none());
735 }
736
737 #[test]
738 fn test_verify_config_new() {
739 let config = VerifyConfig::new();
740 assert_eq!(config, VerifyConfig::default());
741 }
742
743 #[test]
744 fn test_verify_config_builder() {
745 let config = VerifyConfig::new()
746 .with_btree_verification(false)
747 .with_log_verification(true)
748 .with_checksum_verification(false)
749 .with_repair(true)
750 .with_max_errors(50)
751 .with_verbose(true)
752 .for_database("mydb".to_string());
753
754 assert!(!config.verify_btree);
755 assert!(config.verify_log);
756 assert!(!config.verify_data_checksums);
757 assert!(config.repair);
758 assert_eq!(config.max_errors, 50);
759 assert!(config.verbose);
760 assert_eq!(config.database_name, Some("mydb".to_string()));
761 }
762
763 #[test]
764 fn test_verify_result_display_passed() {
765 let result = VerifyResult {
766 errors: Vec::new(),
767 warnings: Vec::new(),
768 databases_verified: 5,
769 records_verified: 1000,
770 passed: true,
771 };
772
773 let output = format!("{}", result);
774 assert!(output.contains("PASSED"));
775 assert!(output.contains("Databases verified: 5"));
776 assert!(output.contains("Records verified: 1000"));
777 assert!(output.contains("No errors or warnings"));
778 }
779
780 #[test]
781 fn test_verify_result_display_with_errors() {
782 let mut result = VerifyResult::new();
783 result.add_error(VerifyError::BtreeError {
784 db_name: "test".to_string(),
785 description: "Bad node".to_string(),
786 });
787 result.databases_verified = 2;
788 result.records_verified = 500;
789
790 let output = format!("{}", result);
791 assert!(output.contains("FAILED"));
792 assert!(output.contains("Errors (1)"));
793 assert!(output.contains("B-tree error"));
794 }
795
796 #[test]
797 fn test_verify_result_display_with_warnings() {
798 let mut result = VerifyResult::new();
799 result.add_warning("Low cache utilization".to_string());
800 result.databases_verified = 3;
801
802 let output = format!("{}", result);
803 assert!(output.contains("PASSED"));
804 assert!(output.contains("Warnings (1)"));
805 assert!(output.contains("Low cache utilization"));
806 }
807
808 #[test]
809 fn test_verify_result_clone() {
810 let mut result = VerifyResult::new();
811 result.add_error(VerifyError::DataInconsistency {
812 description: "Test".to_string(),
813 });
814
815 let cloned = result.clone();
816 assert_eq!(cloned.errors.len(), result.errors.len());
817 assert_eq!(cloned.passed, result.passed);
818 }
819
820 #[test]
821 fn test_verify_error_equality() {
822 let error1 = VerifyError::BtreeError {
823 db_name: "db1".to_string(),
824 description: "error".to_string(),
825 };
826 let error2 = VerifyError::BtreeError {
827 db_name: "db1".to_string(),
828 description: "error".to_string(),
829 };
830 let error3 = VerifyError::BtreeError {
831 db_name: "db2".to_string(),
832 description: "error".to_string(),
833 };
834
835 assert_eq!(error1, error2);
836 assert_ne!(error1, error3);
837 }
838
839 #[test]
840 fn test_verify_config_equality() {
841 let config1 = VerifyConfig::default();
842 let config2 = VerifyConfig::default();
843 let config3 = VerifyConfig::new().with_repair(true);
844
845 assert_eq!(config1, config2);
846 assert_ne!(config1, config3);
847 }
848
849 #[test]
853 fn test_verify_tree_empty() {
854 use noxu_dbi::{DatabaseConfig, DatabaseId, DatabaseImpl, DbType};
855 use noxu_sync::RwLock;
856 use std::sync::Arc;
857
858 let db_id = DatabaseId::new(1);
859 let config = DatabaseConfig::default();
860 let db_impl = DatabaseImpl::new(
861 db_id,
862 "verify_test".to_string(),
863 DbType::User,
864 &config,
865 );
866 let db = Arc::new(RwLock::new(db_impl));
867 let guard = db.read();
868 let cfg = VerifyConfig::default();
869
870 if let Some(t) = guard.get_real_tree() {
871 let result = verify_tree(&t, "verify_test", &cfg);
872 assert!(
873 result.passed,
874 "empty tree should pass: {:?}",
875 result.errors
876 );
877 assert_eq!(result.databases_verified, 1);
878 }
879 }
881
882 #[test]
887 fn test_verify_tree_populated() {
888 use noxu_dbi::{
889 CursorImpl, DatabaseConfig, DatabaseId, DatabaseImpl, DbType,
890 PutMode,
891 };
892 use noxu_log::{FileManager, LogManager};
893 use noxu_sync::RwLock;
894 use std::sync::Arc;
895 use tempfile::TempDir;
896
897 let dir = TempDir::new().unwrap();
898 let fm = Arc::new(
899 FileManager::new(dir.path(), false, 64 * 1024 * 1024, 100).unwrap(),
900 );
901 let lm =
902 Arc::new(LogManager::new(Arc::clone(&fm), 3, 1024 * 1024, 65536));
903
904 let db_id = DatabaseId::new(2);
905 let config = DatabaseConfig::default();
906 let db_impl = DatabaseImpl::new(
907 db_id,
908 "pop_test".to_string(),
909 DbType::User,
910 &config,
911 );
912 let db = Arc::new(RwLock::new(db_impl));
913
914 {
915 let mut cursor = CursorImpl::with_log_manager(
916 Arc::clone(&db),
917 1,
918 Arc::clone(&lm),
919 );
920 cursor.put(b"alpha", b"1", PutMode::Overwrite).unwrap();
921 cursor.put(b"beta", b"2", PutMode::Overwrite).unwrap();
922 cursor.put(b"gamma", b"3", PutMode::Overwrite).unwrap();
923 }
924
925 let guard = db.read();
926 let cfg = VerifyConfig::default();
927
928 if let Some(t) = guard.get_real_tree() {
929 let result = verify_tree(&t, "pop_test", &cfg);
930 assert!(
931 result.passed,
932 "populated tree should pass: {:?}",
933 result.errors
934 );
935 assert_eq!(result.databases_verified, 1);
936 }
937 }
938
939 #[test]
947 fn test_verify_tree_detects_null_lsn() {
948 use noxu_dbi::{
949 CursorImpl, DatabaseConfig, DatabaseId, DatabaseImpl, DbType,
950 PutMode,
951 };
952 use noxu_log::{FileManager, LogManager};
953 use noxu_sync::RwLock;
954 use noxu_tree::tree::TreeNode;
955 use std::sync::Arc;
956 use tempfile::TempDir;
957
958 let dir = TempDir::new().unwrap();
959 let fm = Arc::new(
960 FileManager::new(dir.path(), false, 64 * 1024 * 1024, 100).unwrap(),
961 );
962 let lm =
963 Arc::new(LogManager::new(Arc::clone(&fm), 3, 1024 * 1024, 65536));
964
965 let db_id = DatabaseId::new(3);
966 let config = DatabaseConfig::default();
967 let db_impl = DatabaseImpl::new(
968 db_id,
969 "corrupt_test".to_string(),
970 DbType::User,
971 &config,
972 );
973 let db = Arc::new(RwLock::new(db_impl));
974
975 {
976 let mut cursor = CursorImpl::with_log_manager(
977 Arc::clone(&db),
978 1,
979 Arc::clone(&lm),
980 );
981 cursor.put(b"alpha", b"1", PutMode::Overwrite).unwrap();
982 cursor.put(b"beta", b"2", PutMode::Overwrite).unwrap();
983 cursor.put(b"gamma", b"3", PutMode::Overwrite).unwrap();
984 }
985
986 let guard = db.read();
987 let t = guard
988 .get_real_tree()
989 .expect("invariant: populated db has a real tree");
990
991 let corrupted = corrupt_first_bin_slot(&t, NULL_LSN);
993 assert!(corrupted, "test setup: expected at least one BIN slot");
994
995 let cfg = VerifyConfig::default();
996 let result = verify_tree(&t, "corrupt_test", &cfg);
997 assert!(
998 !result.passed,
999 "verifier must detect the NULL-LSN corruption, got passed=true"
1000 );
1001 assert!(
1002 result.errors.iter().any(|e| matches!(
1003 e,
1004 VerifyError::BtreeError { description, .. }
1005 if description.contains("NULL LSN")
1006 )),
1007 "expected a NULL-LSN BtreeError, got: {:?}",
1008 result.errors
1009 );
1010
1011 fn corrupt_first_bin_slot(
1013 tree: &noxu_tree::Tree,
1014 null_lsn: noxu_util::Lsn,
1015 ) -> bool {
1016 fn recurse(
1017 node: &Arc<noxu_tree::NodeRwLock<TreeNode>>,
1018 null_lsn: noxu_util::Lsn,
1019 ) -> bool {
1020 let mut guard = node.write();
1021 match &mut *guard {
1022 TreeNode::Bottom(bin) => {
1023 if !bin.entries.is_empty() {
1024 bin.entries[0].known_deleted = false;
1025 bin.set_lsn(0, null_lsn);
1026 return true;
1027 }
1028 false
1029 }
1030 TreeNode::Internal(in_node) => {
1031 for child in in_node.resident_children() {
1032 if recurse(&child, null_lsn) {
1033 return true;
1034 }
1035 }
1036 false
1037 }
1038 }
1039 }
1040 match tree.get_root() {
1041 Some(root) => recurse(&root, null_lsn),
1042 None => false,
1043 }
1044 }
1045 }
1046}