1
use std::io::{Read, Seek, Write};
2
use std::mem::size_of;
3

            
4
use bonsaidb_core::key::time::TimestampAsNanoseconds;
5
use bonsaidb_core::test_util::TestDirectory;
6
use bonsaidb_local::config::{Builder, StorageConfiguration};
7
#[cfg(feature = "async")]
8
use bonsaidb_local::AsyncDatabase;
9
use bonsaidb_local::Database;
10
#[cfg(feature = "async")]
11
use futures::StreamExt;
12
#[cfg(feature = "async")]
13
use tokio::io::{AsyncReadExt, AsyncWriteExt};
14

            
15
use crate::{BonsaiFiles, Error, FileConfig, FilesSchema, Truncate};
16

            
17
1
#[test]
18
1
fn simple_file_test() {
19
1
    let directory = TestDirectory::new("simple-file");
20
1
    let database = Database::open::<FilesSchema>(StorageConfiguration::new(&directory)).unwrap();
21
1

            
22
1
    let data = "hello, world!";
23
1
    let mut file = BonsaiFiles::build("/hello/world.txt")
24
1
        .contents(data.as_bytes())
25
1
        .create(&database)
26
1
        .unwrap();
27
1
    assert_eq!(file.len().unwrap(), u64::try_from(data.len()).unwrap());
28

            
29
1
    println!("Created file: {file:?}, length: {}", file.len().unwrap());
30
1

            
31
1
    let contents = file.contents().unwrap();
32
1
    assert_eq!(contents.len(), u64::try_from(data.len()).unwrap());
33

            
34
1
    let bytes = contents.into_string().unwrap();
35
1
    assert_eq!(bytes, data);
36

            
37
1
    let file = BonsaiFiles::load("/hello/world.txt", &database)
38
1
        .unwrap()
39
1
        .unwrap();
40
1
    assert_eq!(file.name(), "world.txt");
41
1
    assert_eq!(file.containing_path(), "/hello/");
42
1
    assert_eq!(file.contents().unwrap().to_string().unwrap(), data);
43

            
44
1
    file.delete().unwrap();
45
1
    assert!(BonsaiFiles::load("/hello/world.txt", &database)
46
1
        .unwrap()
47
1
        .is_none());
48
1
}
49

            
50
#[cfg(feature = "async")]
51
1
#[tokio::test]
52
1
async fn async_simple_file_test() {
53
1
    let directory = TestDirectory::new("simple-file-async");
54
1
    let database = AsyncDatabase::open::<FilesSchema>(StorageConfiguration::new(&directory))
55
1
        .await
56
1
        .unwrap();
57
1

            
58
1
    let data = "hello, world!";
59
1
    let mut file = BonsaiFiles::build("/hello/world.txt")
60
1
        .contents(data.as_bytes())
61
1
        .create_async(&database)
62
2
        .await
63
1
        .unwrap();
64
1
    assert_eq!(
65
1
        file.len().await.unwrap(),
66
1
        u64::try_from(data.len()).unwrap()
67
1
    );
68
1

            
69
1
    println!(
70
1
        "Created file: {file:?}, length: {}",
71
1
        file.len().await.unwrap()
72
1
    );
73
1

            
74
1
    let contents = file.contents().await.unwrap();
75
1
    assert_eq!(contents.len(), u64::try_from(data.len()).unwrap());
76
1

            
77
1
    let bytes = contents.into_string().await.unwrap();
78
1
    assert_eq!(bytes, data);
79
1

            
80
1
    let file = BonsaiFiles::load_async("/hello/world.txt", &database)
81
1
        .await
82
1
        .unwrap()
83
1
        .unwrap();
84
1
    assert_eq!(file.name(), "world.txt");
85
1
    assert_eq!(file.containing_path(), "/hello/");
86
1
    assert_eq!(
87
1
        file.contents().await.unwrap().to_string().await.unwrap(),
88
1
        data
89
1
    );
90
1

            
91
2
    file.delete().await.unwrap();
92
1
    assert!(BonsaiFiles::load_async("/hello/world.txt", &database)
93
1
        .await
94
1
        .unwrap()
95
1
        .is_none());
96
1
}
97

            
98
1
#[test]
99
1
fn load_or_create_test() {
100
1
    let directory = TestDirectory::new("load-or-create");
101
1
    let database = Database::open::<FilesSchema>(StorageConfiguration::new(&directory)).unwrap();
102
1

            
103
1
    let file = BonsaiFiles::load_or_create("/hello/world.txt", true, &database).unwrap();
104
1
    let reloaded = BonsaiFiles::load_or_create("/hello/world.txt", true, &database).unwrap();
105
1
    assert_eq!(file, reloaded);
106
1
    let reloaded_unoptimal =
107
1
        BonsaiFiles::load_or_create("/hello/world.txt", false, &database).unwrap();
108
1
    assert_eq!(file, reloaded_unoptimal);
109

            
110
1
    file.delete().unwrap();
111
1
}
112

            
113
#[cfg(feature = "async")]
114
1
#[tokio::test]
115
1
async fn async_load_or_create_test() {
116
1
    let directory = TestDirectory::new("load-or-create-async");
117
1
    let database = AsyncDatabase::open::<FilesSchema>(StorageConfiguration::new(&directory))
118
1
        .await
119
1
        .unwrap();
120
1

            
121
1
    let file = BonsaiFiles::load_or_create_async("/hello/world.txt", true, &database)
122
2
        .await
123
1
        .unwrap();
124
1
    let reloaded = BonsaiFiles::load_or_create_async("/hello/world.txt", true, &database)
125
1
        .await
126
1
        .unwrap();
127
1
    assert_eq!(file, reloaded);
128
1
    let reloaded_unoptimal =
129
1
        BonsaiFiles::load_or_create_async("/hello/world.txt", false, &database)
130
2
            .await
131
1
            .unwrap();
132
1
    assert_eq!(file, reloaded_unoptimal);
133
1

            
134
2
    file.delete().await.unwrap();
135
1
}
136

            
137
1
#[test]
138
1
fn invalid_name_test() {
139
1
    let directory = TestDirectory::new("invalid-name");
140
1
    let database = Database::open::<FilesSchema>(StorageConfiguration::new(&directory)).unwrap();
141
1

            
142
1
    let err = BonsaiFiles::build("hello/.txt")
143
1
        .contents(b"hello, world!")
144
1
        .create(&database)
145
1
        .unwrap_err();
146
1
    assert!(matches!(err, Error::InvalidName));
147
1
}
148

            
149
#[cfg(feature = "async")]
150
1
#[tokio::test]
151
1
async fn async_invalid_name_test() {
152
1
    let directory = TestDirectory::new("invalid-name-async");
153
1
    let database = AsyncDatabase::open::<FilesSchema>(StorageConfiguration::new(&directory))
154
1
        .await
155
1
        .unwrap();
156
1

            
157
1
    let err = BonsaiFiles::build("hello/.txt")
158
1
        .contents(b"hello, world!")
159
1
        .create_async(&database)
160
1
        .await
161
1
        .unwrap_err();
162
1
    assert!(matches!(err, Error::InvalidName));
163
1
}
164

            
165
1
#[test]
166
1
fn simple_path_test() {
167
1
    let directory = TestDirectory::new("simple-path");
168
1
    let database = Database::open::<FilesSchema>(StorageConfiguration::new(&directory)).unwrap();
169
1

            
170
1
    let data = b"hello";
171
1
    let file = BonsaiFiles::build("hello.txt")
172
1
        .at_path("/some/containing/path")
173
1
        .contents(data)
174
1
        .create(&database)
175
1
        .unwrap();
176
1
    let contents = file.contents().unwrap();
177
1
    println!("Created file: {file:?}, length: {}", contents.len());
178
1
    let bytes = contents.into_vec().unwrap();
179
1
    assert_eq!(bytes, data);
180

            
181
1
    let file = BonsaiFiles::load("/some/containing/path/hello.txt", &database)
182
1
        .unwrap()
183
1
        .unwrap();
184
1
    assert_eq!(file.name(), "hello.txt");
185
1
    assert_eq!(file.containing_path(), "/some/containing/path/");
186

            
187
1
    let data2 = b"world!";
188
1
    let file2 = BonsaiFiles::build("world.txt")
189
1
        .at_path("/some/")
190
1
        .contents(data2)
191
1
        .create(&database)
192
1
        .unwrap();
193
1
    let contents = file2.contents().unwrap();
194
1
    let bytes = contents.into_vec().unwrap();
195
1
    assert_eq!(bytes, data2);
196

            
197
1
    let some_contents = BonsaiFiles::list("/", &database).unwrap();
198
1
    assert_eq!(some_contents.len(), 0);
199
    // First query intentionally has no trailing slash. The rest have trailing slashes.
200
1
    let path_contents = BonsaiFiles::list("/some/containing/path", &database).unwrap();
201
1
    assert_eq!(path_contents.len(), 1);
202
1
    assert_eq!(path_contents[0].name(), "hello.txt");
203
1
    let path_contents = BonsaiFiles::list("/some/", &database).unwrap();
204
1
    assert_eq!(path_contents.len(), 1);
205
1
    let path_contents = BonsaiFiles::list("/some/containing/", &database).unwrap();
206
1
    assert_eq!(path_contents.len(), 0);
207

            
208
1
    let all_contents = BonsaiFiles::list_recursive("/", &database).unwrap();
209
1
    assert_eq!(all_contents.len(), 2);
210
1
    let all_contents = BonsaiFiles::list_recursive("/some", &database).unwrap();
211
1
    assert_eq!(all_contents.len(), 2);
212
1
    let all_contents = BonsaiFiles::list_recursive("/some/containing/", &database).unwrap();
213
1
    assert_eq!(all_contents.len(), 1);
214
1
    let all_contents = BonsaiFiles::list_recursive("/some/containing/path/", &database).unwrap();
215
1
    assert_eq!(all_contents.len(), 1);
216

            
217
1
    let stats = BonsaiFiles::stats(&database).unwrap();
218
1
    assert_eq!(
219
1
        stats.total_bytes,
220
1
        u64::try_from(data.len() + data2.len()).unwrap()
221
1
    );
222
1
    assert_eq!(stats.file_count, 2);
223
1
    assert_eq!(
224
1
        BonsaiFiles::stats_for_path("/some", &database)
225
1
            .unwrap()
226
1
            .total_bytes,
227
1
        u64::try_from(data.len() + data2.len()).unwrap()
228
1
    );
229
1
    assert_eq!(
230
1
        BonsaiFiles::stats_for_path("/some/containing", &database)
231
1
            .unwrap()
232
1
            .total_bytes,
233
1
        u64::try_from(data.len()).unwrap()
234
1
    );
235
1
    assert_eq!(
236
1
        BonsaiFiles::stats_for_path("/nonexistant", &database)
237
1
            .unwrap()
238
1
            .total_bytes,
239
1
        0
240
1
    );
241

            
242
    // Test renaming and moving
243
1
    let mut file = file;
244
1
    file.rename(String::from("new-name.txt")).unwrap();
245
1
    assert!(
246
1
        BonsaiFiles::load("/some/containing/path/hello.txt", &database)
247
1
            .unwrap()
248
1
            .is_none()
249
1
    );
250
1
    let mut file = BonsaiFiles::load("/some/containing/path/new-name.txt", &database)
251
1
        .unwrap()
252
1
        .unwrap();
253
1
    file.move_to("/new/path/").unwrap();
254
1
    assert!(BonsaiFiles::load("/new/path/new-name.txt", &database)
255
1
        .unwrap()
256
1
        .is_some());
257
1
    file.move_to("/final/path/and_name.txt").unwrap();
258
1
    let file = BonsaiFiles::load("/final/path/and_name.txt", &database)
259
1
        .unwrap()
260
1
        .unwrap();
261
1
    let contents = file.contents().unwrap().into_vec().unwrap();
262
1
    assert_eq!(contents, data);
263

            
264
1
    assert_eq!(
265
1
        BonsaiFiles::stats_for_path("/some", &database)
266
1
            .unwrap()
267
1
            .total_bytes,
268
1
        u64::try_from(data2.len()).unwrap()
269
1
    );
270
1
}
271

            
272
#[cfg(feature = "async")]
273
1
#[tokio::test]
274
#[allow(clippy::too_many_lines)]
275
1
async fn async_simple_path_test() {
276
1
    let directory = TestDirectory::new("simple-path-async");
277
1
    let database = AsyncDatabase::open::<FilesSchema>(StorageConfiguration::new(&directory))
278
1
        .await
279
1
        .unwrap();
280
1

            
281
1
    let data = b"hello";
282
1
    let file = BonsaiFiles::build("hello.txt")
283
1
        .at_path("/some/containing/path")
284
1
        .contents(data)
285
1
        .create_async(&database)
286
2
        .await
287
1
        .unwrap();
288
1
    let contents = file.contents().await.unwrap();
289
1
    println!("Created file: {file:?}, length: {}", contents.len());
290
1
    let bytes = contents.into_vec().await.unwrap();
291
1
    assert_eq!(bytes, data);
292
1

            
293
1
    let file = BonsaiFiles::load_async("/some/containing/path/hello.txt", &database)
294
1
        .await
295
1
        .unwrap()
296
1
        .unwrap();
297
1
    assert_eq!(file.name(), "hello.txt");
298
1
    assert_eq!(file.containing_path(), "/some/containing/path/");
299
1

            
300
1
    let data2 = b"world!";
301
1
    let file2 = BonsaiFiles::build("world.txt")
302
1
        .at_path("/some/")
303
1
        .contents(data2)
304
1
        .create_async(&database)
305
2
        .await
306
1
        .unwrap();
307
1
    let contents = file2.contents().await.unwrap();
308
1
    let bytes = contents.into_vec().await.unwrap();
309
1
    assert_eq!(bytes, data2);
310
1

            
311
1
    let some_contents = BonsaiFiles::list_async("/", &database).await.unwrap();
312
1
    assert_eq!(some_contents.len(), 0);
313
1
    // First query intentionally has no trailing slash. The rest have trailing slashes.
314
1
    let path_contents = BonsaiFiles::list_async("/some/containing/path", &database)
315
1
        .await
316
1
        .unwrap();
317
1
    assert_eq!(path_contents.len(), 1);
318
1
    assert_eq!(path_contents[0].name(), "hello.txt");
319
1
    let path_contents = BonsaiFiles::list_async("/some/", &database).await.unwrap();
320
1
    assert_eq!(path_contents.len(), 1);
321
1
    let path_contents = BonsaiFiles::list_async("/some/containing/", &database)
322
1
        .await
323
1
        .unwrap();
324
1
    assert_eq!(path_contents.len(), 0);
325
1

            
326
1
    let all_contents = BonsaiFiles::list_recursive_async("/", &database)
327
1
        .await
328
1
        .unwrap();
329
1
    assert_eq!(all_contents.len(), 2);
330
1
    let all_contents = BonsaiFiles::list_recursive_async("/some", &database)
331
1
        .await
332
1
        .unwrap();
333
1
    assert_eq!(all_contents.len(), 2);
334
1
    let all_contents = BonsaiFiles::list_recursive_async("/some/containing/", &database)
335
1
        .await
336
1
        .unwrap();
337
1
    assert_eq!(all_contents.len(), 1);
338
1
    let all_contents = BonsaiFiles::list_recursive_async("/some/containing/path/", &database)
339
1
        .await
340
1
        .unwrap();
341
1
    assert_eq!(all_contents.len(), 1);
342
1

            
343
2
    let stats = BonsaiFiles::stats_async(&database).await.unwrap();
344
1
    assert_eq!(
345
1
        stats.total_bytes,
346
1
        u64::try_from(data.len() + data2.len()).unwrap()
347
1
    );
348
1
    assert_eq!(stats.file_count, 2);
349
1
    assert_eq!(
350
1
        BonsaiFiles::stats_for_path_async("/some", &database)
351
2
            .await
352
1
            .unwrap()
353
1
            .total_bytes,
354
1
        u64::try_from(data.len() + data2.len()).unwrap()
355
1
    );
356
1
    assert_eq!(
357
1
        BonsaiFiles::stats_for_path_async("/some/containing", &database)
358
2
            .await
359
1
            .unwrap()
360
1
            .total_bytes,
361
1
        u64::try_from(data.len()).unwrap()
362
1
    );
363
1
    assert_eq!(
364
1
        BonsaiFiles::stats_for_path_async("/nonexistant", &database)
365
2
            .await
366
1
            .unwrap()
367
1
            .total_bytes,
368
1
        0
369
1
    );
370
1

            
371
1
    // Test renaming and moving
372
1
    let mut file = file;
373
1
    file.rename(String::from("new-name.txt")).await.unwrap();
374
1
    assert!(
375
1
        BonsaiFiles::load_async("/some/containing/path/hello.txt", &database)
376
1
            .await
377
1
            .unwrap()
378
1
            .is_none()
379
1
    );
380
1
    let mut file = BonsaiFiles::load_async("/some/containing/path/new-name.txt", &database)
381
1
        .await
382
1
        .unwrap()
383
1
        .unwrap();
384
1
    file.move_to("/new/path/").await.unwrap();
385
1
    assert!(BonsaiFiles::load_async("/new/path/new-name.txt", &database)
386
1
        .await
387
1
        .unwrap()
388
1
        .is_some());
389
1
    file.move_to("/final/path/and_name.txt").await.unwrap();
390
1
    let file = BonsaiFiles::load_async("/final/path/and_name.txt", &database)
391
1
        .await
392
1
        .unwrap()
393
1
        .unwrap();
394
1
    let contents = file.contents().await.unwrap().into_vec().await.unwrap();
395
1
    assert_eq!(contents, data);
396
1

            
397
1
    assert_eq!(
398
1
        BonsaiFiles::stats_for_path_async("/some", &database)
399
2
            .await
400
1
            .unwrap()
401
1
            .total_bytes,
402
1
        u64::try_from(data2.len()).unwrap()
403
1
    );
404
1
}
405

            
406
enum SmallBlocks {}
407
impl FileConfig for SmallBlocks {
408
    type Metadata = usize;
409

            
410
    const BLOCK_SIZE: usize = 8;
411

            
412
114
    fn files_name() -> bonsaidb_core::schema::CollectionName {
413
114
        BonsaiFiles::files_name()
414
114
    }
415

            
416
368
    fn blocks_name() -> bonsaidb_core::schema::CollectionName {
417
368
        BonsaiFiles::blocks_name()
418
368
    }
419
}
420

            
421
1
#[test]
422
1
fn blocked_file_test() {
423
1
    let mut big_file = Vec::with_capacity(SmallBlocks::BLOCK_SIZE * 31 / 2);
424
1
    let mut counter = 0_u8;
425
31
    while big_file.len() + 4 < big_file.capacity() {
426
30
        counter += 1;
427
150
        for _ in 0..4 {
428
120
            big_file.push(counter);
429
120
        }
430
    }
431
1
    let directory = TestDirectory::new("blocked-file");
432
1
    let database =
433
1
        Database::open::<FilesSchema<SmallBlocks>>(StorageConfiguration::new(&directory)).unwrap();
434
1

            
435
1
    let test_start = TimestampAsNanoseconds::now();
436
1
    let mut file = SmallBlocks::build("hello.txt")
437
1
        .contents(&big_file)
438
1
        .create(&database)
439
1
        .unwrap();
440
1
    let contents = file.contents().unwrap();
441
1
    println!("Created file: {file:?}, length: {}", contents.len());
442
1
    assert!(file.last_appended_at().unwrap().unwrap() > test_start);
443
1
    assert!(contents.last_appended_at().unwrap() > test_start);
444
1
    let bytes = contents.into_vec().unwrap();
445
1
    assert_eq!(bytes, big_file);
446

            
447
    // Truncate the beginning of the file
448
1
    let new_length = u64::try_from(SmallBlocks::BLOCK_SIZE * 3).unwrap();
449
1
    big_file.splice(..big_file.len() - SmallBlocks::BLOCK_SIZE * 3, []);
450
1
    file.truncate(new_length, Truncate::RemovingStart).unwrap();
451
1
    assert_eq!(file.len().unwrap(), new_length);
452

            
453
1
    let contents = file.contents().unwrap();
454
1
    assert_eq!(contents.len(), new_length);
455
1
    assert_eq!(contents.into_vec().unwrap(), big_file);
456

            
457
    // Truncate the end of the file.
458
1
    let new_length = u64::try_from(SmallBlocks::BLOCK_SIZE).unwrap();
459
1
    big_file.truncate(SmallBlocks::BLOCK_SIZE);
460
1
    file.truncate(new_length, Truncate::RemovingEnd).unwrap();
461
1

            
462
1
    let contents = file.contents().unwrap();
463
1
    assert_eq!(contents.len(), new_length);
464
1
    assert_eq!(contents.to_vec().unwrap(), big_file);
465

            
466
    // Clear the file.
467
1
    file.truncate(0, Truncate::RemovingEnd).unwrap();
468
1
    assert_eq!(file.len().unwrap(), 0);
469
1
    assert!(file.last_appended_at().unwrap().is_none());
470
1
    assert!(file.contents().unwrap().last_appended_at().is_none());
471

            
472
1
    let mut writer = file.append_buffered();
473
1
    let buffer_size = SmallBlocks::BLOCK_SIZE * 3 / 2;
474
1
    writer.set_buffer_size(buffer_size).unwrap();
475
1
    // Write more than the single buffer size.
476
1
    let data_written = &bytes[0..SmallBlocks::BLOCK_SIZE * 2];
477
1
    writer.write_all(data_written).unwrap();
478
1
    assert_eq!(writer.buffer.len(), SmallBlocks::BLOCK_SIZE / 2);
479
1
    drop(writer);
480
1

            
481
1
    let contents = file.contents().unwrap();
482
1
    assert_eq!(contents.to_vec().unwrap(), data_written);
483
1
}
484

            
485
#[cfg(feature = "async")]
486
1
#[tokio::test]
487
1
async fn async_blocked_file_test() {
488
1
    let mut big_file = Vec::with_capacity(SmallBlocks::BLOCK_SIZE * 31 / 2);
489
1
    let mut counter = 0_u8;
490
31
    while big_file.len() + 4 < big_file.capacity() {
491
30
        counter += 1;
492
150
        for _ in 0..4 {
493
120
            big_file.push(counter);
494
120
        }
495
1
    }
496
1
    let directory = TestDirectory::new("blocked-file-async");
497
1
    let database =
498
1
        AsyncDatabase::open::<FilesSchema<SmallBlocks>>(StorageConfiguration::new(&directory))
499
1
            .await
500
1
            .unwrap();
501
1

            
502
1
    let test_start = TimestampAsNanoseconds::now();
503
1
    let mut file = SmallBlocks::build("hello.txt")
504
1
        .contents(&big_file)
505
1
        .create_async(&database)
506
2
        .await
507
1
        .unwrap();
508
1
    let contents = file.contents().await.unwrap();
509
1
    println!("Created file: {file:?}, length: {}", contents.len());
510
1
    assert!(file.last_appended_at().await.unwrap().unwrap() > test_start);
511
1
    let initial_write_timestamp = contents.last_appended_at().unwrap();
512
1
    assert!(initial_write_timestamp > test_start);
513
2
    let bytes = contents.into_vec().await.unwrap();
514
1
    assert_eq!(bytes, big_file);
515
1
    // Truncate the beginning of the file
516
1
    let new_length = u64::try_from(SmallBlocks::BLOCK_SIZE * 3).unwrap();
517
1
    big_file.splice(..big_file.len() - SmallBlocks::BLOCK_SIZE * 3, []);
518
1
    file.truncate(new_length, Truncate::RemovingStart)
519
2
        .await
520
1
        .unwrap();
521
1
    assert_eq!(file.len().await.unwrap(), new_length);
522
1

            
523
1
    let contents = file.contents().await.unwrap();
524
1
    assert_eq!(contents.len(), new_length);
525
1
    assert_eq!(contents.into_vec().await.unwrap(), big_file);
526
1

            
527
1
    // Truncate the end of the file.
528
1
    let new_length = u64::try_from(SmallBlocks::BLOCK_SIZE).unwrap();
529
1
    big_file.truncate(SmallBlocks::BLOCK_SIZE);
530
1
    file.truncate(new_length, Truncate::RemovingEnd)
531
2
        .await
532
1
        .unwrap();
533
1

            
534
1
    let contents = file.contents().await.unwrap();
535
1
    assert_eq!(contents.len(), new_length);
536
1
    assert_eq!(contents.to_vec().await.unwrap(), big_file);
537
1

            
538
1
    // Clear the file.
539
2
    file.truncate(0, Truncate::RemovingEnd).await.unwrap();
540
1
    assert_eq!(file.len().await.unwrap(), 0);
541
1
    assert!(file.last_appended_at().await.unwrap().is_none());
542
1
    assert!(file.contents().await.unwrap().last_appended_at().is_none());
543
1

            
544
1
    let mut writer = file.append_buffered();
545
1
    let buffer_size = SmallBlocks::BLOCK_SIZE * 3 / 2;
546
1
    writer.set_buffer_size(buffer_size).await.unwrap();
547
1
    // Write more than the single buffer size.
548
1
    let data_written = &bytes[0..SmallBlocks::BLOCK_SIZE * 2];
549
1
    writer.write_all(data_written).await.unwrap();
550
1
    assert_eq!(writer.buffer.len(), SmallBlocks::BLOCK_SIZE / 2);
551
1
    drop(writer);
552
1

            
553
1
    // Dropping an unflushed writer will flush it in the background.
554
1
    tokio::time::sleep(std::time::Duration::from_millis(250)).await;
555
1

            
556
1
    let contents = file.contents().await.unwrap();
557
1
    assert!(contents.last_appended_at().unwrap() > initial_write_timestamp);
558
1
    assert_eq!(contents.to_vec().await.unwrap(), data_written);
559
1
}
560

            
561
1
#[test]
562
1
fn seek_read_test() {
563
1
    let mut file_contents = Vec::with_capacity(BonsaiFiles::BLOCK_SIZE * 3);
564
1
    let word_size = size_of::<usize>();
565
24576
    while file_contents.len() + word_size < file_contents.capacity() {
566
24575
        file_contents.extend(file_contents.len().to_be_bytes());
567
24575
    }
568
1
    let directory = TestDirectory::new("seek-read");
569
1
    let database = Database::open::<FilesSchema>(StorageConfiguration::new(&directory)).unwrap();
570
1

            
571
1
    let file = BonsaiFiles::build("hello.bin")
572
1
        .contents(&file_contents)
573
1
        .create(&database)
574
1
        .unwrap();
575
1
    let mut contents = file
576
1
        .contents()
577
1
        .unwrap()
578
1
        .with_buffer_size(BonsaiFiles::BLOCK_SIZE);
579
1
    // Read the last 16 bytes
580
1
    contents.seek(std::io::SeekFrom::End(-16)).unwrap();
581
1
    let mut buffer = [0; 16];
582
1
    contents.read_exact(&mut buffer).unwrap();
583
1
    assert_eq!(&file_contents[file_contents.len() - 16..], &buffer);
584

            
585
    // Seek slightly ahead of the start, and read 16 bytes.
586
1
    contents.seek(std::io::SeekFrom::Start(16)).unwrap();
587
1
    contents.read_exact(&mut buffer).unwrap();
588
1
    assert_eq!(&file_contents[16..32], &buffer);
589

            
590
    // Move foward into the next block.
591
1
    contents
592
1
        .seek(std::io::SeekFrom::Current(
593
1
            i64::try_from(BonsaiFiles::BLOCK_SIZE).unwrap(),
594
1
        ))
595
1
        .unwrap();
596
1
    contents.read_exact(&mut buffer).unwrap();
597
1
    assert_eq!(
598
1
        &file_contents[BonsaiFiles::BLOCK_SIZE + 32..BonsaiFiles::BLOCK_SIZE + 48],
599
1
        &buffer
600
1
    );
601
    // Re-read the last 16 bytes
602
1
    contents.seek(std::io::SeekFrom::Current(-16)).unwrap();
603
1
    assert_eq!(
604
1
        &file_contents[BonsaiFiles::BLOCK_SIZE + 32..BonsaiFiles::BLOCK_SIZE + 48],
605
1
        &buffer
606
1
    );
607
    // Try seeking to the end of the universe. It should return the file's length.
608
1
    assert_eq!(
609
1
        contents.seek(std::io::SeekFrom::End(i64::MAX)).unwrap(),
610
1
        contents.len()
611
1
    );
612
    // Verify read returns 0 bytes.
613
1
    assert_eq!(contents.read(&mut buffer).unwrap(), 0);
614
1
}
615

            
616
#[cfg(feature = "async")]
617
1
#[tokio::test]
618
1
async fn async_seek_read_test() {
619
1
    let mut file_contents = Vec::with_capacity(BonsaiFiles::BLOCK_SIZE * 3);
620
1
    let word_size = size_of::<usize>();
621
24576
    while file_contents.len() + word_size < file_contents.capacity() {
622
24575
        file_contents.extend(file_contents.len().to_be_bytes());
623
24575
    }
624
1
    let directory = TestDirectory::new("seek-read-async");
625
1
    let database = AsyncDatabase::open::<FilesSchema>(StorageConfiguration::new(&directory))
626
1
        .await
627
1
        .unwrap();
628
1

            
629
1
    let file = BonsaiFiles::build("hello.bin")
630
1
        .contents(&file_contents)
631
1
        .create_async(&database)
632
2
        .await
633
1
        .unwrap();
634
1
    let mut contents = file
635
1
        .contents()
636
1
        .await
637
1
        .unwrap()
638
1
        .with_buffer_size(BonsaiFiles::BLOCK_SIZE);
639
1
    // Read the last 16 bytes
640
1
    contents.seek(std::io::SeekFrom::End(-16)).unwrap();
641
1
    let mut buffer = [0; 16];
642
1
    contents.read_exact(&mut buffer).await.unwrap();
643
1
    assert_eq!(&file_contents[file_contents.len() - 16..], &buffer);
644
1

            
645
1
    // Seek slightly ahead of the start, and read 16 bytes.
646
1
    contents.seek(std::io::SeekFrom::Start(16)).unwrap();
647
1
    contents.read_exact(&mut buffer).await.unwrap();
648
1
    assert_eq!(&file_contents[16..32], &buffer);
649
1

            
650
1
    // Move foward into the next block.
651
1
    contents
652
1
        .seek(std::io::SeekFrom::Current(
653
1
            i64::try_from(BonsaiFiles::BLOCK_SIZE).unwrap(),
654
1
        ))
655
1
        .unwrap();
656
1
    contents.read_exact(&mut buffer).await.unwrap();
657
1
    assert_eq!(
658
1
        &file_contents[BonsaiFiles::BLOCK_SIZE + 32..BonsaiFiles::BLOCK_SIZE + 48],
659
1
        &buffer
660
1
    );
661
1
    // Re-read the last 16 bytes
662
1
    contents.seek(std::io::SeekFrom::Current(-16)).unwrap();
663
1
    assert_eq!(
664
1
        &file_contents[BonsaiFiles::BLOCK_SIZE + 32..BonsaiFiles::BLOCK_SIZE + 48],
665
1
        &buffer
666
1
    );
667
1
    // Try seeking to the end of the universe. It should return the file's length.
668
1
    assert_eq!(
669
1
        contents.seek(std::io::SeekFrom::End(i64::MAX)).unwrap(),
670
1
        contents.len()
671
1
    );
672
1
    // Verify read returns 0 bytes.
673
1
    assert_eq!(contents.read(&mut buffer).await.unwrap(), 0);
674
1
}
675

            
676
1
#[test]
677
1
fn block_iterator_test() {
678
1
    let mut file_contents = Vec::with_capacity(BonsaiFiles::BLOCK_SIZE * 3);
679
1
    let word_size = size_of::<usize>();
680
24576
    while file_contents.len() + word_size < file_contents.capacity() {
681
24575
        file_contents.extend(file_contents.len().to_be_bytes());
682
24575
    }
683
1
    let directory = TestDirectory::new("block-iterator");
684
1
    let database = Database::open::<FilesSchema>(StorageConfiguration::new(&directory)).unwrap();
685
1

            
686
1
    let file = BonsaiFiles::build("hello.bin")
687
1
        .contents(&file_contents)
688
1
        .create(&database)
689
1
        .unwrap();
690
1
    let mut compare_against = &file_contents[..];
691
1
    let mut contents = file
692
1
        .contents()
693
1
        .unwrap()
694
1
        .with_buffer_size(BonsaiFiles::BLOCK_SIZE);
695
1
    // read a few bytes to test partial iteration.
696
1
    let mut buffer = [0; 4];
697
1
    contents.read_exact(&mut buffer).unwrap();
698
1
    compare_against = &compare_against[4..];
699
1
    assert_eq!(buffer, &compare_against[..4]);
700

            
701
4
    for block in contents {
702
3
        let bytes = block.unwrap();
703
3
        assert_eq!(bytes, compare_against[..bytes.len()]);
704
3
        compare_against = &compare_against[bytes.len()..];
705
    }
706
1
    assert!(compare_against.is_empty());
707
1
}
708

            
709
1
#[tokio::test]
710
#[cfg(feature = "async")]
711
1
async fn block_stream_test() {
712
1
    let mut file_contents = Vec::with_capacity(BonsaiFiles::BLOCK_SIZE * 3);
713
1
    let word_size = size_of::<usize>();
714
24576
    while file_contents.len() + word_size < file_contents.capacity() {
715
24575
        file_contents.extend(file_contents.len().to_be_bytes());
716
24575
    }
717
1
    let directory = TestDirectory::new("block-stream");
718
1
    let database =
719
1
        AsyncDatabase::open::<FilesSchema<BonsaiFiles>>(StorageConfiguration::new(&directory))
720
1
            .await
721
1
            .unwrap();
722
1

            
723
1
    let file = BonsaiFiles::build("hello.bin")
724
1
        .contents(&file_contents)
725
1
        .create_async(&database)
726
2
        .await
727
1
        .unwrap();
728
1
    let mut compare_against = &file_contents[..];
729
1
    let mut contents = file
730
1
        .contents()
731
1
        .await
732
1
        .unwrap()
733
1
        .with_buffer_size(BonsaiFiles::BLOCK_SIZE);
734
1
    // read a few bytes to test partial iteration.
735
1
    let mut buffer = [0; 4];
736
1
    contents.read_exact(&mut buffer).await.unwrap();
737
1
    compare_against = &compare_against[4..];
738
1
    assert_eq!(buffer, &compare_against[..4]);
739
1

            
740
4
    while let Some(block) = contents.next().await {
741
3
        let bytes = block.unwrap();
742
3
        assert_eq!(bytes, compare_against[..bytes.len()]);
743
3
        compare_against = &compare_against[bytes.len()..];
744
1
    }
745
1
    assert!(compare_against.is_empty());
746
1
}
747

            
748
1
#[test]
749
1
fn simple_metadata_test() {
750
1
    let directory = TestDirectory::new("simple-metadata");
751
1
    let database =
752
1
        Database::open::<FilesSchema<SmallBlocks>>(StorageConfiguration::new(&directory)).unwrap();
753
1

            
754
1
    let file = SmallBlocks::build("/hello/world.txt")
755
1
        .contents(b"hello, world!")
756
1
        .metadata(42)
757
1
        .create(&database)
758
1
        .unwrap();
759
1
    assert_eq!(file.metadata(), &42);
760
1
    let mut file = SmallBlocks::get(file.id(), &database).unwrap().unwrap();
761
1
    assert_eq!(file.metadata(), &42);
762
1
    *file.metadata_mut() = 52;
763
1
    file.update_metadata().unwrap();
764
1

            
765
1
    let file = SmallBlocks::get(file.id(), &database).unwrap().unwrap();
766
1
    assert_eq!(file.metadata(), &52);
767
1
}
768

            
769
#[cfg(feature = "async")]
770
1
#[tokio::test]
771
1
async fn async_metadata_test() {
772
1
    let directory = TestDirectory::new("simple-metadata-async");
773
1
    let database =
774
1
        AsyncDatabase::open::<FilesSchema<SmallBlocks>>(StorageConfiguration::new(&directory))
775
1
            .await
776
1
            .unwrap();
777
1

            
778
1
    let file = SmallBlocks::build("/hello/world.txt")
779
1
        .contents(b"hello, world!")
780
1
        .metadata(42)
781
1
        .create_async(&database)
782
2
        .await
783
1
        .unwrap();
784
1
    assert_eq!(file.metadata(), &42);
785
1
    let mut file = SmallBlocks::get_async(file.id(), &database)
786
1
        .await
787
1
        .unwrap()
788
1
        .unwrap();
789
1
    assert_eq!(file.metadata(), &42);
790
1
    *file.metadata_mut() = 52;
791
1
    file.update_metadata().await.unwrap();
792
1

            
793
1
    let file = SmallBlocks::get_async(file.id(), &database)
794
1
        .await
795
1
        .unwrap()
796
1
        .unwrap();
797
1
    assert_eq!(file.metadata(), &52);
798
1
}