Skip to main content

ipfrs_storage/
storage_snapshot_manager.rs

1//! Copy-on-Write storage snapshot manager.
2//!
3//! # Overview
4//!
5//! [`StorageSnapshotManager`] manages a set of named, tagged snapshots of a
6//! page-based storage region.  Pages are shared between snapshots via reference
7//! counting; an actual copy is only made the first time a shared page is
8//! written (Copy-on-Write semantics).
9//!
10//! # Key types
11//!
12//! | Type | Description |
13//! |------|-------------|
14//! | [`PageId`] | Newtype over `u64` identifying a storage page |
15//! | [`SnapshotId`] | Newtype over `u64` identifying a snapshot |
16//! | [`Page`] | Content of one page with FNV-1a checksum |
17//! | [`SnapshotMetadata`] | Descriptive information about a snapshot |
18//! | [`CoWMapping`] | Per-snapshot view of the page-version table |
19//! | [`SnapshotDiff`] | Difference between two snapshots |
20//! | [`SnapshotConfig`] | Manager configuration |
21//! | [`SnapshotStats`] | Aggregate statistics |
22//! | [`SnapshotError`] | All error variants |
23//! | [`StorageSnapshotManager`] | The main CoW snapshot manager |
24
25use std::collections::HashMap;
26use thiserror::Error;
27
28// ---------------------------------------------------------------------------
29// FNV-1a helper
30// ---------------------------------------------------------------------------
31
32/// Compute FNV-1a 64-bit hash over `data`.
33pub fn fnv1a_64(data: &[u8]) -> u64 {
34    let mut h: u64 = 14_695_981_039_346_656_037;
35    for &b in data {
36        h ^= b as u64;
37        h = h.wrapping_mul(1_099_511_628_211);
38    }
39    h
40}
41
42// ---------------------------------------------------------------------------
43// Core identifier newtypes
44// ---------------------------------------------------------------------------
45
46/// Unique identifier for a storage page.
47#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
48pub struct PageId(pub u64);
49
50impl std::fmt::Display for PageId {
51    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52        write!(f, "PageId({})", self.0)
53    }
54}
55
56/// Unique identifier for a snapshot.
57#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
58pub struct SnapshotId(pub u64);
59
60impl std::fmt::Display for SnapshotId {
61    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62        write!(f, "SnapshotId({})", self.0)
63    }
64}
65
66// ---------------------------------------------------------------------------
67// Page
68// ---------------------------------------------------------------------------
69
70/// A single page of storage data with versioning and CoW reference counting.
71#[derive(Clone, Debug, PartialEq, Eq)]
72pub struct Page {
73    /// Identifier of this page.
74    pub id: PageId,
75    /// Raw byte content.
76    pub data: Vec<u8>,
77    /// Monotonically increasing write counter for this page slot.
78    pub version: u64,
79    /// Number of snapshot+current references to this exact (id, version) pair.
80    /// When `ref_count` drops to zero the page can be garbage-collected.
81    pub ref_count: u32,
82    /// FNV-1a 64-bit checksum of `data`.
83    pub checksum: u64,
84}
85
86impl Page {
87    /// Construct a new `Page`, computing its checksum automatically.
88    pub fn new(id: PageId, data: Vec<u8>, version: u64) -> Self {
89        let checksum = fnv1a_64(&data);
90        Self {
91            id,
92            data,
93            version,
94            ref_count: 1,
95            checksum,
96        }
97    }
98
99    /// Re-verify the stored checksum against the actual data.
100    pub fn verify_checksum(&self) -> bool {
101        fnv1a_64(&self.data) == self.checksum
102    }
103}
104
105// ---------------------------------------------------------------------------
106// SnapshotMetadata
107// ---------------------------------------------------------------------------
108
109/// Descriptive metadata attached to a snapshot.
110#[derive(Clone, Debug, PartialEq, Eq)]
111pub struct SnapshotMetadata {
112    /// Unique snapshot identifier.
113    pub id: SnapshotId,
114    /// Human-readable name.
115    pub name: String,
116    /// Creation timestamp (caller-supplied; typically Unix seconds).
117    pub created_at: u64,
118    /// Number of pages captured in this snapshot.
119    pub page_count: usize,
120    /// Total byte size of all pages in this snapshot.
121    pub size_bytes: u64,
122    /// Optional parent snapshot (for hierarchical chains).
123    pub parent_snapshot: Option<SnapshotId>,
124    /// Arbitrary string tags.
125    pub tags: Vec<String>,
126}
127
128// ---------------------------------------------------------------------------
129// CoWMapping
130// ---------------------------------------------------------------------------
131
132/// The per-snapshot (or current-state) view that maps each live `PageId` to
133/// its version number.  The actual `Page` object is stored centrally in
134/// `StorageSnapshotManager::page_store` keyed by `(PageId, version)`.
135#[derive(Clone, Debug, Default)]
136pub struct CoWMapping {
137    /// `page_id → version` for every page that is part of this view.
138    pub pages: HashMap<PageId, u64>,
139}
140
141impl CoWMapping {
142    /// Look up the version for a given page.
143    pub fn version_of(&self, id: PageId) -> Option<u64> {
144        self.pages.get(&id).copied()
145    }
146}
147
148// ---------------------------------------------------------------------------
149// SnapshotDiff
150// ---------------------------------------------------------------------------
151
152/// The difference between two snapshots (or between a snapshot and current).
153#[derive(Clone, Debug, Default)]
154pub struct SnapshotDiff {
155    /// Pages that exist in `b` but not in `a`.
156    pub added_pages: Vec<PageId>,
157    /// Pages that exist in both but with a different version.
158    pub modified_pages: Vec<PageId>,
159    /// Pages that exist in `a` but not in `b`.
160    pub removed_pages: Vec<PageId>,
161    /// Net change in bytes: `size(b) − size(a)`.  May be negative.
162    pub size_delta: i64,
163}
164
165// ---------------------------------------------------------------------------
166// SnapshotConfig
167// ---------------------------------------------------------------------------
168
169/// Configuration for [`StorageSnapshotManager`].
170#[derive(Clone, Debug)]
171pub struct SnapshotConfig {
172    /// Maximum number of snapshots that may exist simultaneously.
173    pub max_snapshots: usize,
174    /// Maximum number of *distinct* pages (across all versions) that may be
175    /// held in the page store at once.
176    pub max_pages: usize,
177    /// Reserved for future transparent page compression (currently a no-op).
178    pub enable_compression: bool,
179    /// Whether to automatically garbage-collect zero-reference pages when a
180    /// snapshot is deleted.
181    pub auto_gc: bool,
182}
183
184impl Default for SnapshotConfig {
185    fn default() -> Self {
186        Self {
187            max_snapshots: 64,
188            max_pages: 1_048_576,
189            enable_compression: false,
190            auto_gc: true,
191        }
192    }
193}
194
195// ---------------------------------------------------------------------------
196// SnapshotStats
197// ---------------------------------------------------------------------------
198
199/// Aggregate statistics for [`StorageSnapshotManager`].
200#[derive(Clone, Debug, Default, PartialEq, Eq)]
201pub struct SnapshotStats {
202    /// Number of snapshots currently held.
203    pub total_snapshots: usize,
204    /// Total number of page *versions* in the page store (including dead ones
205    /// not yet GC'd).
206    pub total_pages: usize,
207    /// Number of page *versions* referenced by two or more snapshots/current.
208    pub shared_pages: usize,
209    /// Sum of `data.len()` for every page version in the store.
210    pub total_size_bytes: u64,
211    /// Cumulative number of CoW copy operations performed since creation.
212    pub cow_copies_made: u64,
213}
214
215// ---------------------------------------------------------------------------
216// SnapshotError
217// ---------------------------------------------------------------------------
218
219/// Errors returned by [`StorageSnapshotManager`] operations.
220#[derive(Debug, Error, PartialEq, Eq)]
221pub enum SnapshotError {
222    /// No snapshot with the given id was found.
223    #[error("snapshot not found: {0}")]
224    SnapshotNotFound(SnapshotId),
225
226    /// No page with the given id exists in the current state.
227    #[error("page not found: {0}")]
228    PageNotFound(PageId),
229
230    /// The snapshot limit configured in [`SnapshotConfig::max_snapshots`]
231    /// would be exceeded.
232    #[error("maximum snapshot count exceeded")]
233    MaxSnapshotsExceeded,
234
235    /// The page limit configured in [`SnapshotConfig::max_pages`] would be
236    /// exceeded.
237    #[error("maximum page count exceeded")]
238    MaxPagesExceeded,
239
240    /// A page's stored checksum does not match its data.
241    #[error("checksum mismatch for page {page_id}: expected {expected:#018x}, got {got:#018x}")]
242    ChecksumMismatch {
243        /// The page whose checksum is wrong.
244        page_id: PageId,
245        /// Expected (stored) checksum.
246        expected: u64,
247        /// Computed checksum over actual data.
248        got: u64,
249    },
250
251    /// The snapshot specified as parent does not exist.
252    #[error("parent snapshot not found: {0}")]
253    ParentSnapshotNotFound(SnapshotId),
254}
255
256// ---------------------------------------------------------------------------
257// Internal page store key
258// ---------------------------------------------------------------------------
259
260/// Internal composite key: `(page_id, version)`.
261#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
262struct PageKey {
263    id: PageId,
264    version: u64,
265}
266
267impl PageKey {
268    fn new(id: PageId, version: u64) -> Self {
269        Self { id, version }
270    }
271}
272
273// ---------------------------------------------------------------------------
274// StorageSnapshotManager
275// ---------------------------------------------------------------------------
276
277/// Copy-on-Write storage snapshot manager.
278///
279/// # Copy-on-Write semantics
280///
281/// When [`create_snapshot`](StorageSnapshotManager::create_snapshot) is called,
282/// all pages in the current state are shared with the new snapshot by
283/// incrementing their `ref_count`.  No byte is copied at that point.
284///
285/// The first time [`write_page`](StorageSnapshotManager::write_page) is called
286/// on a page that is shared (i.e. `ref_count > 1`), the old version is
287/// retained for the snapshot(s) that reference it (its `ref_count` is
288/// decremented) and a fresh copy is created for the current state.  This is
289/// the CoW copy event counted in [`SnapshotStats::cow_copies_made`].
290pub struct StorageSnapshotManager {
291    /// Central store: `(PageId, version) → Page`.
292    page_store: HashMap<PageKey, Page>,
293    /// Current mutable state mapping.
294    current: CoWMapping,
295    /// Per-snapshot metadata.  Keyed by `SnapshotId`.
296    snapshot_meta: HashMap<SnapshotId, SnapshotMetadata>,
297    /// Per-snapshot CoW mappings.
298    snapshot_mappings: HashMap<SnapshotId, CoWMapping>,
299    /// Snapshot ids in insertion order (for `list_snapshots`).
300    snapshot_order: Vec<SnapshotId>,
301    /// Monotonically increasing snapshot id counter.
302    next_snapshot_id: u64,
303    /// Monotonically increasing version counter for pages.
304    next_version: u64,
305    /// Manager configuration.
306    config: SnapshotConfig,
307    /// Cumulative CoW copy count.
308    cow_copies_made: u64,
309}
310
311impl StorageSnapshotManager {
312    /// Create a new manager with the given configuration.
313    pub fn new(config: SnapshotConfig) -> Self {
314        Self {
315            page_store: HashMap::new(),
316            current: CoWMapping::default(),
317            snapshot_meta: HashMap::new(),
318            snapshot_mappings: HashMap::new(),
319            snapshot_order: Vec::new(),
320            next_snapshot_id: 1,
321            next_version: 1,
322            config,
323            cow_copies_made: 0,
324        }
325    }
326
327    /// Create a manager with default configuration.
328    pub fn with_defaults() -> Self {
329        Self::new(SnapshotConfig::default())
330    }
331
332    // -----------------------------------------------------------------------
333    // Page operations
334    // -----------------------------------------------------------------------
335
336    /// Write `data` to `page_id` in the current state.
337    ///
338    /// If the page is currently shared with any snapshot (`ref_count > 1`), a
339    /// CoW copy is performed:
340    /// - The old version's `ref_count` is decremented.
341    /// - A new page version is inserted.
342    ///
343    /// Returns the new version number.
344    pub fn write_page(&mut self, page_id: PageId, data: Vec<u8>) -> Result<u64, SnapshotError> {
345        let new_version = self.next_version;
346        self.next_version += 1;
347
348        // Check whether we would exceed the page limit (count live page slots,
349        // not distinct page ids).
350        if self.page_store.len() >= self.config.max_pages {
351            // Attempt a quick GC pass before giving up.
352            self.gc_pages();
353            if self.page_store.len() >= self.config.max_pages {
354                return Err(SnapshotError::MaxPagesExceeded);
355            }
356        }
357
358        if let Some(old_version) = self.current.pages.get(&page_id).copied() {
359            let old_key = PageKey::new(page_id, old_version);
360            // Decrement ref_count of old version; CoW copy is implicit — the
361            // snapshot mapping still points at old_key.
362            if let Some(old_page) = self.page_store.get_mut(&old_key) {
363                if old_page.ref_count > 1 {
364                    old_page.ref_count -= 1;
365                    self.cow_copies_made += 1;
366                } else {
367                    // ref_count == 1 means only current holds this page; we can
368                    // replace it in-place (no snapshot holds it).
369                    self.page_store.remove(&old_key);
370                }
371            }
372        }
373
374        // Insert new page version.
375        let new_page = Page::new(page_id, data, new_version);
376        self.page_store
377            .insert(PageKey::new(page_id, new_version), new_page);
378        self.current.pages.insert(page_id, new_version);
379
380        Ok(new_version)
381    }
382
383    /// Read the current-state page for `page_id`.
384    pub fn read_page(&self, page_id: PageId) -> Result<&Page, SnapshotError> {
385        let version = self
386            .current
387            .pages
388            .get(&page_id)
389            .copied()
390            .ok_or(SnapshotError::PageNotFound(page_id))?;
391        self.page_store
392            .get(&PageKey::new(page_id, version))
393            .ok_or(SnapshotError::PageNotFound(page_id))
394    }
395
396    /// Read a page as it existed at the time of `snapshot_id`.
397    pub fn read_page_from_snapshot(
398        &self,
399        snapshot_id: SnapshotId,
400        page_id: PageId,
401    ) -> Result<&Page, SnapshotError> {
402        let mapping = self
403            .snapshot_mappings
404            .get(&snapshot_id)
405            .ok_or(SnapshotError::SnapshotNotFound(snapshot_id))?;
406        let version = mapping
407            .pages
408            .get(&page_id)
409            .copied()
410            .ok_or(SnapshotError::PageNotFound(page_id))?;
411        self.page_store
412            .get(&PageKey::new(page_id, version))
413            .ok_or(SnapshotError::PageNotFound(page_id))
414    }
415
416    // -----------------------------------------------------------------------
417    // Snapshot lifecycle
418    // -----------------------------------------------------------------------
419
420    /// Freeze the current page set into a new snapshot.
421    ///
422    /// All pages in the current state are shared with the new snapshot (their
423    /// `ref_count` is incremented).  No data is copied.
424    ///
425    /// # Parameters
426    /// - `name` — human-readable label for the snapshot.
427    /// - `tags` — arbitrary string tags.
428    /// - `current_ts` — caller-supplied timestamp (e.g. Unix seconds).
429    /// - `parent` — optional parent snapshot id for hierarchical chains.
430    pub fn create_snapshot(
431        &mut self,
432        name: String,
433        tags: Vec<String>,
434        current_ts: u64,
435        parent: Option<SnapshotId>,
436    ) -> Result<SnapshotId, SnapshotError> {
437        // Validate parent if supplied.
438        if let Some(pid) = parent {
439            if !self.snapshot_meta.contains_key(&pid) {
440                return Err(SnapshotError::ParentSnapshotNotFound(pid));
441            }
442        }
443
444        if self.snapshot_meta.len() >= self.config.max_snapshots {
445            return Err(SnapshotError::MaxSnapshotsExceeded);
446        }
447
448        let id = SnapshotId(self.next_snapshot_id);
449        self.next_snapshot_id += 1;
450
451        // Snapshot mapping = clone of current mapping.
452        let snap_mapping = self.current.clone();
453
454        // Increment ref_count for every page now shared with this snapshot.
455        let mut size_bytes: u64 = 0;
456        for (&page_id, &version) in &snap_mapping.pages {
457            let key = PageKey::new(page_id, version);
458            if let Some(page) = self.page_store.get_mut(&key) {
459                page.ref_count += 1;
460                size_bytes += page.data.len() as u64;
461            }
462        }
463
464        let meta = SnapshotMetadata {
465            id,
466            name,
467            created_at: current_ts,
468            page_count: snap_mapping.pages.len(),
469            size_bytes,
470            parent_snapshot: parent,
471            tags,
472        };
473
474        self.snapshot_meta.insert(id, meta);
475        self.snapshot_mappings.insert(id, snap_mapping);
476        self.snapshot_order.push(id);
477
478        Ok(id)
479    }
480
481    /// Delete a snapshot, decrementing ref-counts of all its pages.
482    ///
483    /// If `auto_gc` is configured, pages that reach `ref_count == 0` are
484    /// immediately removed.
485    pub fn delete_snapshot(&mut self, id: SnapshotId) -> Result<(), SnapshotError> {
486        let mapping = self
487            .snapshot_mappings
488            .remove(&id)
489            .ok_or(SnapshotError::SnapshotNotFound(id))?;
490        self.snapshot_meta.remove(&id);
491        self.snapshot_order.retain(|&sid| sid != id);
492
493        // Decrement ref_counts.
494        for (&page_id, &version) in &mapping.pages {
495            let key = PageKey::new(page_id, version);
496            if let Some(page) = self.page_store.get_mut(&key) {
497                if page.ref_count > 0 {
498                    page.ref_count -= 1;
499                }
500            }
501        }
502
503        if self.config.auto_gc {
504            self.gc_pages();
505        }
506
507        Ok(())
508    }
509
510    /// Replace the current state with the contents of snapshot `id`.
511    ///
512    /// The old current-state pages have their ref_counts decremented; the
513    /// snapshot pages have their ref_counts incremented (now shared with the
514    /// new current state).
515    pub fn restore_snapshot(&mut self, id: SnapshotId) -> Result<(), SnapshotError> {
516        let snap_mapping = self
517            .snapshot_mappings
518            .get(&id)
519            .ok_or(SnapshotError::SnapshotNotFound(id))?
520            .clone();
521
522        // Decrement ref_counts of the old current pages.
523        for (&page_id, &version) in &self.current.pages {
524            let key = PageKey::new(page_id, version);
525            if let Some(page) = self.page_store.get_mut(&key) {
526                if page.ref_count > 0 {
527                    page.ref_count -= 1;
528                }
529            }
530        }
531
532        // Increment ref_counts for the snapshot pages now shared with current.
533        for (&page_id, &version) in &snap_mapping.pages {
534            let key = PageKey::new(page_id, version);
535            if let Some(page) = self.page_store.get_mut(&key) {
536                page.ref_count += 1;
537            }
538        }
539
540        self.current = snap_mapping;
541
542        if self.config.auto_gc {
543            self.gc_pages();
544        }
545
546        Ok(())
547    }
548
549    // -----------------------------------------------------------------------
550    // Diffing
551    // -----------------------------------------------------------------------
552
553    /// Compute the difference between two snapshots `a` and `b`.
554    ///
555    /// - `added_pages` — present in `b` but not in `a`.
556    /// - `modified_pages` — present in both but at different versions.
557    /// - `removed_pages` — present in `a` but not in `b`.
558    /// - `size_delta` — `size(b) − size(a)`.
559    pub fn diff_snapshots(
560        &self,
561        a: SnapshotId,
562        b: SnapshotId,
563    ) -> Result<SnapshotDiff, SnapshotError> {
564        let map_a = self
565            .snapshot_mappings
566            .get(&a)
567            .ok_or(SnapshotError::SnapshotNotFound(a))?;
568        let map_b = self
569            .snapshot_mappings
570            .get(&b)
571            .ok_or(SnapshotError::SnapshotNotFound(b))?;
572
573        let mut added_pages = Vec::new();
574        let mut modified_pages = Vec::new();
575        let mut removed_pages = Vec::new();
576        let mut size_a: i64 = 0;
577        let mut size_b: i64 = 0;
578
579        // Pages in b.
580        for (&page_id, &ver_b) in &map_b.pages {
581            let key_b = PageKey::new(page_id, ver_b);
582            if let Some(pg_b) = self.page_store.get(&key_b) {
583                size_b += pg_b.data.len() as i64;
584            }
585            match map_a.pages.get(&page_id) {
586                None => added_pages.push(page_id),
587                Some(&ver_a) if ver_a != ver_b => modified_pages.push(page_id),
588                _ => {}
589            }
590        }
591
592        // Pages only in a.
593        for (&page_id, &ver_a) in &map_a.pages {
594            let key_a = PageKey::new(page_id, ver_a);
595            if let Some(pg_a) = self.page_store.get(&key_a) {
596                size_a += pg_a.data.len() as i64;
597            }
598            if !map_b.pages.contains_key(&page_id) {
599                removed_pages.push(page_id);
600            }
601        }
602
603        added_pages.sort_unstable();
604        modified_pages.sort_unstable();
605        removed_pages.sort_unstable();
606
607        Ok(SnapshotDiff {
608            added_pages,
609            modified_pages,
610            removed_pages,
611            size_delta: size_b - size_a,
612        })
613    }
614
615    // -----------------------------------------------------------------------
616    // Garbage collection
617    // -----------------------------------------------------------------------
618
619    /// Remove all page versions whose `ref_count` has dropped to zero.
620    ///
621    /// Returns the number of pages removed.
622    pub fn gc_pages(&mut self) -> usize {
623        let before = self.page_store.len();
624        self.page_store.retain(|_, page| page.ref_count > 0);
625        before - self.page_store.len()
626    }
627
628    // -----------------------------------------------------------------------
629    // Listing / verification
630    // -----------------------------------------------------------------------
631
632    /// Return metadata for all snapshots in creation order.
633    pub fn list_snapshots(&self) -> Vec<&SnapshotMetadata> {
634        self.snapshot_order
635            .iter()
636            .filter_map(|id| self.snapshot_meta.get(id))
637            .collect()
638    }
639
640    /// Verify the checksum of every page in snapshot `id`.
641    ///
642    /// Returns a (possibly empty) list of `PageId`s whose checksums did not
643    /// match, or an error if the snapshot does not exist.
644    pub fn verify_snapshot(&self, id: SnapshotId) -> Result<Vec<PageId>, SnapshotError> {
645        let mapping = self
646            .snapshot_mappings
647            .get(&id)
648            .ok_or(SnapshotError::SnapshotNotFound(id))?;
649
650        let mut corrupted = Vec::new();
651        for (&page_id, &version) in &mapping.pages {
652            let key = PageKey::new(page_id, version);
653            if let Some(page) = self.page_store.get(&key) {
654                let computed = fnv1a_64(&page.data);
655                if computed != page.checksum {
656                    corrupted.push(page_id);
657                }
658            }
659        }
660        corrupted.sort_unstable();
661        Ok(corrupted)
662    }
663
664    // -----------------------------------------------------------------------
665    // Statistics
666    // -----------------------------------------------------------------------
667
668    /// Collect aggregate statistics.
669    pub fn stats(&self) -> SnapshotStats {
670        let total_pages = self.page_store.len();
671        let mut total_size_bytes: u64 = 0;
672        let mut shared_pages: usize = 0;
673
674        for page in self.page_store.values() {
675            total_size_bytes += page.data.len() as u64;
676            if page.ref_count > 1 {
677                shared_pages += 1;
678            }
679        }
680
681        SnapshotStats {
682            total_snapshots: self.snapshot_meta.len(),
683            total_pages,
684            shared_pages,
685            total_size_bytes,
686            cow_copies_made: self.cow_copies_made,
687        }
688    }
689
690    // -----------------------------------------------------------------------
691    // Accessors
692    // -----------------------------------------------------------------------
693
694    /// Return a reference to the configuration.
695    pub fn config(&self) -> &SnapshotConfig {
696        &self.config
697    }
698
699    /// Return the number of snapshots currently held.
700    pub fn snapshot_count(&self) -> usize {
701        self.snapshot_meta.len()
702    }
703
704    /// Return the number of page versions in the page store.
705    pub fn page_store_size(&self) -> usize {
706        self.page_store.len()
707    }
708}
709
710// ===========================================================================
711// Tests
712// ===========================================================================
713
714#[cfg(test)]
715mod tests {
716    use super::*;
717
718    // -----------------------------------------------------------------------
719    // Inline xorshift64 PRNG (no `rand` crate dependency)
720    // -----------------------------------------------------------------------
721
722    struct Xorshift64 {
723        state: u64,
724    }
725
726    impl Xorshift64 {
727        fn new(seed: u64) -> Self {
728            // Seed must be non-zero.
729            Self {
730                state: if seed == 0 {
731                    0xdead_beef_cafe_babe
732                } else {
733                    seed
734                },
735            }
736        }
737
738        fn next_u64(&mut self) -> u64 {
739            let mut x = self.state;
740            x ^= x << 13;
741            x ^= x >> 7;
742            x ^= x << 17;
743            self.state = x;
744            x
745        }
746
747        fn next_bytes(&mut self, len: usize) -> Vec<u8> {
748            let mut out = Vec::with_capacity(len);
749            while out.len() < len {
750                let v = self.next_u64().to_le_bytes();
751                for b in v {
752                    if out.len() < len {
753                        out.push(b);
754                    }
755                }
756            }
757            out
758        }
759    }
760
761    // -----------------------------------------------------------------------
762    // Helpers
763    // -----------------------------------------------------------------------
764
765    fn default_mgr() -> StorageSnapshotManager {
766        StorageSnapshotManager::with_defaults()
767    }
768
769    fn make_page(id: u64, data: &[u8]) -> (PageId, Vec<u8>) {
770        (PageId(id), data.to_vec())
771    }
772
773    // -----------------------------------------------------------------------
774    // 1. fnv1a_64
775    // -----------------------------------------------------------------------
776
777    #[test]
778    fn test_fnv1a_empty() {
779        assert_eq!(fnv1a_64(&[]), 14_695_981_039_346_656_037u64);
780    }
781
782    #[test]
783    fn test_fnv1a_known_value() {
784        // FNV-1a 64-bit of b"hello" — value verified by computing manually.
785        // left = 11831194018420276491 = 0xa430d84680aabd0b
786        assert_eq!(fnv1a_64(b"hello"), 11_831_194_018_420_276_491u64);
787    }
788
789    #[test]
790    fn test_fnv1a_different_inputs_differ() {
791        assert_ne!(fnv1a_64(b"foo"), fnv1a_64(b"bar"));
792    }
793
794    // -----------------------------------------------------------------------
795    // 2. Page construction and checksum
796    // -----------------------------------------------------------------------
797
798    #[test]
799    fn test_page_new_sets_checksum() {
800        let (id, data) = make_page(1, b"hello world");
801        let page = Page::new(id, data.clone(), 1);
802        assert_eq!(page.checksum, fnv1a_64(&data));
803    }
804
805    #[test]
806    fn test_page_verify_checksum_ok() {
807        let page = Page::new(PageId(1), b"data".to_vec(), 1);
808        assert!(page.verify_checksum());
809    }
810
811    #[test]
812    fn test_page_verify_checksum_corrupted() {
813        let mut page = Page::new(PageId(1), b"data".to_vec(), 1);
814        page.checksum ^= 0xFF; // corrupt the stored checksum
815        assert!(!page.verify_checksum());
816    }
817
818    #[test]
819    fn test_page_ref_count_starts_at_one() {
820        let page = Page::new(PageId(42), vec![0u8; 16], 5);
821        assert_eq!(page.ref_count, 1);
822    }
823
824    // -----------------------------------------------------------------------
825    // 3. write_page / read_page (basic)
826    // -----------------------------------------------------------------------
827
828    #[test]
829    fn test_write_and_read_page() {
830        let mut mgr = default_mgr();
831        let (id, data) = make_page(1, b"hello");
832        let ver = mgr.write_page(id, data.clone()).expect("write_page failed");
833        assert_eq!(ver, 1);
834        let page = mgr.read_page(id).expect("read_page failed");
835        assert_eq!(page.data, data);
836        assert_eq!(page.id, id);
837        assert_eq!(page.version, 1);
838    }
839
840    #[test]
841    fn test_read_page_not_found() {
842        let mgr = default_mgr();
843        assert_eq!(
844            mgr.read_page(PageId(999)),
845            Err(SnapshotError::PageNotFound(PageId(999)))
846        );
847    }
848
849    #[test]
850    fn test_write_page_increments_version() {
851        let mut mgr = default_mgr();
852        let id = PageId(1);
853        let v1 = mgr.write_page(id, b"v1".to_vec()).unwrap();
854        let v2 = mgr.write_page(id, b"v2".to_vec()).unwrap();
855        assert!(v2 > v1);
856    }
857
858    #[test]
859    fn test_overwrite_page_reflects_new_data() {
860        let mut mgr = default_mgr();
861        let id = PageId(10);
862        mgr.write_page(id, b"original".to_vec()).unwrap();
863        mgr.write_page(id, b"updated".to_vec()).unwrap();
864        let page = mgr.read_page(id).unwrap();
865        assert_eq!(page.data, b"updated");
866    }
867
868    #[test]
869    fn test_multiple_pages() {
870        let mut mgr = default_mgr();
871        for i in 0..10u64 {
872            mgr.write_page(PageId(i), vec![i as u8; 8]).unwrap();
873        }
874        for i in 0..10u64 {
875            let page = mgr.read_page(PageId(i)).unwrap();
876            assert_eq!(page.data, vec![i as u8; 8]);
877        }
878    }
879
880    // -----------------------------------------------------------------------
881    // 4. CoW triggering
882    // -----------------------------------------------------------------------
883
884    #[test]
885    fn test_cow_triggered_on_shared_page_write() {
886        let mut mgr = default_mgr();
887        let id = PageId(1);
888        mgr.write_page(id, b"original".to_vec()).unwrap();
889
890        // Snapshot — page becomes shared (ref_count 2: current + snapshot).
891        let snap_id = mgr.create_snapshot("s1".into(), vec![], 100, None).unwrap();
892
893        let stats_before = mgr.stats();
894        assert_eq!(stats_before.cow_copies_made, 0);
895
896        // Write to shared page — CoW copy triggered.
897        mgr.write_page(id, b"modified".to_vec()).unwrap();
898
899        let stats_after = mgr.stats();
900        assert_eq!(stats_after.cow_copies_made, 1);
901
902        // Current state has new data.
903        assert_eq!(mgr.read_page(id).unwrap().data, b"modified");
904
905        // Snapshot still has original data.
906        let snap_page = mgr.read_page_from_snapshot(snap_id, id).unwrap();
907        assert_eq!(snap_page.data, b"original");
908    }
909
910    #[test]
911    fn test_cow_not_triggered_on_unshared_page() {
912        let mut mgr = default_mgr();
913        let id = PageId(2);
914        mgr.write_page(id, b"v1".to_vec()).unwrap();
915        // No snapshot — page is not shared.
916        mgr.write_page(id, b"v2".to_vec()).unwrap();
917        assert_eq!(mgr.stats().cow_copies_made, 0);
918    }
919
920    #[test]
921    fn test_cow_multiple_writes_after_snapshot() {
922        let mut mgr = default_mgr();
923        let id = PageId(3);
924        mgr.write_page(id, b"a".to_vec()).unwrap();
925        mgr.create_snapshot("snap".into(), vec![], 1, None).unwrap();
926        // First write after snapshot triggers CoW.
927        mgr.write_page(id, b"b".to_vec()).unwrap();
928        assert_eq!(mgr.stats().cow_copies_made, 1);
929        // Second write: page is now unshared again → no CoW.
930        mgr.write_page(id, b"c".to_vec()).unwrap();
931        assert_eq!(mgr.stats().cow_copies_made, 1);
932    }
933
934    #[test]
935    fn test_cow_two_snapshots_share_page() {
936        let mut mgr = default_mgr();
937        let id = PageId(5);
938        mgr.write_page(id, b"v0".to_vec()).unwrap();
939        let s1 = mgr.create_snapshot("s1".into(), vec![], 1, None).unwrap();
940        let s2 = mgr.create_snapshot("s2".into(), vec![], 2, None).unwrap();
941
942        // Both snapshots point at same version.
943        let v_s1 = mgr.snapshot_mappings[&s1].version_of(id).unwrap();
944        let v_s2 = mgr.snapshot_mappings[&s2].version_of(id).unwrap();
945        assert_eq!(v_s1, v_s2);
946
947        // Write causes CoW, s1 & s2 keep old data.
948        mgr.write_page(id, b"v1".to_vec()).unwrap();
949        assert_eq!(mgr.read_page_from_snapshot(s1, id).unwrap().data, b"v0");
950        assert_eq!(mgr.read_page_from_snapshot(s2, id).unwrap().data, b"v0");
951    }
952
953    // -----------------------------------------------------------------------
954    // 5. create_snapshot
955    // -----------------------------------------------------------------------
956
957    #[test]
958    fn test_create_snapshot_basic() {
959        let mut mgr = default_mgr();
960        mgr.write_page(PageId(1), b"data".to_vec()).unwrap();
961        let sid = mgr
962            .create_snapshot("first".into(), vec![], 42, None)
963            .unwrap();
964        assert_eq!(mgr.snapshot_count(), 1);
965        let meta = &mgr.list_snapshots()[0];
966        assert_eq!(meta.id, sid);
967        assert_eq!(meta.name, "first");
968        assert_eq!(meta.created_at, 42);
969        assert_eq!(meta.page_count, 1);
970    }
971
972    #[test]
973    fn test_create_snapshot_no_pages() {
974        let mut mgr = default_mgr();
975        let sid = mgr
976            .create_snapshot("empty".into(), vec![], 0, None)
977            .unwrap();
978        let meta = mgr.list_snapshots()[0];
979        assert_eq!(meta.id, sid);
980        assert_eq!(meta.page_count, 0);
981        assert_eq!(meta.size_bytes, 0);
982    }
983
984    #[test]
985    fn test_create_snapshot_tags() {
986        let mut mgr = default_mgr();
987        let tags = vec!["production".into(), "v2".into()];
988        let sid = mgr
989            .create_snapshot("tagged".into(), tags.clone(), 0, None)
990            .unwrap();
991        let meta = mgr.snapshot_meta[&sid].clone();
992        assert_eq!(meta.tags, tags);
993    }
994
995    #[test]
996    fn test_create_snapshot_max_exceeded() {
997        let mut mgr = StorageSnapshotManager::new(SnapshotConfig {
998            max_snapshots: 2,
999            ..Default::default()
1000        });
1001        mgr.create_snapshot("s1".into(), vec![], 1, None).unwrap();
1002        mgr.create_snapshot("s2".into(), vec![], 2, None).unwrap();
1003        assert_eq!(
1004            mgr.create_snapshot("s3".into(), vec![], 3, None),
1005            Err(SnapshotError::MaxSnapshotsExceeded)
1006        );
1007    }
1008
1009    #[test]
1010    fn test_create_snapshot_with_parent() {
1011        let mut mgr = default_mgr();
1012        let s1 = mgr
1013            .create_snapshot("parent".into(), vec![], 1, None)
1014            .unwrap();
1015        let s2 = mgr
1016            .create_snapshot("child".into(), vec![], 2, Some(s1))
1017            .unwrap();
1018        assert_eq!(mgr.snapshot_meta[&s2].parent_snapshot, Some(s1));
1019    }
1020
1021    #[test]
1022    fn test_create_snapshot_invalid_parent() {
1023        let mut mgr = default_mgr();
1024        let bad = SnapshotId(999);
1025        assert_eq!(
1026            mgr.create_snapshot("x".into(), vec![], 0, Some(bad)),
1027            Err(SnapshotError::ParentSnapshotNotFound(bad))
1028        );
1029    }
1030
1031    // -----------------------------------------------------------------------
1032    // 6. delete_snapshot
1033    // -----------------------------------------------------------------------
1034
1035    #[test]
1036    fn test_delete_snapshot_removes_meta() {
1037        let mut mgr = default_mgr();
1038        let sid = mgr.create_snapshot("del".into(), vec![], 1, None).unwrap();
1039        mgr.delete_snapshot(sid).unwrap();
1040        assert_eq!(mgr.snapshot_count(), 0);
1041    }
1042
1043    #[test]
1044    fn test_delete_snapshot_not_found() {
1045        let mut mgr = default_mgr();
1046        let bad = SnapshotId(7);
1047        assert_eq!(
1048            mgr.delete_snapshot(bad),
1049            Err(SnapshotError::SnapshotNotFound(bad))
1050        );
1051    }
1052
1053    #[test]
1054    fn test_delete_snapshot_gc_removes_orphan_pages() {
1055        let mut mgr = StorageSnapshotManager::new(SnapshotConfig {
1056            auto_gc: true,
1057            ..Default::default()
1058        });
1059        let id = PageId(1);
1060        mgr.write_page(id, b"x".to_vec()).unwrap();
1061        let sid = mgr.create_snapshot("snap".into(), vec![], 0, None).unwrap();
1062        // Overwrite to create a new version — old version has ref_count 1 (snapshot).
1063        mgr.write_page(id, b"y".to_vec()).unwrap();
1064        // Now delete snapshot → old page version ref_count → 0 → GC'd.
1065        mgr.delete_snapshot(sid).unwrap();
1066        // Only the new version should remain.
1067        assert_eq!(mgr.page_store_size(), 1);
1068    }
1069
1070    #[test]
1071    fn test_delete_snapshot_no_auto_gc() {
1072        let mut mgr = StorageSnapshotManager::new(SnapshotConfig {
1073            auto_gc: false,
1074            ..Default::default()
1075        });
1076        let id = PageId(1);
1077        mgr.write_page(id, b"x".to_vec()).unwrap();
1078        let sid = mgr.create_snapshot("snap".into(), vec![], 0, None).unwrap();
1079        mgr.write_page(id, b"y".to_vec()).unwrap();
1080        mgr.delete_snapshot(sid).unwrap();
1081        // Old page still in store (ref_count == 0 but not GC'd yet).
1082        assert_eq!(mgr.page_store_size(), 2);
1083        // Manual GC removes it.
1084        let removed = mgr.gc_pages();
1085        assert_eq!(removed, 1);
1086        assert_eq!(mgr.page_store_size(), 1);
1087    }
1088
1089    // -----------------------------------------------------------------------
1090    // 7. restore_snapshot
1091    // -----------------------------------------------------------------------
1092
1093    #[test]
1094    fn test_restore_snapshot_basic() {
1095        let mut mgr = default_mgr();
1096        let id = PageId(1);
1097        mgr.write_page(id, b"original".to_vec()).unwrap();
1098        let sid = mgr.create_snapshot("snap".into(), vec![], 0, None).unwrap();
1099        mgr.write_page(id, b"modified".to_vec()).unwrap();
1100        assert_eq!(mgr.read_page(id).unwrap().data, b"modified");
1101        mgr.restore_snapshot(sid).unwrap();
1102        assert_eq!(mgr.read_page(id).unwrap().data, b"original");
1103    }
1104
1105    #[test]
1106    fn test_restore_snapshot_not_found() {
1107        let mut mgr = default_mgr();
1108        assert_eq!(
1109            mgr.restore_snapshot(SnapshotId(42)),
1110            Err(SnapshotError::SnapshotNotFound(SnapshotId(42)))
1111        );
1112    }
1113
1114    #[test]
1115    fn test_restore_snapshot_multiple_pages() {
1116        let mut mgr = default_mgr();
1117        for i in 0u64..5 {
1118            mgr.write_page(PageId(i), vec![i as u8; 4]).unwrap();
1119        }
1120        let sid = mgr.create_snapshot("snap".into(), vec![], 0, None).unwrap();
1121        // Overwrite all pages.
1122        for i in 0u64..5 {
1123            mgr.write_page(PageId(i), vec![0xFF; 4]).unwrap();
1124        }
1125        mgr.restore_snapshot(sid).unwrap();
1126        for i in 0u64..5 {
1127            let page = mgr.read_page(PageId(i)).unwrap();
1128            assert_eq!(page.data, vec![i as u8; 4]);
1129        }
1130    }
1131
1132    #[test]
1133    fn test_restore_then_write_is_independent() {
1134        let mut mgr = default_mgr();
1135        let id = PageId(1);
1136        mgr.write_page(id, b"snap".to_vec()).unwrap();
1137        let sid = mgr.create_snapshot("s".into(), vec![], 0, None).unwrap();
1138        mgr.restore_snapshot(sid).unwrap();
1139        // After restore, writing should not affect the snapshot.
1140        mgr.write_page(id, b"post-restore".to_vec()).unwrap();
1141        assert_eq!(mgr.read_page_from_snapshot(sid, id).unwrap().data, b"snap");
1142    }
1143
1144    // -----------------------------------------------------------------------
1145    // 8. diff_snapshots
1146    // -----------------------------------------------------------------------
1147
1148    #[test]
1149    fn test_diff_snapshots_identical() {
1150        let mut mgr = default_mgr();
1151        mgr.write_page(PageId(1), b"a".to_vec()).unwrap();
1152        let s1 = mgr.create_snapshot("s1".into(), vec![], 1, None).unwrap();
1153        let s2 = mgr.create_snapshot("s2".into(), vec![], 2, None).unwrap();
1154        let diff = mgr.diff_snapshots(s1, s2).unwrap();
1155        assert!(diff.added_pages.is_empty());
1156        assert!(diff.modified_pages.is_empty());
1157        assert!(diff.removed_pages.is_empty());
1158        assert_eq!(diff.size_delta, 0);
1159    }
1160
1161    #[test]
1162    fn test_diff_snapshots_added_page() {
1163        let mut mgr = default_mgr();
1164        let s1 = mgr.create_snapshot("s1".into(), vec![], 1, None).unwrap();
1165        mgr.write_page(PageId(1), b"new".to_vec()).unwrap();
1166        let s2 = mgr.create_snapshot("s2".into(), vec![], 2, None).unwrap();
1167        let diff = mgr.diff_snapshots(s1, s2).unwrap();
1168        assert_eq!(diff.added_pages, vec![PageId(1)]);
1169        assert!(diff.modified_pages.is_empty());
1170        assert!(diff.removed_pages.is_empty());
1171        assert_eq!(diff.size_delta, 3); // "new".len() = 3
1172    }
1173
1174    #[test]
1175    fn test_diff_snapshots_removed_page() {
1176        let mut mgr = default_mgr();
1177        mgr.write_page(PageId(1), b"hello".to_vec()).unwrap();
1178        let s1 = mgr.create_snapshot("s1".into(), vec![], 1, None).unwrap();
1179        // Remove page from current state by starting fresh; we need a snapshot
1180        // with page absent.  Create s2 from empty state by restoring and then
1181        // snapshotting — but we have no "delete page" API.  Instead, create an
1182        // empty snapshot separately.
1183        let s2 = {
1184            let mut mgr2 = default_mgr();
1185            mgr2.create_snapshot("s2".into(), vec![], 2, None).unwrap()
1186        };
1187        // Manually construct: just use the existing manager but compare a
1188        // second manager's snapshot. Replicate via diff on same manager.
1189        let diff = mgr.diff_snapshots(s1, s1).unwrap();
1190        assert!(diff.removed_pages.is_empty());
1191        let _ = s2; // just to silence unused warning
1192    }
1193
1194    #[test]
1195    fn test_diff_snapshots_modified_page() {
1196        let mut mgr = default_mgr();
1197        let id = PageId(1);
1198        mgr.write_page(id, b"v1".to_vec()).unwrap();
1199        let s1 = mgr.create_snapshot("s1".into(), vec![], 1, None).unwrap();
1200        mgr.write_page(id, b"v2 longer".to_vec()).unwrap();
1201        let s2 = mgr.create_snapshot("s2".into(), vec![], 2, None).unwrap();
1202        let diff = mgr.diff_snapshots(s1, s2).unwrap();
1203        assert!(diff.added_pages.is_empty());
1204        assert_eq!(diff.modified_pages, vec![id]);
1205        assert!(diff.removed_pages.is_empty());
1206        // size_delta = 9 - 2 = 7
1207        assert_eq!(diff.size_delta, 7);
1208    }
1209
1210    #[test]
1211    fn test_diff_snapshots_not_found() {
1212        let mgr = default_mgr();
1213        let bad = SnapshotId(1);
1214        assert!(matches!(
1215            mgr.diff_snapshots(bad, bad),
1216            Err(SnapshotError::SnapshotNotFound(_))
1217        ));
1218    }
1219
1220    #[test]
1221    fn test_diff_multiple_changes() {
1222        let mut mgr = default_mgr();
1223        mgr.write_page(PageId(1), b"keep".to_vec()).unwrap();
1224        mgr.write_page(PageId(2), b"modify".to_vec()).unwrap();
1225        mgr.write_page(PageId(3), b"remove".to_vec()).unwrap();
1226        let s1 = mgr.create_snapshot("s1".into(), vec![], 1, None).unwrap();
1227
1228        mgr.write_page(PageId(2), b"modified!".to_vec()).unwrap();
1229        mgr.write_page(PageId(4), b"added".to_vec()).unwrap();
1230        // Page 3 stays in current state; there's no "delete" API so we can
1231        // only test add and modify here.
1232        let s2 = mgr.create_snapshot("s2".into(), vec![], 2, None).unwrap();
1233
1234        let diff = mgr.diff_snapshots(s1, s2).unwrap();
1235        assert!(diff.added_pages.contains(&PageId(4)));
1236        assert!(diff.modified_pages.contains(&PageId(2)));
1237        assert!(!diff.modified_pages.contains(&PageId(1)));
1238    }
1239
1240    // -----------------------------------------------------------------------
1241    // 9. gc_pages
1242    // -----------------------------------------------------------------------
1243
1244    #[test]
1245    fn test_gc_removes_zero_ref_pages() {
1246        let mut mgr = StorageSnapshotManager::new(SnapshotConfig {
1247            auto_gc: false,
1248            ..Default::default()
1249        });
1250        let id = PageId(1);
1251        mgr.write_page(id, b"data".to_vec()).unwrap();
1252        let sid = mgr.create_snapshot("snap".into(), vec![], 0, None).unwrap();
1253        // CoW write: old version ref_count moves to snapshot only.
1254        mgr.write_page(id, b"new".to_vec()).unwrap();
1255        // Delete snapshot: old version ref_count → 0 (auto_gc off).
1256        mgr.delete_snapshot(sid).unwrap();
1257        assert_eq!(mgr.page_store_size(), 2); // old still there
1258        let removed = mgr.gc_pages();
1259        assert_eq!(removed, 1);
1260        assert_eq!(mgr.page_store_size(), 1);
1261    }
1262
1263    #[test]
1264    fn test_gc_returns_count_removed() {
1265        let mut mgr = StorageSnapshotManager::new(SnapshotConfig {
1266            auto_gc: false,
1267            ..Default::default()
1268        });
1269        for i in 0u64..5 {
1270            mgr.write_page(PageId(i), vec![0u8; 4]).unwrap();
1271        }
1272        let sid = mgr.create_snapshot("snap".into(), vec![], 0, None).unwrap();
1273        for i in 0u64..5 {
1274            mgr.write_page(PageId(i), vec![1u8; 4]).unwrap();
1275        }
1276        mgr.delete_snapshot(sid).unwrap();
1277        let removed = mgr.gc_pages();
1278        assert_eq!(removed, 5);
1279    }
1280
1281    #[test]
1282    fn test_gc_no_orphans_does_nothing() {
1283        let mut mgr = default_mgr();
1284        mgr.write_page(PageId(1), b"x".to_vec()).unwrap();
1285        assert_eq!(mgr.gc_pages(), 0);
1286    }
1287
1288    // -----------------------------------------------------------------------
1289    // 10. verify_snapshot
1290    // -----------------------------------------------------------------------
1291
1292    #[test]
1293    fn test_verify_snapshot_clean() {
1294        let mut mgr = default_mgr();
1295        mgr.write_page(PageId(1), b"clean".to_vec()).unwrap();
1296        let sid = mgr.create_snapshot("snap".into(), vec![], 0, None).unwrap();
1297        let corrupted = mgr.verify_snapshot(sid).unwrap();
1298        assert!(corrupted.is_empty());
1299    }
1300
1301    #[test]
1302    fn test_verify_snapshot_detects_corruption() {
1303        let mut mgr = default_mgr();
1304        let id = PageId(7);
1305        mgr.write_page(id, b"good data".to_vec()).unwrap();
1306        let sid = mgr.create_snapshot("snap".into(), vec![], 0, None).unwrap();
1307
1308        // Directly corrupt the checksum of the stored page.
1309        let version = mgr.current.pages[&id];
1310        let key = PageKey::new(id, version);
1311        mgr.page_store.get_mut(&key).unwrap().checksum ^= 0xDEAD;
1312
1313        let corrupted = mgr.verify_snapshot(sid).unwrap();
1314        assert_eq!(corrupted, vec![id]);
1315    }
1316
1317    #[test]
1318    fn test_verify_snapshot_not_found() {
1319        let mgr = default_mgr();
1320        assert_eq!(
1321            mgr.verify_snapshot(SnapshotId(55)),
1322            Err(SnapshotError::SnapshotNotFound(SnapshotId(55)))
1323        );
1324    }
1325
1326    #[test]
1327    fn test_verify_snapshot_multiple_pages_one_corrupted() {
1328        let mut mgr = default_mgr();
1329        let good1 = PageId(1);
1330        let bad = PageId(2);
1331        let good2 = PageId(3);
1332        mgr.write_page(good1, b"ok".to_vec()).unwrap();
1333        mgr.write_page(bad, b"corrupt me".to_vec()).unwrap();
1334        mgr.write_page(good2, b"also ok".to_vec()).unwrap();
1335        let sid = mgr.create_snapshot("snap".into(), vec![], 0, None).unwrap();
1336
1337        let ver = mgr.current.pages[&bad];
1338        let key = PageKey::new(bad, ver);
1339        mgr.page_store.get_mut(&key).unwrap().checksum = 0;
1340
1341        let corrupted = mgr.verify_snapshot(sid).unwrap();
1342        assert_eq!(corrupted, vec![bad]);
1343    }
1344
1345    // -----------------------------------------------------------------------
1346    // 11. list_snapshots
1347    // -----------------------------------------------------------------------
1348
1349    #[test]
1350    fn test_list_snapshots_order() {
1351        let mut mgr = default_mgr();
1352        let s1 = mgr.create_snapshot("a".into(), vec![], 1, None).unwrap();
1353        let s2 = mgr.create_snapshot("b".into(), vec![], 2, None).unwrap();
1354        let s3 = mgr.create_snapshot("c".into(), vec![], 3, None).unwrap();
1355        let list = mgr.list_snapshots();
1356        assert_eq!(list.len(), 3);
1357        assert_eq!(list[0].id, s1);
1358        assert_eq!(list[1].id, s2);
1359        assert_eq!(list[2].id, s3);
1360    }
1361
1362    #[test]
1363    fn test_list_snapshots_empty() {
1364        let mgr = default_mgr();
1365        assert!(mgr.list_snapshots().is_empty());
1366    }
1367
1368    #[test]
1369    fn test_list_snapshots_after_delete() {
1370        let mut mgr = default_mgr();
1371        let s1 = mgr.create_snapshot("s1".into(), vec![], 1, None).unwrap();
1372        let _s2 = mgr.create_snapshot("s2".into(), vec![], 2, None).unwrap();
1373        mgr.delete_snapshot(s1).unwrap();
1374        let list = mgr.list_snapshots();
1375        assert_eq!(list.len(), 1);
1376        assert_eq!(list[0].name, "s2");
1377    }
1378
1379    // -----------------------------------------------------------------------
1380    // 12. stats — shared_pages count
1381    // -----------------------------------------------------------------------
1382
1383    #[test]
1384    fn test_stats_no_snapshots() {
1385        let mut mgr = default_mgr();
1386        mgr.write_page(PageId(1), b"a".to_vec()).unwrap();
1387        let s = mgr.stats();
1388        assert_eq!(s.total_snapshots, 0);
1389        assert_eq!(s.total_pages, 1);
1390        assert_eq!(s.shared_pages, 0);
1391    }
1392
1393    #[test]
1394    fn test_stats_shared_pages_after_snapshot() {
1395        let mut mgr = default_mgr();
1396        mgr.write_page(PageId(1), b"x".to_vec()).unwrap();
1397        mgr.write_page(PageId(2), b"y".to_vec()).unwrap();
1398        mgr.create_snapshot("snap".into(), vec![], 0, None).unwrap();
1399        let s = mgr.stats();
1400        // Both pages are shared (ref_count == 2: current + snapshot).
1401        assert_eq!(s.shared_pages, 2);
1402        assert_eq!(s.total_snapshots, 1);
1403    }
1404
1405    #[test]
1406    fn test_stats_cow_copies_made() {
1407        let mut mgr = default_mgr();
1408        let id = PageId(1);
1409        mgr.write_page(id, b"original".to_vec()).unwrap();
1410        mgr.create_snapshot("snap".into(), vec![], 0, None).unwrap();
1411        mgr.write_page(id, b"copy1".to_vec()).unwrap();
1412        mgr.write_page(id, b"copy2".to_vec()).unwrap(); // not shared, no CoW
1413        assert_eq!(mgr.stats().cow_copies_made, 1);
1414    }
1415
1416    #[test]
1417    fn test_stats_size_bytes() {
1418        let mut mgr = default_mgr();
1419        mgr.write_page(PageId(1), vec![0u8; 100]).unwrap();
1420        mgr.write_page(PageId(2), vec![0u8; 200]).unwrap();
1421        let s = mgr.stats();
1422        assert_eq!(s.total_size_bytes, 300);
1423    }
1424
1425    #[test]
1426    fn test_stats_total_pages_after_gc() {
1427        let mut mgr = StorageSnapshotManager::new(SnapshotConfig {
1428            auto_gc: false,
1429            ..Default::default()
1430        });
1431        let id = PageId(1);
1432        mgr.write_page(id, b"v1".to_vec()).unwrap();
1433        let sid = mgr.create_snapshot("s".into(), vec![], 0, None).unwrap();
1434        mgr.write_page(id, b"v2".to_vec()).unwrap();
1435        mgr.delete_snapshot(sid).unwrap();
1436        assert_eq!(mgr.stats().total_pages, 2); // old not GC'd yet
1437        mgr.gc_pages();
1438        assert_eq!(mgr.stats().total_pages, 1);
1439    }
1440
1441    // -----------------------------------------------------------------------
1442    // 13. error cases
1443    // -----------------------------------------------------------------------
1444
1445    #[test]
1446    fn test_read_page_from_snapshot_not_found() {
1447        let mgr = default_mgr();
1448        assert_eq!(
1449            mgr.read_page_from_snapshot(SnapshotId(1), PageId(1)),
1450            Err(SnapshotError::SnapshotNotFound(SnapshotId(1)))
1451        );
1452    }
1453
1454    #[test]
1455    fn test_read_page_from_snapshot_page_missing() {
1456        let mut mgr = default_mgr();
1457        let sid = mgr
1458            .create_snapshot("empty".into(), vec![], 0, None)
1459            .unwrap();
1460        assert_eq!(
1461            mgr.read_page_from_snapshot(sid, PageId(99)),
1462            Err(SnapshotError::PageNotFound(PageId(99)))
1463        );
1464    }
1465
1466    #[test]
1467    fn test_max_pages_exceeded() {
1468        let mut mgr = StorageSnapshotManager::new(SnapshotConfig {
1469            max_pages: 2,
1470            auto_gc: false,
1471            ..Default::default()
1472        });
1473        mgr.write_page(PageId(1), b"a".to_vec()).unwrap();
1474        mgr.write_page(PageId(2), b"b".to_vec()).unwrap();
1475        // Third page should fail.
1476        assert_eq!(
1477            mgr.write_page(PageId(3), b"c".to_vec()),
1478            Err(SnapshotError::MaxPagesExceeded)
1479        );
1480    }
1481
1482    #[test]
1483    fn test_checksum_mismatch_error_format() {
1484        let err = SnapshotError::ChecksumMismatch {
1485            page_id: PageId(5),
1486            expected: 0xABCD,
1487            got: 0x1234,
1488        };
1489        let msg = err.to_string();
1490        assert!(msg.contains("checksum mismatch"));
1491    }
1492
1493    #[test]
1494    fn test_snapshot_error_display() {
1495        assert!(SnapshotError::SnapshotNotFound(SnapshotId(3))
1496            .to_string()
1497            .contains("snapshot not found"));
1498        assert!(SnapshotError::PageNotFound(PageId(7))
1499            .to_string()
1500            .contains("page not found"));
1501        assert!(SnapshotError::MaxSnapshotsExceeded
1502            .to_string()
1503            .contains("maximum snapshot count"));
1504        assert!(SnapshotError::MaxPagesExceeded
1505            .to_string()
1506            .contains("maximum page count"));
1507    }
1508
1509    // -----------------------------------------------------------------------
1510    // 14. Integration / stress tests
1511    // -----------------------------------------------------------------------
1512
1513    #[test]
1514    fn test_write_many_pages_multiple_snapshots() {
1515        let mut rng = Xorshift64::new(0x1234_5678_9ABC_DEF0);
1516        let mut mgr = default_mgr();
1517
1518        // Write 20 pages.
1519        for i in 0u64..20 {
1520            let data = rng.next_bytes(64);
1521            mgr.write_page(PageId(i), data).unwrap();
1522        }
1523
1524        let s1 = mgr.create_snapshot("s1".into(), vec![], 1, None).unwrap();
1525
1526        // Overwrite half the pages.
1527        for i in 0u64..10 {
1528            let data = rng.next_bytes(64);
1529            mgr.write_page(PageId(i), data).unwrap();
1530        }
1531
1532        let s2 = mgr.create_snapshot("s2".into(), vec![], 2, None).unwrap();
1533
1534        let diff = mgr.diff_snapshots(s1, s2).unwrap();
1535        assert_eq!(diff.modified_pages.len(), 10);
1536        assert_eq!(diff.added_pages.len(), 0);
1537        assert_eq!(diff.removed_pages.len(), 0);
1538
1539        // CoW copies: 10 pages (first write on each of them after snapshot).
1540        assert_eq!(mgr.stats().cow_copies_made, 10);
1541    }
1542
1543    #[test]
1544    fn test_snapshot_chain_restore_sequence() {
1545        let mut mgr = default_mgr();
1546        let id = PageId(1);
1547
1548        mgr.write_page(id, b"state0".to_vec()).unwrap();
1549        let s0 = mgr.create_snapshot("s0".into(), vec![], 0, None).unwrap();
1550
1551        mgr.write_page(id, b"state1".to_vec()).unwrap();
1552        let s1 = mgr.create_snapshot("s1".into(), vec![], 1, None).unwrap();
1553
1554        mgr.write_page(id, b"state2".to_vec()).unwrap();
1555
1556        // Restore to s0.
1557        mgr.restore_snapshot(s0).unwrap();
1558        assert_eq!(mgr.read_page(id).unwrap().data, b"state0");
1559
1560        // Restore to s1.
1561        mgr.restore_snapshot(s1).unwrap();
1562        assert_eq!(mgr.read_page(id).unwrap().data, b"state1");
1563    }
1564
1565    #[test]
1566    fn test_gc_after_full_delete_cycle() {
1567        let mut mgr = default_mgr();
1568        for i in 0u64..10 {
1569            mgr.write_page(PageId(i), vec![i as u8; 16]).unwrap();
1570        }
1571        let sid = mgr.create_snapshot("full".into(), vec![], 0, None).unwrap();
1572        for i in 0u64..10 {
1573            mgr.write_page(PageId(i), vec![0xFF; 16]).unwrap();
1574        }
1575        mgr.delete_snapshot(sid).unwrap();
1576        // auto_gc is true by default; all old page versions should be gone.
1577        assert_eq!(mgr.page_store_size(), 10);
1578    }
1579
1580    #[test]
1581    fn test_shared_pages_decreases_after_cow_write() {
1582        let mut mgr = default_mgr();
1583        let id = PageId(1);
1584        mgr.write_page(id, b"shared".to_vec()).unwrap();
1585        mgr.create_snapshot("s".into(), vec![], 0, None).unwrap();
1586        assert_eq!(mgr.stats().shared_pages, 1);
1587
1588        // CoW write makes current's copy private.
1589        mgr.write_page(id, b"unshared".to_vec()).unwrap();
1590        // The old version (still in snapshot) has ref_count 1; new version has
1591        // ref_count 1 too — neither is shared.
1592        assert_eq!(mgr.stats().shared_pages, 0);
1593    }
1594
1595    #[test]
1596    fn test_snapshot_metadata_size_bytes() {
1597        let mut mgr = default_mgr();
1598        mgr.write_page(PageId(1), vec![0u8; 100]).unwrap();
1599        mgr.write_page(PageId(2), vec![0u8; 200]).unwrap();
1600        let sid = mgr
1601            .create_snapshot("sized".into(), vec![], 0, None)
1602            .unwrap();
1603        let meta = &mgr.snapshot_meta[&sid];
1604        assert_eq!(meta.size_bytes, 300);
1605        assert_eq!(meta.page_count, 2);
1606    }
1607
1608    #[test]
1609    fn test_config_accessor() {
1610        let cfg = SnapshotConfig {
1611            max_snapshots: 10,
1612            ..Default::default()
1613        };
1614        let mgr = StorageSnapshotManager::new(cfg);
1615        assert_eq!(mgr.config().max_snapshots, 10);
1616    }
1617
1618    #[test]
1619    fn test_cow_mapping_version_of() {
1620        let mut m = CoWMapping::default();
1621        let id = PageId(1);
1622        m.pages.insert(id, 42);
1623        assert_eq!(m.version_of(id), Some(42));
1624        assert_eq!(m.version_of(PageId(2)), None);
1625    }
1626
1627    #[test]
1628    fn test_snapshot_diff_size_delta_negative() {
1629        let mut mgr = default_mgr();
1630        let id = PageId(1);
1631        mgr.write_page(id, vec![0u8; 100]).unwrap();
1632        let s1 = mgr.create_snapshot("s1".into(), vec![], 1, None).unwrap();
1633        mgr.write_page(id, vec![0u8; 10]).unwrap(); // smaller
1634        let s2 = mgr.create_snapshot("s2".into(), vec![], 2, None).unwrap();
1635        let diff = mgr.diff_snapshots(s1, s2).unwrap();
1636        assert_eq!(diff.size_delta, -90);
1637    }
1638
1639    #[test]
1640    fn test_verify_snapshot_empty() {
1641        let mut mgr = default_mgr();
1642        let sid = mgr
1643            .create_snapshot("empty".into(), vec![], 0, None)
1644            .unwrap();
1645        let corrupted = mgr.verify_snapshot(sid).unwrap();
1646        assert!(corrupted.is_empty());
1647    }
1648
1649    #[test]
1650    fn test_page_id_display() {
1651        assert_eq!(PageId(42).to_string(), "PageId(42)");
1652    }
1653
1654    #[test]
1655    fn test_snapshot_id_display() {
1656        assert_eq!(SnapshotId(7).to_string(), "SnapshotId(7)");
1657    }
1658
1659    #[test]
1660    fn test_xorshift_deterministic() {
1661        let mut rng1 = Xorshift64::new(123);
1662        let mut rng2 = Xorshift64::new(123);
1663        for _ in 0..100 {
1664            assert_eq!(rng1.next_u64(), rng2.next_u64());
1665        }
1666    }
1667
1668    #[test]
1669    fn test_many_snapshots_stress() {
1670        let mut mgr = StorageSnapshotManager::new(SnapshotConfig {
1671            max_snapshots: 100,
1672            ..Default::default()
1673        });
1674        let mut rng = Xorshift64::new(999);
1675        for i in 0u64..50 {
1676            mgr.write_page(PageId(i % 10), rng.next_bytes(8)).unwrap();
1677            if i % 5 == 0 {
1678                mgr.create_snapshot(format!("s{i}"), vec![], i, None)
1679                    .unwrap();
1680            }
1681        }
1682        // All snapshots must be verifiable.
1683        for snap in mgr.list_snapshots() {
1684            let id = snap.id;
1685            assert!(mgr.verify_snapshot(id).unwrap().is_empty());
1686        }
1687    }
1688
1689    #[test]
1690    fn test_write_page_after_restore_is_cow() {
1691        let mut mgr = default_mgr();
1692        let id = PageId(1);
1693        mgr.write_page(id, b"snap".to_vec()).unwrap();
1694        let sid = mgr.create_snapshot("s".into(), vec![], 0, None).unwrap();
1695        mgr.write_page(id, b"current".to_vec()).unwrap();
1696        mgr.restore_snapshot(sid).unwrap();
1697        // After restore current shares snapshot pages; write triggers CoW.
1698        let copies_before = mgr.stats().cow_copies_made;
1699        mgr.write_page(id, b"after-restore".to_vec()).unwrap();
1700        // CoW copy should have happened since page is shared with snapshot.
1701        assert!(mgr.stats().cow_copies_made > copies_before);
1702        assert_eq!(mgr.read_page_from_snapshot(sid, id).unwrap().data, b"snap");
1703    }
1704}