liteboxfs 0.2.0

A modern POSIX filesystem in a SQLite database
Documentation
use super::{TEST_BLOCK_SIZE, random_buf};
use crate::{
    block::{
        logical::{LogicalBlockStore, LogicalBlockStoreAdapter},
        store::{BlockStore, BlockStoreInput},
        types::{BlockData, FileId},
    },
    testing::Case,
    testing::MemoryBlockStore,
};

use xpct::{be_none, be_ok, be_some, equal, expect, have_len, match_pattern, pattern};

const LOGICAL_BLOCK_SIZE: usize = TEST_BLOCK_SIZE * 2;

#[cfg(test)]
mod get_block_at_index {
    use super::*;

    #[test]
    fn single_physical_block_aligned() {
        let mut block_store = MemoryBlockStore::new();
        let file_id = FileId::from(1);

        let data = random_buf(LOGICAL_BLOCK_SIZE, Case::Lower);
        expect!(block_store.insert_block(file_id, 0, data.as_slice().into())).to(be_ok());

        let block_list = expect!(block_store.list_blocks(file_id))
            .to(be_ok())
            .into_inner();

        let mut adapter = LogicalBlockStoreAdapter::new(block_store, LOGICAL_BLOCK_SIZE);

        let result = expect!(adapter.get_block_at_index(0, &block_list))
            .to(be_ok())
            .to(be_some())
            .into_inner();

        match result.as_ref() {
            BlockData::Data { bytes } => {
                expect!(bytes).to(have_len(LOGICAL_BLOCK_SIZE));
                expect!(bytes).to(equal(&data));
            }
            BlockData::Hole { .. } => panic!("Expected Data, got Hole"),
        }
    }

    #[test]
    fn single_physical_block_partial() {
        let mut block_store = MemoryBlockStore::new();
        let file_id = FileId::from(1);

        let data = random_buf(TEST_BLOCK_SIZE, Case::Lower);
        expect!(block_store.insert_block(file_id, 0, data.as_slice().into())).to(be_ok());

        let block_list = expect!(block_store.list_blocks(file_id))
            .to(be_ok())
            .into_inner();

        let mut adapter = LogicalBlockStoreAdapter::new(block_store, LOGICAL_BLOCK_SIZE);

        let result = expect!(adapter.get_block_at_index(0, &block_list))
            .to(be_ok())
            .to(be_some())
            .into_inner();

        match result.as_ref() {
            BlockData::Data { bytes } => {
                expect!(bytes).to(have_len(LOGICAL_BLOCK_SIZE));
                expect!(&bytes[..TEST_BLOCK_SIZE]).to(equal(&data[..]));
                expect!(&bytes[TEST_BLOCK_SIZE..]).to(equal(&vec![0u8; TEST_BLOCK_SIZE][..]));
            }
            BlockData::Hole { .. } => panic!("Expected Data, got Hole"),
        }
    }

    #[test]
    fn logical_block_beyond_eof() {
        let mut block_store = MemoryBlockStore::new();
        let file_id = FileId::from(1);

        let data = random_buf(TEST_BLOCK_SIZE, Case::Lower);
        expect!(block_store.insert_block(file_id, 0, data.as_slice().into())).to(be_ok());

        let block_list = expect!(block_store.list_blocks(file_id))
            .to(be_ok())
            .into_inner();

        let mut adapter = LogicalBlockStoreAdapter::new(block_store, LOGICAL_BLOCK_SIZE);

        expect!(adapter.get_block_at_index(5, &block_list))
            .to(be_ok())
            .to(be_none());
    }

