1use std::collections::HashMap;
26use thiserror::Error;
27
28pub 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#[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#[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#[derive(Clone, Debug, PartialEq, Eq)]
72pub struct Page {
73 pub id: PageId,
75 pub data: Vec<u8>,
77 pub version: u64,
79 pub ref_count: u32,
82 pub checksum: u64,
84}
85
86impl Page {
87 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 pub fn verify_checksum(&self) -> bool {
101 fnv1a_64(&self.data) == self.checksum
102 }
103}
104
105#[derive(Clone, Debug, PartialEq, Eq)]
111pub struct SnapshotMetadata {
112 pub id: SnapshotId,
114 pub name: String,
116 pub created_at: u64,
118 pub page_count: usize,
120 pub size_bytes: u64,
122 pub parent_snapshot: Option<SnapshotId>,
124 pub tags: Vec<String>,
126}
127
128#[derive(Clone, Debug, Default)]
136pub struct CoWMapping {
137 pub pages: HashMap<PageId, u64>,
139}
140
141impl CoWMapping {
142 pub fn version_of(&self, id: PageId) -> Option<u64> {
144 self.pages.get(&id).copied()
145 }
146}
147
148#[derive(Clone, Debug, Default)]
154pub struct SnapshotDiff {
155 pub added_pages: Vec<PageId>,
157 pub modified_pages: Vec<PageId>,
159 pub removed_pages: Vec<PageId>,
161 pub size_delta: i64,
163}
164
165#[derive(Clone, Debug)]
171pub struct SnapshotConfig {
172 pub max_snapshots: usize,
174 pub max_pages: usize,
177 pub enable_compression: bool,
179 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#[derive(Clone, Debug, Default, PartialEq, Eq)]
201pub struct SnapshotStats {
202 pub total_snapshots: usize,
204 pub total_pages: usize,
207 pub shared_pages: usize,
209 pub total_size_bytes: u64,
211 pub cow_copies_made: u64,
213}
214
215#[derive(Debug, Error, PartialEq, Eq)]
221pub enum SnapshotError {
222 #[error("snapshot not found: {0}")]
224 SnapshotNotFound(SnapshotId),
225
226 #[error("page not found: {0}")]
228 PageNotFound(PageId),
229
230 #[error("maximum snapshot count exceeded")]
233 MaxSnapshotsExceeded,
234
235 #[error("maximum page count exceeded")]
238 MaxPagesExceeded,
239
240 #[error("checksum mismatch for page {page_id}: expected {expected:#018x}, got {got:#018x}")]
242 ChecksumMismatch {
243 page_id: PageId,
245 expected: u64,
247 got: u64,
249 },
250
251 #[error("parent snapshot not found: {0}")]
253 ParentSnapshotNotFound(SnapshotId),
254}
255
256#[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
273pub struct StorageSnapshotManager {
291 page_store: HashMap<PageKey, Page>,
293 current: CoWMapping,
295 snapshot_meta: HashMap<SnapshotId, SnapshotMetadata>,
297 snapshot_mappings: HashMap<SnapshotId, CoWMapping>,
299 snapshot_order: Vec<SnapshotId>,
301 next_snapshot_id: u64,
303 next_version: u64,
305 config: SnapshotConfig,
307 cow_copies_made: u64,
309}
310
311impl StorageSnapshotManager {
312 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 pub fn with_defaults() -> Self {
329 Self::new(SnapshotConfig::default())
330 }
331
332 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 if self.page_store.len() >= self.config.max_pages {
351 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 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 self.page_store.remove(&old_key);
370 }
371 }
372 }
373
374 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 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 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 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 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 let snap_mapping = self.current.clone();
453
454 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 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 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 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 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 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 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 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 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 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 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 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 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 pub fn config(&self) -> &SnapshotConfig {
696 &self.config
697 }
698
699 pub fn snapshot_count(&self) -> usize {
701 self.snapshot_meta.len()
702 }
703
704 pub fn page_store_size(&self) -> usize {
706 self.page_store.len()
707 }
708}
709
710#[cfg(test)]
715mod tests {
716 use super::*;
717
718 struct Xorshift64 {
723 state: u64,
724 }
725
726 impl Xorshift64 {
727 fn new(seed: u64) -> Self {
728 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 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 #[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 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 #[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; 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 #[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 #[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 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 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 assert_eq!(mgr.read_page(id).unwrap().data, b"modified");
904
905 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 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 mgr.write_page(id, b"b".to_vec()).unwrap();
928 assert_eq!(mgr.stats().cow_copies_made, 1);
929 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 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 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 #[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 #[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 mgr.write_page(id, b"y".to_vec()).unwrap();
1064 mgr.delete_snapshot(sid).unwrap();
1066 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 assert_eq!(mgr.page_store_size(), 2);
1083 let removed = mgr.gc_pages();
1085 assert_eq!(removed, 1);
1086 assert_eq!(mgr.page_store_size(), 1);
1087 }
1088
1089 #[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 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 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 #[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); }
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 let s2 = {
1184 let mut mgr2 = default_mgr();
1185 mgr2.create_snapshot("s2".into(), vec![], 2, None).unwrap()
1186 };
1187 let diff = mgr.diff_snapshots(s1, s1).unwrap();
1190 assert!(diff.removed_pages.is_empty());
1191 let _ = s2; }
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 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 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 #[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 mgr.write_page(id, b"new".to_vec()).unwrap();
1255 mgr.delete_snapshot(sid).unwrap();
1257 assert_eq!(mgr.page_store_size(), 2); 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 #[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 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 #[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 #[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 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(); 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); mgr.gc_pages();
1438 assert_eq!(mgr.stats().total_pages, 1);
1439 }
1440
1441 #[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 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 #[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 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 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 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 mgr.restore_snapshot(s0).unwrap();
1558 assert_eq!(mgr.read_page(id).unwrap().data, b"state0");
1559
1560 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 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 mgr.write_page(id, b"unshared".to_vec()).unwrap();
1590 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(); 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 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 let copies_before = mgr.stats().cow_copies_made;
1699 mgr.write_page(id, b"after-restore".to_vec()).unwrap();
1700 assert!(mgr.stats().cow_copies_made > copies_before);
1702 assert_eq!(mgr.read_page_from_snapshot(sid, id).unwrap().data, b"snap");
1703 }
1704}