Nothing to see here, move along meow
1use lancer_lancerfs::file;
2use lancer_lancerfs::test_helpers::setup_fs;
3
4#[test]
5fn snapshot_preserves_directory_structure() {
6 let mut fs = setup_fs(4096);
7 let root = fs.root_block();
8 let (_inode, block_a) = fs.create_file(root, b"file_a");
9
10 let data_a: Vec<u8> = (0..4096u16).map(|i| (i % 199) as u8).collect();
11 fs.write_file(block_a, 0, &data_a);
12 fs.commit();
13
14 fs.create_snapshot(b"snap1");
15
16 fs.create_file(root, b"file_b");
17 fs.commit();
18
19 assert!(fs.lookup_file(root, b"file_a").is_some());
20 assert!(fs.lookup_file(root, b"file_b").is_some());
21
22 let snap_ref = fs.snapshot_lookup(b"snap1").expect("snapshot must exist");
23
24 let snap_root_entry = lancer_lancerfs::btree::btree_lookup(
25 &mut fs.pool,
26 &mut fs.cache,
27 &mut fs.bio,
28 &snap_ref.root_blockref,
29 fs.state.root_object_id,
30 snap_ref.txn_filter(),
31 )
32 .unwrap()
33 .expect("snapshot must contain root inode");
34
35 let snap_root_block = lancer_lancerfs::blockref_block_num(&snap_root_entry);
36 let snap_root_inode = file::read_inode(&mut fs.cache, &mut fs.bio, snap_root_block).unwrap();
37
38 let snap_file_a = lancer_lancerfs::dir::dir_lookup(
39 &mut fs.pool,
40 &mut fs.cache,
41 &mut fs.bio,
42 &snap_root_inode,
43 b"file_a",
44 snap_ref.txn_filter(),
45 )
46 .unwrap();
47 assert!(
48 snap_file_a.is_some(),
49 "snapshot must contain file_a (created before snapshot)"
50 );
51
52 let snap_file_b = lancer_lancerfs::dir::dir_lookup(
53 &mut fs.pool,
54 &mut fs.cache,
55 &mut fs.bio,
56 &snap_root_inode,
57 b"file_b",
58 snap_ref.txn_filter(),
59 )
60 .unwrap();
61 assert!(
62 snap_file_b.is_none(),
63 "snapshot must not contain file_b (created after snapshot)"
64 );
65
66 let snap_entry = snap_file_a.unwrap();
67 let snap_file_block = lancer_lancerfs::blockref_block_num(&snap_entry);
68 let snap_file_inode = file::read_inode(&mut fs.cache, &mut fs.bio, snap_file_block).unwrap();
69
70 let mut snap_buf = vec![0u8; 4096];
71 let n = file::file_read(
72 &mut fs.pool,
73 &mut fs.cache,
74 &mut fs.bio,
75 &snap_file_inode,
76 0,
77 &mut snap_buf,
78 )
79 .unwrap();
80 snap_buf.truncate(n);
81
82 assert_eq!(
83 snap_buf, data_a,
84 "unmodified file in snapshot must return original data"
85 );
86}
87
88#[test]
89fn snapshot_lookup_returns_none_for_missing() {
90 let mut fs = setup_fs(4096);
91 let result = fs.snapshot_lookup(b"nonexistent");
92 assert!(result.is_none());
93}
94
95#[test]
96fn delete_snapshot_removes_it() {
97 let mut fs = setup_fs(4096);
98 let root = fs.root_block();
99 let (_inode, block) = fs.create_file(root, b"file_for_snap");
100
101 let data = vec![0xCCu8; 100];
102 fs.write_file(block, 0, &data);
103 fs.commit();
104
105 fs.create_snapshot(b"to_delete");
106 assert!(fs.snapshot_lookup(b"to_delete").is_some());
107
108 fs.delete_snapshot(b"to_delete");
109 assert!(fs.snapshot_lookup(b"to_delete").is_none());
110}
111
112#[test]
113fn multiple_snapshots_coexist() {
114 let mut fs = setup_fs(4096);
115 let root = fs.root_block();
116 let (_inode, block) = fs.create_file(root, b"multi_snap");
117
118 let data1 = vec![0x11u8; 100];
119 fs.write_file(block, 0, &data1);
120 fs.commit();
121 fs.create_snapshot(b"v1");
122
123 let data2 = vec![0x22u8; 100];
124 fs.write_file(block, 0, &data2);
125 fs.commit();
126 fs.create_snapshot(b"v2");
127
128 assert!(fs.snapshot_lookup(b"v1").is_some());
129 assert!(fs.snapshot_lookup(b"v2").is_some());
130
131 let snap1 = fs.snapshot_lookup(b"v1").unwrap();
132 let snap2 = fs.snapshot_lookup(b"v2").unwrap();
133 assert_ne!(snap1.snapshot_txn_id, snap2.snapshot_txn_id);
134}
135
136#[test]
137fn delete_snapshot_bulkfree_reclaims_blocks() {
138 let mut fs = setup_fs(4096);
139 let root = fs.root_block();
140 let (_inode, block) = fs.create_file(root, b"snapdata");
141
142 let data: Vec<u8> = (0..4096u16).map(|i| (i % 199) as u8).collect();
143 fs.write_file(block, 0, &data);
144 fs.commit();
145
146 fs.create_snapshot(b"reclaim_snap");
147
148 let data_new: Vec<u8> = (0..4096u16).map(|i| (i % 97) as u8).collect();
149 fs.write_file(block, 0, &data_new);
150 fs.commit();
151
152 let alloc_before = fs.count_allocated_blocks();
153
154 fs.delete_snapshot(b"reclaim_snap");
155 fs.commit();
156 fs.run_bulkfree();
157
158 let alloc_after = fs.count_allocated_blocks();
159 assert!(
160 alloc_after < alloc_before,
161 "bulkfree should reclaim snapshot blocks: before={alloc_before}, after={alloc_after}"
162 );
163}
164
165#[test]
166fn snapshot_data_isolation_after_overwrite() {
167 let mut fs = setup_fs(4096);
168 let root = fs.root_block();
169
170 let (_inode, _block) = fs.create_file(root, b"cow_file");
171 let data_a = vec![0xAAu8; 4096];
172 fs.write_file_cow(b"cow_file", 0, &data_a);
173 fs.commit();
174
175 fs.create_snapshot(b"before_overwrite");
176
177 let data_b = vec![0xBBu8; 4096];
178 fs.write_file_cow(b"cow_file", 0, &data_b);
179 fs.commit();
180
181 let live_block = fs
182 .lookup_file(fs.root_block(), b"cow_file")
183 .expect("live file must exist");
184 let live_data = fs.read_file(live_block, 0, 4096);
185 assert_eq!(live_data, data_b, "live tree must reflect overwritten data");
186
187 fs.remount();
188
189 let snap_ref = fs
190 .snapshot_lookup(b"before_overwrite")
191 .expect("snapshot must exist");
192 let snap_root_entry = lancer_lancerfs::btree::btree_lookup(
193 &mut fs.pool,
194 &mut fs.cache,
195 &mut fs.bio,
196 &snap_ref.root_blockref,
197 fs.state.root_object_id,
198 snap_ref.txn_filter(),
199 )
200 .unwrap()
201 .expect("snapshot must contain root inode");
202
203 let snap_root_block = lancer_lancerfs::blockref_block_num(&snap_root_entry);
204 let snap_root_inode = file::read_inode(&mut fs.cache, &mut fs.bio, snap_root_block).unwrap();
205
206 let snap_file_entry = lancer_lancerfs::dir::dir_lookup(
207 &mut fs.pool,
208 &mut fs.cache,
209 &mut fs.bio,
210 &snap_root_inode,
211 b"cow_file",
212 snap_ref.txn_filter(),
213 )
214 .unwrap()
215 .expect("snapshot must contain cow_file");
216
217 let snap_file_block = lancer_lancerfs::blockref_block_num(&snap_file_entry);
218 let snap_file_inode = file::read_inode(&mut fs.cache, &mut fs.bio, snap_file_block).unwrap();
219
220 let mut snap_buf = vec![0u8; 4096];
221 let n = file::file_read(
222 &mut fs.pool,
223 &mut fs.cache,
224 &mut fs.bio,
225 &snap_file_inode,
226 0,
227 &mut snap_buf,
228 )
229 .unwrap();
230 snap_buf.truncate(n);
231
232 assert_eq!(
233 snap_buf, data_a,
234 "snapshot must return original data (A), not overwritten data (B)"
235 );
236}
237
238#[test]
239fn writable_snapshot_isolates_modifications() {
240 let mut fs = setup_fs(4096);
241 let root = fs.root_block();
242
243 let (_inode, _block) = fs.create_file(root, b"original");
244 fs.write_file_cow(b"original", 0, b"hello");
245 fs.commit();
246
247 fs.create_snapshot(b"snap2");
248
249 let snap_ref = fs.snapshot_lookup(b"snap2").expect("snapshot must exist");
250 let snap_txn = snap_ref.snapshot_txn_id;
251 let new_snap_tree = fs.snapshot_create_file(&snap_ref, b"snap_only_file");
252
253 fs.writable_snapshot_commit(b"snap2", &new_snap_tree, snap_txn);
254
255 assert!(
256 fs.lookup_file(fs.root_block(), b"original").is_some(),
257 "live tree must still have 'original'"
258 );
259 assert!(
260 fs.lookup_file(fs.root_block(), b"snap_only_file").is_none(),
261 "live tree must NOT have 'snap_only_file'"
262 );
263
264 let updated_snap = fs
265 .snapshot_lookup(b"snap2")
266 .expect("snapshot must still exist");
267 let snap_root_entry = lancer_lancerfs::btree::btree_lookup(
268 &mut fs.pool,
269 &mut fs.cache,
270 &mut fs.bio,
271 &updated_snap.root_blockref,
272 fs.state.root_object_id,
273 updated_snap.txn_filter(),
274 )
275 .unwrap()
276 .expect("updated snapshot must contain root inode");
277
278 let snap_root_block = lancer_lancerfs::blockref_block_num(&snap_root_entry);
279 let snap_root_inode = file::read_inode(&mut fs.cache, &mut fs.bio, snap_root_block).unwrap();
280
281 let snap_has_original = lancer_lancerfs::dir::dir_lookup(
282 &mut fs.pool,
283 &mut fs.cache,
284 &mut fs.bio,
285 &snap_root_inode,
286 b"original",
287 None,
288 )
289 .unwrap();
290 assert!(
291 snap_has_original.is_some(),
292 "snapshot must contain 'original'"
293 );
294
295 let snap_has_new = lancer_lancerfs::dir::dir_lookup(
296 &mut fs.pool,
297 &mut fs.cache,
298 &mut fs.bio,
299 &snap_root_inode,
300 b"snap_only_file",
301 None,
302 )
303 .unwrap();
304 assert!(
305 snap_has_new.is_some(),
306 "snapshot must contain 'snap_only_file'"
307 );
308}