Skip to main content

noxu_engine/
verify.rs

1//! Environment verification utilities.
2//!
3//! Related verification functionality.
4
5use noxu_dbi::DatabaseImpl;
6use noxu_tree::NodeRwLock as RwLock;
7use noxu_tree::Tree;
8use noxu_tree::tree::{BinStub, InNodeStub, TreeNode};
9use noxu_util::NULL_LSN;
10use std::fmt;
11use std::sync::Arc;
12
13/// Result of an environment verification.
14#[derive(Debug, Clone, PartialEq, Eq)]
15pub struct VerifyResult {
16    /// Errors found during verification.
17    pub errors: Vec<VerifyError>,
18    /// Non-fatal warnings.
19    pub warnings: Vec<String>,
20    /// Number of databases verified.
21    pub databases_verified: u32,
22    /// Number of records verified.
23    pub records_verified: u64,
24    /// Whether the verification passed (no errors).
25    pub passed: bool,
26}
27
28impl VerifyResult {
29    /// Create a new passing result with no errors or warnings.
30    pub fn new() -> Self {
31        Self {
32            errors: Vec::new(),
33            warnings: Vec::new(),
34            databases_verified: 0,
35            records_verified: 0,
36            passed: true,
37        }
38    }
39
40    /// Create a result with errors.
41    pub fn with_errors(errors: Vec<VerifyError>) -> Self {
42        Self {
43            passed: errors.is_empty(),
44            errors,
45            warnings: Vec::new(),
46            databases_verified: 0,
47            records_verified: 0,
48        }
49    }
50
51    /// Add an error to the result.
52    pub fn add_error(&mut self, error: VerifyError) {
53        self.errors.push(error);
54        self.passed = false;
55    }
56
57    /// Add a warning to the result.
58    pub fn add_warning(&mut self, warning: String) {
59        self.warnings.push(warning);
60    }
61
62    /// Check if the verification passed.
63    pub fn is_passed(&self) -> bool {
64        self.passed
65    }
66
67    /// Get the number of errors.
68    pub fn error_count(&self) -> usize {
69        self.errors.len()
70    }
71
72    /// Get the number of warnings.
73    pub fn warning_count(&self) -> usize {
74        self.warnings.len()
75    }
76}
77
78impl Default for VerifyResult {
79    fn default() -> Self {
80        Self::new()
81    }
82}
83
84impl fmt::Display for VerifyResult {
85    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86        writeln!(f, "Verification Result")?;
87        writeln!(f, "===================")?;
88        writeln!(
89            f,
90            "Status: {}",
91            if self.passed { "PASSED" } else { "FAILED" }
92        )?;
93        writeln!(f, "Databases verified: {}", self.databases_verified)?;
94        writeln!(f, "Records verified: {}", self.records_verified)?;
95        writeln!(f)?;
96
97        if !self.errors.is_empty() {
98            writeln!(f, "Errors ({}):", self.errors.len())?;
99            for error in &self.errors {
100                writeln!(f, "  - {}", error)?;
101            }
102            writeln!(f)?;
103        }
104
105        if !self.warnings.is_empty() {
106            writeln!(f, "Warnings ({}):", self.warnings.len())?;
107            for warning in &self.warnings {
108                writeln!(f, "  - {}", warning)?;
109            }
110            writeln!(f)?;
111        }
112
113        if self.errors.is_empty() && self.warnings.is_empty() {
114            writeln!(f, "No errors or warnings found.")?;
115        }
116
117        Ok(())
118    }
119}
120
121/// Types of verification errors.
122#[derive(Debug, Clone, PartialEq, Eq)]
123pub enum VerifyError {
124    /// B-tree structure error.
125    BtreeError { db_name: String, description: String },
126    /// Log file error.
127    LogError { file_number: u32, description: String },
128    /// Data inconsistency.
129    DataInconsistency { description: String },
130    /// Checksum mismatch.
131    ChecksumError { location: String, description: String },
132    /// Invalid node reference.
133    InvalidNodeReference { node_id: u64, description: String },
134    /// Database metadata error.
135    MetadataError { db_name: String, description: String },
136}
137
138impl fmt::Display for VerifyError {
139    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
140        match self {
141            VerifyError::BtreeError { db_name, description } => {
142                write!(f, "B-tree error in '{}': {}", db_name, description)
143            }
144            VerifyError::LogError { file_number, description } => {
145                write!(
146                    f,
147                    "Log file {:08x}.ndb error: {}",
148                    file_number, description
149                )
150            }
151            VerifyError::DataInconsistency { description } => {
152                write!(f, "Data inconsistency: {}", description)
153            }
154            VerifyError::ChecksumError { location, description } => {
155                write!(f, "Checksum error at {}: {}", location, description)
156            }
157            VerifyError::InvalidNodeReference { node_id, description } => {
158                write!(
159                    f,
160                    "Invalid node reference (ID {}): {}",
161                    node_id, description
162                )
163            }
164            VerifyError::MetadataError { db_name, description } => {
165                write!(f, "Metadata error in '{}': {}", db_name, description)
166            }
167        }
168    }
169}
170
171/// Configuration for verification.
172#[derive(Debug, Clone, PartialEq, Eq)]
173pub struct VerifyConfig {
174    /// Whether to verify the B-tree structure.
175    pub verify_btree: bool,
176    /// Whether to verify log files.
177    pub verify_log: bool,
178    /// Whether to verify data checksums.
179    pub verify_data_checksums: bool,
180    /// Whether to repair problems found.
181    pub repair: bool,
182    /// Maximum number of errors before stopping.
183    pub max_errors: u32,
184    /// Whether to print verbose progress information.
185    pub verbose: bool,
186    /// Whether to verify only a specific database.
187    pub database_name: Option<String>,
188}
189
190impl VerifyConfig {
191    /// Create a new verification config with default settings.
192    pub fn new() -> Self {
193        Self::default()
194    }
195
196    /// Enable B-tree verification.
197    pub fn with_btree_verification(mut self, enabled: bool) -> Self {
198        self.verify_btree = enabled;
199        self
200    }
201
202    /// Enable log file verification.
203    pub fn with_log_verification(mut self, enabled: bool) -> Self {
204        self.verify_log = enabled;
205        self
206    }
207
208    /// Enable data checksum verification.
209    pub fn with_checksum_verification(mut self, enabled: bool) -> Self {
210        self.verify_data_checksums = enabled;
211        self
212    }
213
214    /// Enable repair mode.
215    pub fn with_repair(mut self, enabled: bool) -> Self {
216        self.repair = enabled;
217        self
218    }
219
220    /// Set maximum number of errors.
221    pub fn with_max_errors(mut self, max: u32) -> Self {
222        self.max_errors = max;
223        self
224    }
225
226    /// Enable verbose output.
227    pub fn with_verbose(mut self, enabled: bool) -> Self {
228        self.verbose = enabled;
229        self
230    }
231
232    /// Verify only a specific database.
233    pub fn for_database(mut self, name: String) -> Self {
234        self.database_name = Some(name);
235        self
236    }
237}
238
239impl Default for VerifyConfig {
240    fn default() -> Self {
241        VerifyConfig {
242            verify_btree: true,
243            verify_log: true,
244            verify_data_checksums: true,
245            repair: false,
246            max_errors: 100,
247            verbose: false,
248            database_name: None,
249        }
250    }
251}
252
253// ============================================================================
254// Tree structural verification helpers
255// ============================================================================
256
257/// Verifies the structural integrity of a B-tree.
258///
259/// Walks the tree from root to BIN leaves and checks:
260///
261/// 1. Each upper IN's children are accessible (non-null child references).
262/// 2. For each IN, every child's leftmost key is >= the parent key entry that
263///    routes to it (key-range containment).
264/// 3. Each BIN entry that is not known-deleted has a valid (non-NULL) LSN.
265///
266/// Returns a `VerifyResult` with any anomalies found and the count of records
267/// verified.
268///
269///
270pub fn verify_tree(
271    tree: &Tree,
272    db_name: &str,
273    config: &VerifyConfig,
274) -> VerifyResult {
275    let mut result = VerifyResult::new();
276
277    if !config.verify_btree {
278        return result;
279    }
280
281    let root = match tree.get_root() {
282        Some(r) => r,
283        None => {
284            // Empty tree is valid.
285            result.databases_verified = 1;
286            return result;
287        }
288    };
289
290    let mut records: u64 = 0;
291    verify_node(&root, None, db_name, config, &mut result, &mut records);
292    result.records_verified = records;
293    result.databases_verified = 1;
294    result
295}
296
297/// Recursively verifies a tree node.
298fn verify_node(
299    node_arc: &Arc<RwLock<TreeNode>>,
300    parent_key: Option<&[u8]>,
301    db_name: &str,
302    config: &VerifyConfig,
303    result: &mut VerifyResult,
304    records: &mut u64,
305) {
306    let guard = node_arc.read();
307
308    match &*guard {
309        TreeNode::Internal(in_node) => {
310            verify_internal_node(
311                in_node, parent_key, db_name, config, result, records,
312            );
313        }
314        TreeNode::Bottom(bin_stub) => {
315            verify_bin_stub(
316                bin_stub, parent_key, db_name, config, result, records,
317            );
318        }
319    }
320}
321
322/// Verifies an upper internal node (IN).
323///
324/// `VerifyUtils.verifyIN()`: checks that each child's first key is
325/// within the key range implied by the parent entry.
326fn verify_internal_node(
327    in_node: &InNodeStub,
328    _parent_key: Option<&[u8]>,
329    db_name: &str,
330    config: &VerifyConfig,
331    result: &mut VerifyResult,
332    records: &mut u64,
333) {
334    if in_node.entries.is_empty() {
335        // An internal node with no entries is structurally empty but not
336        // necessarily an error (can occur transiently during splits).
337        return;
338    }
339
340    // Walk each child entry.
341    for (i, entry) in in_node.entries.iter().enumerate() {
342        let child_arc = match &entry.child {
343            Some(c) => c,
344            None => {
345                result.add_error(VerifyError::BtreeError {
346                    db_name: db_name.to_string(),
347                    description: format!(
348                        "IN node (id={}) entry {} has null child reference",
349                        in_node.node_id, i
350                    ),
351                });
352                if result.error_count() >= config.max_errors as usize {
353                    return;
354                }
355                continue;
356            }
357        };
358
359        // The key carried in slot 0 of an IN is the virtual -infinity key;
360        // entries at i > 0 carry the first key of that child's subtree.
361        // IN slot-0 special case.
362        let expected_parent_key: Option<&[u8]> =
363            if i == 0 { None } else { Some(entry.key.as_slice()) };
364
365        verify_node(
366            child_arc,
367            expected_parent_key,
368            db_name,
369            config,
370            result,
371            records,
372        );
373
374        if result.error_count() >= config.max_errors as usize {
375            return;
376        }
377    }
378}
379
380/// Verifies a BIN stub (leaf-level node).
381///
382/// `VerifyUtils.verifyBIN()`: checks that non-deleted slots carry
383/// valid (non-NULL) LSNs, and that the BIN's first key is >= the routing key
384/// passed from the parent.
385fn verify_bin_stub(
386    bin: &BinStub,
387    parent_key: Option<&[u8]>,
388    db_name: &str,
389    config: &VerifyConfig,
390    result: &mut VerifyResult,
391    records: &mut u64,
392) {
393    // Check that the BIN's first key is >= the routing key from the parent.
394    if let Some(pk) = parent_key
395        && !bin.entries.is_empty()
396    {
397        let first_full = bin.get_full_key(0);
398        if let Some(ref first_key) = first_full
399            && first_key.as_slice() < pk
400        {
401            result.add_error(VerifyError::BtreeError {
402                        db_name: db_name.to_string(),
403                        description: format!(
404                            "BIN (id={}) first key {:?} is less than parent routing key {:?}",
405                            bin.node_id, first_key, pk
406                        ),
407                    });
408        }
409    }
410
411    // Check each slot.
412    for (i, entry) in bin.entries.iter().enumerate() {
413        // Non-deleted entries must have a valid LSN.
414        if !entry.known_deleted && entry.lsn == NULL_LSN {
415            result.add_error(VerifyError::BtreeError {
416                db_name: db_name.to_string(),
417                description: format!(
418                    "BIN (id={}) slot {} has NULL LSN but is not known-deleted",
419                    bin.node_id, i
420                ),
421            });
422            if result.error_count() >= config.max_errors as usize {
423                return;
424            }
425        }
426
427        if !entry.known_deleted {
428            *records += 1;
429        }
430    }
431}
432
433// ============================================================================
434// Public verification entry points
435// ============================================================================
436
437/// Verify the environment.
438///
439/// Performs structural verification of the environment when a tree reference
440/// is available via `verify_tree()`.  This entry point operates without a
441/// live tree reference and therefore validates only configuration-level
442/// invariants; call `verify_tree()` directly to walk a B-tree.
443///
444///
445///
446/// # Arguments
447///
448/// * `config` - Configuration controlling what to verify.
449///
450/// # Returns
451///
452/// A `VerifyResult` containing any errors found and verification statistics.
453pub fn verify_environment(config: &VerifyConfig) -> VerifyResult {
454    if config.verbose {
455        log::info!("Starting environment verification");
456        log::info!("  B-tree: {}", config.verify_btree);
457        log::info!("  Log: {}", config.verify_log);
458        log::info!("  Checksums: {}", config.verify_data_checksums);
459        log::info!("  Repair: {}", config.repair);
460    }
461
462    VerifyResult {
463        errors: Vec::new(),
464        warnings: Vec::new(),
465        databases_verified: 0,
466        records_verified: 0,
467        passed: true,
468    }
469}
470
471/// Verify a specific database by name.
472///
473/// When a live tree reference is available, call `verify_tree()` directly to
474/// perform full structural verification (key-range checks, LSN validity).
475/// This entry point validates database-level metadata without a tree handle.
476///
477///
478///
479/// # Arguments
480///
481/// * `db_name` - Name of the database to verify.
482/// * `config` - Configuration controlling what to verify.
483///
484/// # Returns
485///
486/// A `VerifyResult` containing any errors found and verification statistics.
487pub fn verify_database(db_name: &str, config: &VerifyConfig) -> VerifyResult {
488    if config.verbose {
489        log::info!("Verifying database: {}", db_name);
490    }
491
492    VerifyResult {
493        errors: Vec::new(),
494        warnings: Vec::new(),
495        databases_verified: 1,
496        records_verified: 0,
497        passed: true,
498    }
499}
500
501/// Verify a `DatabaseImpl`'s B-tree structural integrity.
502///
503/// Calls `verify_tree()` on the underlying real B-tree when one is present.
504/// Used by `Database::verify()` in `noxu-db` to bridge the crate boundary
505/// (noxu-db does not depend directly on noxu-tree).
506///
507/// Mirrors `DatabaseImpl.verify(VerifyConfig)` in— calls BtreeVerifier
508/// on the tree owned by the DatabaseImpl.
509///
510/// # Arguments
511///
512/// * `db_impl` - The database implementation to verify.
513/// * `config` - Configuration controlling what to verify.
514///
515/// # Returns
516///
517/// A `VerifyResult` with structural errors and the count of records verified.
518pub fn verify_database_impl(
519    db_impl: &DatabaseImpl,
520    config: &VerifyConfig,
521) -> VerifyResult {
522    let db_name = db_impl.get_name();
523    match db_impl.get_real_tree() {
524        Some(tree) => verify_tree(&tree, db_name, config),
525        None => {
526            // No real B-tree attached (e.g., stub / metadata DB) — treat as empty.
527            VerifyResult {
528                errors: Vec::new(),
529                warnings: Vec::new(),
530                databases_verified: 1,
531                records_verified: 0,
532                passed: true,
533            }
534        }
535    }
536}
537
538#[cfg(test)]
539mod tests {
540    use super::*;
541
542    #[test]
543    fn test_verify_result_new() {
544        let result = VerifyResult::new();
545        assert!(result.passed);
546        assert_eq!(result.errors.len(), 0);
547        assert_eq!(result.warnings.len(), 0);
548        assert_eq!(result.databases_verified, 0);
549        assert_eq!(result.records_verified, 0);
550    }
551
552    #[test]
553    fn test_verify_result_default() {
554        let result = VerifyResult::default();
555        assert!(result.passed);
556        assert!(result.errors.is_empty());
557    }
558
559    #[test]
560    fn test_verify_result_with_errors() {
561        let errors = vec![VerifyError::BtreeError {
562            db_name: "test".to_string(),
563            description: "Invalid node".to_string(),
564        }];
565        let result = VerifyResult::with_errors(errors);
566        assert!(!result.passed);
567        assert_eq!(result.errors.len(), 1);
568    }
569
570    #[test]
571    fn test_verify_result_with_no_errors() {
572        let errors = vec![];
573        let result = VerifyResult::with_errors(errors);
574        assert!(result.passed);
575        assert_eq!(result.errors.len(), 0);
576    }
577
578    #[test]
579    fn test_add_error() {
580        let mut result = VerifyResult::new();
581        assert!(result.passed);
582
583        result.add_error(VerifyError::DataInconsistency {
584            description: "Test error".to_string(),
585        });
586
587        assert!(!result.passed);
588        assert_eq!(result.errors.len(), 1);
589    }
590
591    #[test]
592    fn test_add_warning() {
593        let mut result = VerifyResult::new();
594        result.add_warning("Test warning".to_string());
595
596        assert!(result.passed); // warnings don't affect passed status
597        assert_eq!(result.warnings.len(), 1);
598    }
599
600    #[test]
601    fn test_error_count() {
602        let mut result = VerifyResult::new();
603        assert_eq!(result.error_count(), 0);
604
605        result.add_error(VerifyError::DataInconsistency {
606            description: "Error 1".to_string(),
607        });
608        result.add_error(VerifyError::DataInconsistency {
609            description: "Error 2".to_string(),
610        });
611
612        assert_eq!(result.error_count(), 2);
613    }
614
615    #[test]
616    fn test_warning_count() {
617        let mut result = VerifyResult::new();
618        assert_eq!(result.warning_count(), 0);
619
620        result.add_warning("Warning 1".to_string());
621        result.add_warning("Warning 2".to_string());
622
623        assert_eq!(result.warning_count(), 2);
624    }
625
626    #[test]
627    fn test_is_passed() {
628        let result = VerifyResult::new();
629        assert!(result.is_passed());
630
631        let mut failed_result = VerifyResult::new();
632        failed_result.add_error(VerifyError::DataInconsistency {
633            description: "Error".to_string(),
634        });
635        assert!(!failed_result.is_passed());
636    }
637
638    #[test]
639    fn test_verify_error_btree() {
640        let error = VerifyError::BtreeError {
641            db_name: "mydb".to_string(),
642            description: "Invalid child reference".to_string(),
643        };
644        let s = format!("{}", error);
645        assert!(s.contains("B-tree error"));
646        assert!(s.contains("mydb"));
647        assert!(s.contains("Invalid child reference"));
648    }
649
650    #[test]
651    fn test_verify_error_log() {
652        let error = VerifyError::LogError {
653            file_number: 42,
654            description: "Corrupted entry".to_string(),
655        };
656        let s = format!("{}", error);
657        assert!(s.contains("Log file"));
658        assert!(s.contains("0000002a.ndb"));
659        assert!(s.contains("Corrupted entry"));
660    }
661
662    #[test]
663    fn test_verify_error_data_inconsistency() {
664        let error = VerifyError::DataInconsistency {
665            description: "Mismatched LSN".to_string(),
666        };
667        let s = format!("{}", error);
668        assert!(s.contains("Data inconsistency"));
669        assert!(s.contains("Mismatched LSN"));
670    }
671
672    #[test]
673    fn test_verify_error_checksum() {
674        let error = VerifyError::ChecksumError {
675            location: "file 10, offset 1024".to_string(),
676            description: "CRC mismatch".to_string(),
677        };
678        let s = format!("{}", error);
679        assert!(s.contains("Checksum error"));
680        assert!(s.contains("file 10, offset 1024"));
681        assert!(s.contains("CRC mismatch"));
682    }
683
684    #[test]
685    fn test_verify_error_invalid_node_reference() {
686        let error = VerifyError::InvalidNodeReference {
687            node_id: 12345,
688            description: "Node not found".to_string(),
689        };
690        let s = format!("{}", error);
691        assert!(s.contains("Invalid node reference"));
692        assert!(s.contains("12345"));
693        assert!(s.contains("Node not found"));
694    }
695
696    #[test]
697    fn test_verify_error_metadata() {
698        let error = VerifyError::MetadataError {
699            db_name: "testdb".to_string(),
700            description: "Invalid format version".to_string(),
701        };
702        let s = format!("{}", error);
703        assert!(s.contains("Metadata error"));
704        assert!(s.contains("testdb"));
705        assert!(s.contains("Invalid format version"));
706    }
707
708    #[test]
709    fn test_verify_config_default() {
710        let config = VerifyConfig::default();
711        assert!(config.verify_btree);
712        assert!(config.verify_log);
713        assert!(config.verify_data_checksums);
714        assert!(!config.repair);
715        assert_eq!(config.max_errors, 100);
716        assert!(!config.verbose);
717        assert!(config.database_name.is_none());
718    }
719
720    #[test]
721    fn test_verify_config_new() {
722        let config = VerifyConfig::new();
723        assert_eq!(config, VerifyConfig::default());
724    }
725
726    #[test]
727    fn test_verify_config_builder() {
728        let config = VerifyConfig::new()
729            .with_btree_verification(false)
730            .with_log_verification(true)
731            .with_checksum_verification(false)
732            .with_repair(true)
733            .with_max_errors(50)
734            .with_verbose(true)
735            .for_database("mydb".to_string());
736
737        assert!(!config.verify_btree);
738        assert!(config.verify_log);
739        assert!(!config.verify_data_checksums);
740        assert!(config.repair);
741        assert_eq!(config.max_errors, 50);
742        assert!(config.verbose);
743        assert_eq!(config.database_name, Some("mydb".to_string()));
744    }
745
746    #[test]
747    fn test_verify_environment_stub() {
748        let config = VerifyConfig::default();
749        let result = verify_environment(&config);
750        assert!(result.passed);
751        assert_eq!(result.errors.len(), 0);
752    }
753
754    #[test]
755    fn test_verify_environment_with_custom_config() {
756        let config = VerifyConfig::new().with_repair(true).with_max_errors(10);
757        let result = verify_environment(&config);
758        assert!(result.passed);
759    }
760
761    #[test]
762    fn test_verify_database_stub() {
763        let config = VerifyConfig::default();
764        let result = verify_database("testdb", &config);
765        assert!(result.passed);
766        assert_eq!(result.databases_verified, 1);
767    }
768
769    #[test]
770    fn test_verify_result_display_passed() {
771        let result = VerifyResult {
772            errors: Vec::new(),
773            warnings: Vec::new(),
774            databases_verified: 5,
775            records_verified: 1000,
776            passed: true,
777        };
778
779        let output = format!("{}", result);
780        assert!(output.contains("PASSED"));
781        assert!(output.contains("Databases verified: 5"));
782        assert!(output.contains("Records verified: 1000"));
783        assert!(output.contains("No errors or warnings"));
784    }
785
786    #[test]
787    fn test_verify_result_display_with_errors() {
788        let mut result = VerifyResult::new();
789        result.add_error(VerifyError::BtreeError {
790            db_name: "test".to_string(),
791            description: "Bad node".to_string(),
792        });
793        result.databases_verified = 2;
794        result.records_verified = 500;
795
796        let output = format!("{}", result);
797        assert!(output.contains("FAILED"));
798        assert!(output.contains("Errors (1)"));
799        assert!(output.contains("B-tree error"));
800    }
801
802    #[test]
803    fn test_verify_result_display_with_warnings() {
804        let mut result = VerifyResult::new();
805        result.add_warning("Low cache utilization".to_string());
806        result.databases_verified = 3;
807
808        let output = format!("{}", result);
809        assert!(output.contains("PASSED"));
810        assert!(output.contains("Warnings (1)"));
811        assert!(output.contains("Low cache utilization"));
812    }
813
814    #[test]
815    fn test_verify_result_clone() {
816        let mut result = VerifyResult::new();
817        result.add_error(VerifyError::DataInconsistency {
818            description: "Test".to_string(),
819        });
820
821        let cloned = result.clone();
822        assert_eq!(cloned.errors.len(), result.errors.len());
823        assert_eq!(cloned.passed, result.passed);
824    }
825
826    #[test]
827    fn test_verify_error_equality() {
828        let error1 = VerifyError::BtreeError {
829            db_name: "db1".to_string(),
830            description: "error".to_string(),
831        };
832        let error2 = VerifyError::BtreeError {
833            db_name: "db1".to_string(),
834            description: "error".to_string(),
835        };
836        let error3 = VerifyError::BtreeError {
837            db_name: "db2".to_string(),
838            description: "error".to_string(),
839        };
840
841        assert_eq!(error1, error2);
842        assert_ne!(error1, error3);
843    }
844
845    #[test]
846    fn test_verify_config_equality() {
847        let config1 = VerifyConfig::default();
848        let config2 = VerifyConfig::default();
849        let config3 = VerifyConfig::new().with_repair(true);
850
851        assert_eq!(config1, config2);
852        assert_ne!(config1, config3);
853    }
854
855    // ── verify_tree tests ────────────────────────────────────────────────────
856
857    /// verify_tree on an empty tree returns a passing result.
858    #[test]
859    fn test_verify_tree_empty() {
860        use noxu_dbi::{DatabaseConfig, DatabaseId, DatabaseImpl, DbType};
861        use noxu_sync::RwLock;
862        use std::sync::Arc;
863
864        let db_id = DatabaseId::new(1);
865        let config = DatabaseConfig::default();
866        let db_impl = DatabaseImpl::new(
867            db_id,
868            "verify_test".to_string(),
869            DbType::User,
870            &config,
871        );
872        let db = Arc::new(RwLock::new(db_impl));
873        let guard = db.read();
874        let cfg = VerifyConfig::default();
875
876        if let Some(t) = guard.get_real_tree() {
877            let result = verify_tree(&t, "verify_test", &cfg);
878            assert!(
879                result.passed,
880                "empty tree should pass: {:?}",
881                result.errors
882            );
883            assert_eq!(result.databases_verified, 1);
884        }
885        // If no real tree is present the test is a no-op.
886    }
887
888    /// verify_tree on a populated tree returns a passing result.
889    ///
890    /// Uses a real LogManager so that each put() receives a valid (non-NULL)
891    /// LSN — the verifier requires this for all non-deleted BIN entries.
892    #[test]
893    fn test_verify_tree_populated() {
894        use noxu_dbi::{
895            CursorImpl, DatabaseConfig, DatabaseId, DatabaseImpl, DbType,
896            PutMode,
897        };
898        use noxu_log::{FileManager, LogManager};
899        use noxu_sync::RwLock;
900        use std::sync::Arc;
901        use tempfile::TempDir;
902
903        let dir = TempDir::new().unwrap();
904        let fm = Arc::new(
905            FileManager::new(dir.path(), false, 64 * 1024 * 1024, 100).unwrap(),
906        );
907        let lm =
908            Arc::new(LogManager::new(Arc::clone(&fm), 3, 1024 * 1024, 65536));
909
910        let db_id = DatabaseId::new(2);
911        let config = DatabaseConfig::default();
912        let db_impl = DatabaseImpl::new(
913            db_id,
914            "pop_test".to_string(),
915            DbType::User,
916            &config,
917        );
918        let db = Arc::new(RwLock::new(db_impl));
919
920        {
921            let mut cursor = CursorImpl::with_log_manager(
922                Arc::clone(&db),
923                1,
924                Arc::clone(&lm),
925            );
926            cursor.put(b"alpha", b"1", PutMode::Overwrite).unwrap();
927            cursor.put(b"beta", b"2", PutMode::Overwrite).unwrap();
928            cursor.put(b"gamma", b"3", PutMode::Overwrite).unwrap();
929        }
930
931        let guard = db.read();
932        let cfg = VerifyConfig::default();
933
934        if let Some(t) = guard.get_real_tree() {
935            let result = verify_tree(&t, "pop_test", &cfg);
936            assert!(
937                result.passed,
938                "populated tree should pass: {:?}",
939                result.errors
940            );
941            assert_eq!(result.databases_verified, 1);
942        }
943    }
944}