    #[test]
    fn multiple_blocks_in_single_logical() {
        let mut block_store = MemoryBlockStore::new();
        let file_id = FileId::from(1);

        let data1 = random_buf(TEST_BLOCK_SIZE, Case::Lower);
        let data2 = random_buf(TEST_BLOCK_SIZE, Case::Lower);
        expect!(block_store.insert_block(file_id, 0, data1.as_slice().into())).to(be_ok());
        expect!(block_store.insert_block(file_id, 1, data2.as_slice().into())).to(be_ok());

        let block_list = expect!(block_store.list_blocks(file_id))
            .to(be_ok())
            .into_inner();

        let mut adapter = LogicalBlockStoreAdapter::new(block_store, LOGICAL_BLOCK_SIZE);

        let result = expect!(adapter.get_block_at_index(0, &block_list))
            .to(be_ok())
            .to(be_some())
            .into_inner();

        match result.as_ref() {
            BlockData::Data { bytes } => {
                expect!(bytes).to(have_len(LOGICAL_BLOCK_SIZE));
                expect!(&bytes[..TEST_BLOCK_SIZE]).to(equal(&data1[..]));
                expect!(&bytes[TEST_BLOCK_SIZE..]).to(equal(&data2[..]));
            }
            BlockData::Hole { .. } => panic!("Expected Data, got Hole"),
        }
    }

    #[test]
    fn logical_block_middle_of_file() {
        let mut block_store = MemoryBlockStore::new();
        let file_id = FileId::from(1);

        for i in 0..4 {
            let data = random_buf(TEST_BLOCK_SIZE, Case::Lower);
            expect!(block_store.insert_block(file_id, i, data.as_slice().into())).to(be_ok());
        }

        let block_list = expect!(block_store.list_blocks(file_id))
            .to(be_ok())
            .into_inner();

        let mut adapter = LogicalBlockStoreAdapter::new(block_store, LOGICAL_BLOCK_SIZE);

        let result = expect!(adapter.get_block_at_index(1, &block_list))
            .to(be_ok())
            .to(be_some())
            .into_inner();

        match result.as_ref() {
            BlockData::Data { bytes } => {
                expect!(bytes).to(have_len(LOGICAL_BLOCK_SIZE));
            }
            BlockData::Hole { .. } => panic!("Expected Data, got Hole"),
        }
    }

    #[test]
    fn entire_logical_block_is_hole() {
        let mut block_store = MemoryBlockStore::new();
        let file_id = FileId::from(1);

        expect!(block_store.insert_block(
            file_id,
            0,
            BlockStoreInput::Hole {
                len: LOGICAL_BLOCK_SIZE as u64
            }
        ))
        .to(be_ok());

        let block_list = expect!(block_store.list_blocks(file_id))
            .to(be_ok())
            .into_inner();

        let mut adapter = LogicalBlockStoreAdapter::new(block_store, LOGICAL_BLOCK_SIZE);

        expect!(adapter.get_block_at_index(0, &block_list))
            .to(be_ok())
            .to(be_some())
            .into()
            .to(match_pattern(
                pattern!(BlockData::Hole { len } if *len == LOGICAL_BLOCK_SIZE as u64),
            ));
    }

    #[test]
    fn logical_block_spans_data_and_hole() {
        let mut block_store = MemoryBlockStore::new();
        let file_id = FileId::from(1);

        let data = random_buf(TEST_BLOCK_SIZE, Case::Lower);
        expect!(block_store.insert_block(file_id, 0, data.as_slice().into())).to(be_ok());
        expect!(block_store.insert_block(
            file_id,
            1,
            BlockStoreInput::Hole {
                len: TEST_BLOCK_SIZE as u64
            }
        ))
        .to(be_ok());

        let block_list = expect!(block_store.list_blocks(file_id))
            .to(be_ok())
            .into_inner();

        let mut adapter = LogicalBlockStoreAdapter::new(block_store, LOGICAL_BLOCK_SIZE);

        let result = expect!(adapter.get_block_at_index(0, &block_list))
            .to(be_ok())
            .to(be_some())
            .into_inner();

        match result.as_ref() {
            BlockData::Data { bytes } => {
                expect!(bytes).to(have_len(LOGICAL_BLOCK_SIZE));
                expect!(&bytes[..TEST_BLOCK_SIZE]).to(equal(&data[..]));
                expect!(&bytes[TEST_BLOCK_SIZE..]).to(equal(&vec![0u8; TEST_BLOCK_SIZE][..]));
            }
            BlockData::Hole { .. } => panic!("Expected Data, got Hole"),
        }
    }

