random-access-disk 4.0.0-alpha

Continuously read and write to disk, using random offsets and lengths
Documentation
use random_access_disk as rad;
use random_access_storage::RandomAccess;
use std::io::Read;
use tempfile::Builder;

use tokio::test as async_test;

#[async_test]
async fn can_call_new() {
    let dir = Builder::new()
        .prefix("random-access-disk")
        .tempdir()
        .unwrap();
    let _file = rad::RandomAccessDisk::open(dir.path().join("1.db"))
        .await
        .unwrap();
}

#[async_test]
async fn can_open_buffer() {
    let dir = Builder::new()
        .prefix("random-access-disk")
        .tempdir()
        .unwrap();
    let file = rad::RandomAccessDisk::open(dir.path().join("2.db"))
        .await
        .unwrap();
    file.write(0, b"hello").await.unwrap();
}

#[async_test]
async fn can_write() {
    let dir = Builder::new()
        .prefix("random-access-disk")
        .tempdir()
        .unwrap();
    let file = rad::RandomAccessDisk::open(dir.path().join("3.db"))
        .await
        .unwrap();
    file.write(0, b"hello").await.unwrap();
    file.write(5, b" world").await.unwrap();
}

#[async_test]
async fn can_read() {
    let dir = Builder::new()
        .prefix("random-access-disk")
        .tempdir()
        .unwrap();
    let file = rad::RandomAccessDisk::open(dir.path().join("4.db"))
        .await
        .unwrap();
    file.write(0, b"hello").await.unwrap();
    file.write(5, b" world").await.unwrap();
    let text = file.read(0, 11).await.unwrap();
    assert_eq!(String::from_utf8(text.to_vec()).unwrap(), "hello world");
}

#[async_test]
async fn can_truncate_lt() {
    let dir = Builder::new()
        .prefix("random-access-disk")
        .tempdir()
        .unwrap();
    let file = rad::RandomAccessDisk::open(dir.path().join("5.db"))
        .await
        .unwrap();
    file.write(0, b"hello").await.unwrap();
    file.write(5, b" world").await.unwrap();
    file.truncate(7).await.unwrap();
    let text = file.read(0, 7).await.unwrap();
    assert_eq!(String::from_utf8(text.to_vec()).unwrap(), "hello w");
    if file.read(0, 8).await.is_ok() {
        panic!("file is too big. read past the end should have failed");
    };
    let mut c_file = std::fs::File::open(dir.path().join("5.db")).unwrap();
    let mut c_contents = String::new();
    c_file.read_to_string(&mut c_contents).unwrap();
    assert_eq!(c_contents, "hello w");
}

#[async_test]
async fn can_truncate_gt() {
    let dir = Builder::new()
        .prefix("random-access-disk")
        .tempdir()
        .unwrap();
    let file = rad::RandomAccessDisk::open(dir.path().join("6.db"))
        .await
        .unwrap();
    file.write(0, b"hello").await.unwrap();
    file.write(5, b" world").await.unwrap();
    file.truncate(15).await.unwrap();
    let text = file.read(0, 15).await.unwrap();
    assert_eq!(
        String::from_utf8(text.to_vec()).unwrap(),
        "hello world\0\0\0\0"
    );
    if file.read(0, 16).await.is_ok() {
        panic!("file is too big. read past the end should have failed");
    };
    let mut c_file = std::fs::File::open(dir.path().join("6.db")).unwrap();
    let mut c_contents = String::new();
    c_file.read_to_string(&mut c_contents).unwrap();
    assert_eq!(c_contents, "hello world\0\0\0\0");
}

#[async_test]
async fn can_truncate_eq() {
    let dir = Builder::new()
        .prefix("random-access-disk")
        .tempdir()
        .unwrap();
    let file = rad::RandomAccessDisk::open(dir.path().join("7.db"))
        .await
        .unwrap();
    file.write(0, b"hello").await.unwrap();
    file.write(5, b" world").await.unwrap();
    file.truncate(11).await.unwrap();
    let text = file.read(0, 11).await.unwrap();
    assert_eq!(String::from_utf8(text.to_vec()).unwrap(), "hello world");
    if file.read(0, 12).await.is_ok() {
        panic!("file is too big. read past the end should have failed");
    };
    let mut c_file = std::fs::File::open(dir.path().join("7.db")).unwrap();
    let mut c_contents = String::new();
    c_file.read_to_string(&mut c_contents).unwrap();
    assert_eq!(c_contents, "hello world");
}

#[async_test]
async fn can_len() {
    let dir = Builder::new()
        .prefix("random-access-disk")
        .tempdir()
        .unwrap();
    let file = rad::RandomAccessDisk::open(dir.path().join("8.db"))
        .await
        .unwrap();
    assert_eq!(file.len(), 0);
    file.write(0, b"hello").await.unwrap();
    assert_eq!(file.len(), 5);
    file.write(5, b" world").await.unwrap();
    assert_eq!(file.len(), 11);
    file.truncate(15).await.unwrap();
    assert_eq!(file.len(), 15);
    file.truncate(8).await.unwrap();
    assert_eq!(file.len(), 8);
}

#[async_test]
async fn can_is_empty() {
    let dir = Builder::new()
        .prefix("random-access-disk")
        .tempdir()
        .unwrap();
    let file = rad::RandomAccessDisk::open(dir.path().join("9.db"))
        .await
        .unwrap();
    assert!(file.is_empty());
    file.write(0, b"hello").await.unwrap();
    assert!(!file.is_empty());
    file.truncate(0).await.unwrap();
    assert!(file.is_empty());
    file.truncate(1).await.unwrap();
    assert!(!file.is_empty());
    file.truncate(0).await.unwrap();
    assert!(file.is_empty());
    file.write(0, b"what").await.unwrap();
    assert!(!file.is_empty());
}

#[async_test]
async fn explicit_no_auto_sync() {
    let dir = Builder::new()
        .prefix("random-access-disk")
        .tempdir()
        .unwrap();
    let file = rad::RandomAccessDisk::builder(dir.path().join("10.db"))
        .auto_sync(false)
        .build()
        .await
        .unwrap();
    file.write(0, b"hello").await.unwrap();
    file.write(5, b" world").await.unwrap();
    file.truncate(11).await.unwrap();
    file.sync_all().await.unwrap();
    let text = file.read(0, 11).await.unwrap();
    assert_eq!(String::from_utf8(text.to_vec()).unwrap(), "hello world");
    if file.read(0, 12).await.is_ok() {
        panic!("file is too big. read past the end should have failed");
    };
    let mut c_file = std::fs::File::open(dir.path().join("10.db")).unwrap();
    let mut c_contents = String::new();
    c_file.read_to_string(&mut c_contents).unwrap();
    assert_eq!(c_contents, "hello world");
}

#[async_test]
async fn auto_sync() {
    let dir = Builder::new()
        .prefix("random-access-disk")
        .tempdir()
        .unwrap();
    let file = rad::RandomAccessDisk::builder(dir.path().join("11.db"))
        .build()
        .await
        .unwrap();
    file.write(0, b"hello").await.unwrap();
    file.write(5, b" world").await.unwrap();
    file.truncate(11).await.unwrap();
    let text = file.read(0, 11).await.unwrap();
    assert_eq!(String::from_utf8(text.to_vec()).unwrap(), "hello world");
    if file.read(0, 12).await.is_ok() {
        panic!("file is too big. read past the end should have failed");
    };
    let mut c_file = std::fs::File::open(dir.path().join("11.db")).unwrap();
    let mut c_contents = String::new();
    c_file.read_to_string(&mut c_contents).unwrap();
    assert_eq!(c_contents, "hello world");
}

#[async_test]
async fn auto_sync_with_sync_call() {
    let dir = Builder::new()
        .prefix("random-access-disk")
        .tempdir()
        .unwrap();
    let file = rad::RandomAccessDisk::builder(dir.path().join("12.db"))
        .build()
        .await
        .unwrap();
    file.write(0, b"hello").await.unwrap();
    file.write(5, b" world").await.unwrap();
    file.truncate(11).await.unwrap();
    file.sync_all().await.unwrap();
    let text = file.read(0, 11).await.unwrap();
    assert_eq!(String::from_utf8(text.to_vec()).unwrap(), "hello world");
    if file.read(0, 12).await.is_ok() {
        panic!("file is too big. read past the end should have failed");
    };
    let mut c_file = std::fs::File::open(dir.path().join("12.db")).unwrap();
    let mut c_contents = String::new();
    c_file.read_to_string(&mut c_contents).unwrap();
    assert_eq!(c_contents, "hello world");
}

#[async_test]
async fn can_del_short() {
    let dir = Builder::new()
        .prefix("random-access-disk")
        .tempdir()
        .unwrap();
    let file = rad::RandomAccessDisk::builder(dir.path().join("13.db"))
        .build()
        .await
        .unwrap();
    file.write(0, b"hello").await.unwrap();
    file.write(5, b" world").await.unwrap();
    file.write(11, b" people").await.unwrap();
    file.del(5, 6).await.unwrap();
    let hello = file.read(0, 5).await.unwrap();
    assert_eq!(String::from_utf8(hello.to_vec()).unwrap(), "hello");
    let zeros = file.read(5, 6).await.unwrap();
    assert_eq!(zeros, vec![0; 6]);
    let people = file.read(12, 6).await.unwrap();
    assert_eq!(String::from_utf8(people.to_vec()).unwrap(), "people");
}