    #[test]
    fn logical_block_spans_hole_and_data() {
        let mut block_store = MemoryBlockStore::new();
        let file_id = FileId::from(1);

        expect!(block_store.insert_block(
            file_id,
            0,
            BlockStoreInput::Hole {
                len: TEST_BLOCK_SIZE as u64
            }
        ))
        .to(be_ok());
        let data = random_buf(TEST_BLOCK_SIZE, Case::Lower);
        expect!(block_store.insert_block(file_id, 1, data.as_slice().into())).to(be_ok());

        let block_list = expect!(block_store.list_blocks(file_id))
            .to(be_ok())
            .into_inner();

        let mut adapter = LogicalBlockStoreAdapter::new(block_store, LOGICAL_BLOCK_SIZE);

        let result = expect!(adapter.get_block_at_index(0, &block_list))
            .to(be_ok())
            .to(be_some())
            .into_inner();

        match result.as_ref() {
            BlockData::Data { bytes } => {
                expect!(bytes).to(have_len(LOGICAL_BLOCK_SIZE));
                expect!(&bytes[..TEST_BLOCK_SIZE]).to(equal(&vec![0u8; TEST_BLOCK_SIZE][..]));
                expect!(&bytes[TEST_BLOCK_SIZE..]).to(equal(&data[..]));
            }
            BlockData::Hole { .. } => panic!("Expected Data, got Hole"),
        }
    }

    #[test]
    fn logical_block_at_exact_boundary() {
        let mut block_store = MemoryBlockStore::new();
        let file_id = FileId::from(1);

        for i in 0..4 {
            let data = random_buf(TEST_BLOCK_SIZE, Case::Lower);
            expect!(block_store.insert_block(file_id, i, data.as_slice().into())).to(be_ok());
        }

        let block_list = expect!(block_store.list_blocks(file_id))
            .to(be_ok())
            .into_inner();

        let mut adapter = LogicalBlockStoreAdapter::new(block_store, LOGICAL_BLOCK_SIZE);

        let result = expect!(adapter.get_block_at_index(1, &block_list))
            .to(be_ok())
            .to(be_some())
            .into_inner();

        match result.as_ref() {
            BlockData::Data { bytes } => {
                expect!(bytes).to(have_len(LOGICAL_BLOCK_SIZE));
            }
            BlockData::Hole { .. } => panic!("Expected Data, got Hole"),
        }
    }

    #[test]
    fn logical_block_partial_at_eof() {
        let mut block_store = MemoryBlockStore::new();
        let file_id = FileId::from(1);

        let data1 = random_buf(TEST_BLOCK_SIZE, Case::Lower);
        let data2 = random_buf(TEST_BLOCK_SIZE, Case::Lower);
        expect!(block_store.insert_block(file_id, 0, data1.as_slice().into())).to(be_ok());
        expect!(block_store.insert_block(file_id, 1, data2.as_slice().into())).to(be_ok());

        let data3 = random_buf(4, Case::Lower);
        expect!(block_store.insert_block(file_id, 2, data3.as_slice().into())).to(be_ok());

        let block_list = expect!(block_store.list_blocks(file_id))
            .to(be_ok())
            .into_inner();

        let mut adapter = LogicalBlockStoreAdapter::new(block_store, LOGICAL_BLOCK_SIZE);

        let result = expect!(adapter.get_block_at_index(1, &block_list))
            .to(be_ok())
            .to(be_some())
            .into_inner();

        match result.as_ref() {
            BlockData::Data { bytes } => {
                expect!(bytes).to(have_len(LOGICAL_BLOCK_SIZE));
                expect!(&bytes[..4]).to(equal(&data3[..]));
                expect!(&bytes[4..]).to(equal(&vec![0u8; 12][..]));
            }
            BlockData::Hole { .. } => panic!("Expected Data, got Hole"),
        }
    }
}