#[async_test]
async fn can_del_long_middle() {
    let dir = Builder::new()
        .prefix("random-access-disk")
        .tempdir()
        .unwrap();
    let file = rad::RandomAccessDisk::builder(dir.path().join("14.db"))
        .build()
        .await
        .unwrap();
    file.write(0, b"hello").await.unwrap();
    const MULTI_BLOCK_LEN: usize = 4096 * 3;
    let multi_block = &[0x61_u8; MULTI_BLOCK_LEN];
    file.write(5, multi_block).await.unwrap();
    file.write((MULTI_BLOCK_LEN + 5) as u64, b"to all the ")
        .await
        .unwrap();
    file.write((MULTI_BLOCK_LEN + 16) as u64, b"people")
        .await
        .unwrap();
    file.del(5, MULTI_BLOCK_LEN as u64).await.unwrap();
    let hello = file.read(0, 5).await.unwrap();
    assert_eq!(String::from_utf8(hello.to_vec()).unwrap(), "hello");
    let zeros = file.read(5, 10).await.unwrap();
    assert_eq!(zeros, vec![0; 10]);
    let zeros = file.read(MULTI_BLOCK_LEN as u64, 5).await.unwrap();
    assert_eq!(zeros, vec![0; 5]);
    let zeros = file.read((MULTI_BLOCK_LEN / 2) as u64, 5).await.unwrap();
    assert_eq!(zeros, vec![0; 5]);
    let to_all_the_people = file.read((MULTI_BLOCK_LEN + 5) as u64, 17).await.unwrap();
    assert_eq!(
        String::from_utf8(to_all_the_people.to_vec()).unwrap(),
        "to all the people"
    );
    file.del((MULTI_BLOCK_LEN + 7) as u64, 4).await.unwrap();
    let zeros = file.read((MULTI_BLOCK_LEN + 7) as u64, 4).await.unwrap();
    assert_eq!(zeros, vec![0; 4]);
    let to = file.read((MULTI_BLOCK_LEN + 5) as u64, 2).await.unwrap();
    assert_eq!(String::from_utf8(to.to_vec()).unwrap(), "to");
}

#[async_test]
async fn can_del_long_exact_block() {
    let dir = Builder::new()
        .prefix("random-access-disk")
        .tempdir()
        .unwrap();
    let file = rad::RandomAccessDisk::builder(dir.path().join("15.db"))
        .build()
        .await
        .unwrap();
    const BLOCK_LEN: usize = 4096;
    let block = &[0x61_u8; BLOCK_LEN + 1];
    file.write(0, block).await.unwrap();
    file.del(0, BLOCK_LEN as u64).await.unwrap();
    let zeros = file.read(0, 5).await.unwrap();
    assert_eq!(zeros, vec![0; 5]);
    let zeros = file.read(BLOCK_LEN as u64 - 5, 5).await.unwrap();
    assert_eq!(zeros, vec![0; 5]);
    file.del(0, (BLOCK_LEN + 1) as u64).await.unwrap();
    assert_eq!(0, file.len());
}

#[async_test]
async fn can_del_long_more_than_block() {
    let dir = Builder::new()
        .prefix("random-access-disk")
        .tempdir()
        .unwrap();
    let file = rad::RandomAccessDisk::builder(dir.path().join("16.db"))
        .build()
        .await
        .unwrap();
    file.write(0, b"hello").await.unwrap();
    const MORE_THAN_BLOCK_LEN: usize = 4096 + 1000;
    let more_than_block = &[0x61_u8; MORE_THAN_BLOCK_LEN + 1];
    file.write(5, more_than_block).await.unwrap();
    file.del(5, MORE_THAN_BLOCK_LEN as u64).await.unwrap();
    let zeros = file.read(5, 5).await.unwrap();
    assert_eq!(zeros, vec![0; 5]);
    let zeros = file.read(MORE_THAN_BLOCK_LEN as u64, 5).await.unwrap();
    assert_eq!(zeros, vec![0; 5]);

    const EXACT_TO_THIRD_BLOCK_LEN: usize = 4096 * 2 - 5;
    let exact_to_third_block = &[0x61_u8; EXACT_TO_THIRD_BLOCK_LEN + 1];
    file.write(5, exact_to_third_block).await.unwrap();
    file.del(5, EXACT_TO_THIRD_BLOCK_LEN as u64).await.unwrap();
    let zeros = file.read(5, 5).await.unwrap();
    assert_eq!(zeros, vec![0; 5]);
    let zeros = file.read(EXACT_TO_THIRD_BLOCK_LEN as u64, 5).await.unwrap();
    assert_eq!(zeros, vec![0; 5]);
    file.del(5, (EXACT_TO_THIRD_BLOCK_LEN * 2) as u64)
        .await
        .unwrap();
    assert_eq!(5, file.len());
}