use xpct::{be_ok, be_some, equal, expect, fields, have_len, match_elements, match_fields};
use crate::{
block::{
adapter::ChunkingBlockStoreAdapter,
store::DataStore,
store::{BlockStore, BlockStoreInput},
types::{Block, FileId, FileOffset, HoleLen},
},
hash::BlockHasher,
testing::{Case, MemoryBlockStore, random_buf, random_string},
};
use super::{BlockSignature, BlockStoreTestExt, DataBlock, TEST_BLOCK_SIZE};
#[test]
fn write_zero_bytes_is_noop() {
let mut block_store = MemoryBlockStore::new();
let file_id = FileId::from(1);
let DataBlock {
id: original_id,
signature: original_sig,
..
} = expect!(block_store.insert_data(file_id, 0))
.to(be_ok())
.into_inner();
let mut data_store = ChunkingBlockStoreAdapter::without_chunking(block_store);
let block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner();
let write_data = b"";
let block_list =
expect!(data_store.write_block(file_id, 0, block_list, write_data.as_slice().into()))
.to(be_ok())
.into_inner()
.into_iter()
.collect::<Vec<_>>();
expect!(&block_list).to(have_len(1));
expect!(block_list.clone()).to(match_elements([match_fields(fields!(Block {
id: equal(original_id),
signature: equal(original_sig),
}))]));
let actual_block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner()
.into_iter()
.collect::<Vec<_>>();
expect!(block_list).to(equal(actual_block_list));
}
#[test]
fn write_file_start_to_end() {
let mut block_store = MemoryBlockStore::new();
let file_id = FileId::from(1);
expect!(block_store.insert_data(file_id, 0))
.to(be_ok())
.into_inner();
expect!(block_store.insert_data(file_id, 1))
.to(be_ok())
.into_inner();
let mut data_store = ChunkingBlockStoreAdapter::without_chunking(block_store);
let write_len = TEST_BLOCK_SIZE * 2;
let write_data = random_buf(write_len, Case::Upper);
let write_hash = BlockHasher::hash(&write_data);
let block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner();
let block_list =
expect!(data_store.write_block(file_id, 0, block_list, write_data.as_slice().into()))
.to(be_ok())
.into_inner()
.into_iter()
.collect::<Vec<_>>();
expect!(&block_list).to(have_len(1));
expect!(block_list.clone()).to(match_elements([match_fields(fields!(Block {
signature: equal(BlockSignature::Data {
hash: write_hash,
len: write_data.len(),
}),
}))]));
let actual_block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner()
.into_iter()
.collect::<Vec<_>>();
expect!(block_list).to(equal(actual_block_list));
}
#[test]
fn write_file_start_to_block_boundary() {
let mut block_store = MemoryBlockStore::new();
let file_id = FileId::from(1);
expect!(block_store.insert_data(file_id, 0))
.to(be_ok())
.into_inner();
let DataBlock {
bytes: second_bytes,
hash: second_hash,
..
} = expect!(block_store.insert_data(file_id, 1))
.to(be_ok())
.into_inner();
let mut data_store = ChunkingBlockStoreAdapter::without_chunking(block_store);
let write_data = random_buf(TEST_BLOCK_SIZE, Case::Upper);
let write_hash = BlockHasher::hash(&write_data);
let block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner();
let block_list =
expect!(data_store.write_block(file_id, 0, block_list, write_data.as_slice().into()))
.to(be_ok())
.into_inner()
.into_iter()
.collect::<Vec<_>>();
expect!(&block_list).to(have_len(2));
expect!(block_list.clone()).to(match_elements([
match_fields(fields!(Block {
signature: equal(BlockSignature::Data {
hash: write_hash,
len: write_data.len(),
}),
})),
match_fields(fields!(Block {
signature: equal(BlockSignature::Data {
hash: second_hash,
len: second_bytes.len(),
}),
})),
]));
let actual_block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner()
.into_iter()
.collect::<Vec<_>>();
expect!(block_list).to(equal(actual_block_list));
}
#[test]
fn write_file_start_to_mid_same_block() {
let mut block_store = MemoryBlockStore::new();
let file_id = FileId::from(1);
let DataBlock { bytes, .. } = expect!(block_store.insert_data(file_id, 0))
.to(be_ok())
.into_inner();
let mut data_store = ChunkingBlockStoreAdapter::without_chunking(block_store);
let block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner();
let new_bytes = b"XXX";
let block_list =
expect!(data_store.write_block(file_id, 0, block_list, new_bytes.as_slice().into()))
.to(be_ok())
.into_inner()
.into_iter()
.collect::<Vec<_>>();
let expected_bytes = [new_bytes, &bytes[new_bytes.len()..]].concat();
expect!(block_list.clone()).to(match_elements([match_fields(fields!(Block {
signature: equal(BlockSignature::Data {
hash: BlockHasher::hash(&expected_bytes),
len: expected_bytes.len(),
}),
}))]));
let actual_block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner()
.into_iter()
.collect::<Vec<_>>();
expect!(block_list).to(equal(actual_block_list));
}
#[test]
fn write_file_start_to_mid_different_block() {
let mut block_store = MemoryBlockStore::new();
let file_id = FileId::from(1);
expect!(block_store.insert_data(file_id, 0))
.to(be_ok())
.into_inner();
let DataBlock {
bytes: second_bytes,
..
} = expect!(block_store.insert_data(file_id, 1))
.to(be_ok())
.into_inner();
let mut data_store = ChunkingBlockStoreAdapter::without_chunking(block_store);
let write_len = TEST_BLOCK_SIZE + TEST_BLOCK_SIZE / 2;
let write_data = random_buf(write_len, Case::Upper);
let block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner();
let block_list =
expect!(data_store.write_block(file_id, 0, block_list, write_data.as_slice().into()))
.to(be_ok())
.into_inner()
.into_iter()
.collect::<Vec<_>>();
let expected_bytes = [&write_data, &second_bytes[TEST_BLOCK_SIZE / 2..]].concat();
let expected_hash = BlockHasher::hash(&expected_bytes);
expect!(block_list.clone()).to(match_elements([match_fields(fields!(Block {
signature: equal(BlockSignature::Data {
hash: expected_hash,
len: expected_bytes.len(),
}),
}))]));
let actual_block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner()
.into_iter()
.collect::<Vec<_>>();
expect!(block_list).to(equal(actual_block_list));
}
#[test]
fn write_block_boundary_to_file_end() {
let mut block_store = MemoryBlockStore::new();
let file_id = FileId::from(1);
let DataBlock {
bytes: first_bytes,
hash: first_hash,
..
} = expect!(block_store.insert_data(file_id, 0))
.to(be_ok())
.into_inner();
expect!(block_store.insert_data(file_id, 1))
.to(be_ok())
.into_inner();
let mut data_store = ChunkingBlockStoreAdapter::without_chunking(block_store);
let write_offset = TEST_BLOCK_SIZE as u64;
let write_data = random_buf(TEST_BLOCK_SIZE, Case::Upper);
let write_hash = BlockHasher::hash(&write_data);
let block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner();
let block_list = expect!(data_store.write_block(
file_id,
write_offset,
block_list,
write_data.as_slice().into(),
))
.to(be_ok())
.into_inner()
.into_iter()
.collect::<Vec<_>>();
expect!(&block_list).to(have_len(2));
expect!(block_list.clone()).to(match_elements([
match_fields(fields!(Block {
signature: equal(BlockSignature::Data {
hash: first_hash,
len: first_bytes.len(),
}),
})),
match_fields(fields!(Block {
signature: equal(BlockSignature::Data {
hash: write_hash,
len: write_data.len(),
}),
})),
]));
let actual_block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner()
.into_iter()
.collect::<Vec<_>>();
expect!(block_list).to(equal(actual_block_list));
}
#[test]
fn write_block_boundary_to_block_boundary() {
let mut block_store = MemoryBlockStore::new();
let file_id = FileId::from(1);
let DataBlock {
bytes: first_bytes,
hash: first_hash,
..
} = expect!(block_store.insert_data(file_id, 0))
.to(be_ok())
.into_inner();
expect!(block_store.insert_data(file_id, 1))
.to(be_ok())
.into_inner();
let DataBlock {
bytes: third_bytes,
hash: third_hash,
..
} = expect!(block_store.insert_data(file_id, 2))
.to(be_ok())
.into_inner();
let mut data_store = ChunkingBlockStoreAdapter::without_chunking(block_store);
let block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner();
let write_offset = TEST_BLOCK_SIZE as u64;
let write_data = random_buf(TEST_BLOCK_SIZE, Case::Upper);
let write_hash = BlockHasher::hash(&write_data);
let block_list = expect!(data_store.write_block(
file_id,
write_offset,
block_list,
write_data.as_slice().into(),
))
.to(be_ok())
.into_inner()
.into_iter()
.collect::<Vec<_>>();
expect!(&block_list).to(have_len(3));
expect!(block_list.clone()).to(match_elements([
match_fields(fields!(Block {
signature: equal(BlockSignature::Data {
hash: first_hash,
len: first_bytes.len(),
}),
})),
match_fields(fields!(Block {
signature: equal(BlockSignature::Data {
hash: write_hash,
len: write_data.len(),
}),
})),
match_fields(fields!(Block {
signature: equal(BlockSignature::Data {
hash: third_hash,
len: third_bytes.len(),
}),
})),
]));
let actual_block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner()
.into_iter()
.collect::<Vec<_>>();
expect!(block_list).to(equal(actual_block_list));
}
#[test]
fn write_block_boundary_to_mid_same_block() {
let mut block_store = MemoryBlockStore::new();
let file_id = FileId::from(1);
let DataBlock {
bytes: first_bytes,
hash: first_hash,
..
} = expect!(block_store.insert_data(file_id, 0))
.to(be_ok())
.into_inner();
let DataBlock {
bytes: second_bytes,
..
} = expect!(block_store.insert_data(file_id, 1))
.to(be_ok())
.into_inner();
let mut data_store = ChunkingBlockStoreAdapter::without_chunking(block_store);
let block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner();
let write_offset = TEST_BLOCK_SIZE as u64;
let write_len = TEST_BLOCK_SIZE / 2;
let write_data = random_buf(write_len, Case::Upper);
let block_list = expect!(data_store.write_block(
file_id,
write_offset,
block_list,
write_data.as_slice().into()
))
.to(be_ok())
.into_inner()
.into_iter()
.collect::<Vec<_>>();
let expected_second_bytes = [&write_data, &second_bytes[write_len..]].concat();
let expected_second_hash = BlockHasher::hash(&expected_second_bytes);
expect!(&block_list).to(have_len(2));
expect!(block_list.clone()).to(match_elements([
match_fields(fields!(Block {
signature: equal(BlockSignature::Data {
hash: first_hash,
len: first_bytes.len(),
}),
})),
match_fields(fields!(Block {
signature: equal(BlockSignature::Data {
hash: expected_second_hash,
len: expected_second_bytes.len(),
}),
})),
]));
let actual_block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner()
.into_iter()
.collect::<Vec<_>>();
expect!(block_list).to(equal(actual_block_list));
}
#[test]
fn write_block_boundary_to_mid_different_block() {
let mut block_store = MemoryBlockStore::new();
let file_id = FileId::from(1);
let DataBlock {
bytes: first_bytes,
hash: first_hash,
..
} = expect!(block_store.insert_data(file_id, 0))
.to(be_ok())
.into_inner();
let DataBlock {
bytes: second_bytes,
..
} = expect!(block_store.insert_data(file_id, 1))
.to(be_ok())
.into_inner();
let mut data_store = ChunkingBlockStoreAdapter::without_chunking(block_store);
let block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner();
let write_offset = TEST_BLOCK_SIZE as u64;
let write_len = TEST_BLOCK_SIZE / 2;
let write_data = random_buf(write_len, Case::Upper);
let block_list = expect!(data_store.write_block(
file_id,
write_offset,
block_list,
write_data.as_slice().into()
))
.to(be_ok())
.into_inner()
.into_iter()
.collect::<Vec<_>>();
let expected_second_bytes = [&write_data, &second_bytes[write_len..]].concat();
let expected_second_hash = BlockHasher::hash(&expected_second_bytes);
expect!(block_list.clone()).to(match_elements([
match_fields(fields!(Block {
signature: equal(BlockSignature::Data {
hash: first_hash,
len: first_bytes.len(),
}),
})),
match_fields(fields!(Block {
signature: equal(BlockSignature::Data {
hash: expected_second_hash,
len: expected_second_bytes.len(),
}),
})),
]));
let actual_block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner()
.into_iter()
.collect::<Vec<_>>();
expect!(block_list).to(equal(actual_block_list));
}
#[test]
fn write_mid_block_to_file_end() {
let mut block_store = MemoryBlockStore::new();
let file_id = FileId::from(1);
let DataBlock {
bytes: first_bytes, ..
} = expect!(block_store.insert_data(file_id, 0))
.to(be_ok())
.into_inner();
expect!(block_store.insert_data(file_id, 1))
.to(be_ok())
.into_inner();
let mut data_store = ChunkingBlockStoreAdapter::without_chunking(block_store);
let block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner();
let write_offset = TEST_BLOCK_SIZE as u64 / 2;
let write_data = random_buf(
(TEST_BLOCK_SIZE - write_offset as usize) + TEST_BLOCK_SIZE,
Case::Upper,
);
let block_list = expect!(data_store.write_block(
file_id,
write_offset,
block_list,
write_data.as_slice().into()
))
.to(be_ok())
.into_inner()
.into_iter()
.collect::<Vec<_>>();
let expected_bytes = [&first_bytes[0..write_offset as usize], &write_data].concat();
let expected_hash = BlockHasher::hash(&expected_bytes);
expect!(&block_list).to(have_len(1));
expect!(block_list.clone()).to(match_elements([match_fields(fields!(Block {
signature: equal(BlockSignature::Data {
hash: expected_hash,
len: expected_bytes.len(),
}),
}))]));
let actual_block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner()
.into_iter()
.collect::<Vec<_>>();
expect!(block_list).to(equal(actual_block_list));
}
#[test]
fn write_mid_block_to_block_boundary() {
let mut block_store = MemoryBlockStore::new();
let file_id = FileId::from(1);
let DataBlock {
bytes: first_bytes, ..
} = expect!(block_store.insert_data(file_id, 0))
.to(be_ok())
.into_inner();
let DataBlock {
bytes: second_bytes,
hash: second_hash,
..
} = expect!(block_store.insert_data(file_id, 1))
.to(be_ok())
.into_inner();
let mut data_store = ChunkingBlockStoreAdapter::without_chunking(block_store);
let block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner();
let write_offset = TEST_BLOCK_SIZE as u64 / 2;
let write_len = TEST_BLOCK_SIZE / 2;
let write_data = random_buf(write_len, Case::Upper);
let block_list = expect!(data_store.write_block(
file_id,
write_offset,
block_list,
write_data.as_slice().into()
))
.to(be_ok())
.into_inner()
.into_iter()
.collect::<Vec<_>>();
let expected_first_bytes = [&first_bytes[0..write_offset as usize], &write_data].concat();
let expected_first_hash = BlockHasher::hash(&expected_first_bytes);
expect!(block_list.clone()).to(match_elements([
match_fields(fields!(Block {
signature: equal(BlockSignature::Data {
hash: expected_first_hash,
len: expected_first_bytes.len(),
}),
})),
match_fields(fields!(Block {
signature: equal(BlockSignature::Data {
hash: second_hash,
len: second_bytes.len(),
}),
})),
]));
let actual_block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner()
.into_iter()
.collect::<Vec<_>>();
expect!(block_list).to(equal(actual_block_list));
}
#[test]
fn write_mid_block_to_mid_same_block() {
let mut block_store = MemoryBlockStore::new();
let file_id = FileId::from(1);
let DataBlock { bytes: block, .. } = expect!(block_store.insert_data(file_id, 0))
.to(be_ok())
.into_inner();
let mut data_store = ChunkingBlockStoreAdapter::without_chunking(block_store);
let block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner();
let write_offset = TEST_BLOCK_SIZE as u64 - 3;
let write_data = b"XX";
let block_list = expect!(data_store.write_block(
file_id,
write_offset,
block_list,
write_data.as_slice().into()
))
.to(be_ok())
.into_inner()
.into_iter()
.collect::<Vec<_>>();
let expected_bytes = &[
&block[0..write_offset as usize],
write_data,
&block[(write_offset as usize + write_data.len())..],
]
.concat();
let expected_hash = BlockHasher::hash(expected_bytes);
expect!(block_list.clone()).to(match_elements([match_fields(fields!(Block {
signature: equal(BlockSignature::Data {
hash: expected_hash,
len: expected_bytes.len(),
}),
}))]));
let actual_block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner()
.into_iter()
.collect::<Vec<_>>();
expect!(block_list).to(equal(actual_block_list));
}
#[test]
fn write_mid_block_to_mid_different_block() {
let mut block_store = MemoryBlockStore::new();
let file_id = FileId::from(1);
let DataBlock {
bytes: first_bytes, ..
} = expect!(block_store.insert_data(file_id, 0))
.to(be_ok())
.into_inner();
expect!(block_store.insert_data(file_id, 1))
.to(be_ok())
.into_inner();
let DataBlock {
bytes: third_bytes, ..
} = expect!(block_store.insert_data(file_id, 2))
.to(be_ok())
.into_inner();
let mut data_store = ChunkingBlockStoreAdapter::without_chunking(block_store);
let block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner();
let write_offset = TEST_BLOCK_SIZE as u64 / 2;
let write_len = TEST_BLOCK_SIZE * 2;
let write_data = random_buf(write_len, Case::Upper);
let block_list = expect!(data_store.write_block(
file_id,
write_offset,
block_list,
write_data.as_slice().into()
))
.to(be_ok())
.into_inner()
.into_iter()
.collect::<Vec<_>>();
let expected_block = &[
&first_bytes[0..write_offset as usize],
&write_data,
&third_bytes[TEST_BLOCK_SIZE / 2..],
]
.concat();
let expected_hash = BlockHasher::hash(expected_block);
expect!(block_list.clone()).to(match_elements([match_fields(fields!(Block {
signature: equal(BlockSignature::Data {
hash: expected_hash,
len: expected_block.len(),
}),
}))]));
let actual_block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner()
.into_iter()
.collect::<Vec<_>>();
expect!(block_list).to(equal(actual_block_list));
}
#[test]
fn write_append() {
let mut block_store = MemoryBlockStore::new();
let file_id = FileId::from(1);
let DataBlock {
id: original_id, ..
} = expect!(block_store.insert_data(file_id, 0))
.to(be_ok())
.into_inner();
let mut data_store = ChunkingBlockStoreAdapter::without_chunking(block_store);
let block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner();
let write_data = b"APPEND";
let write_hash = BlockHasher::hash(write_data);
let block_list = expect!(data_store.write_block(
file_id,
TEST_BLOCK_SIZE as u64,
block_list,
write_data.as_slice().into()
))
.to(be_ok())
.into_inner()
.into_iter()
.collect::<Vec<_>>();
expect!(&block_list).to(have_len(2));
expect!(block_list.clone()).to(match_elements([
match_fields(fields!(Block {
id: equal(original_id),
})),
match_fields(fields!(Block {
signature: equal(BlockSignature::Data {
hash: write_hash,
len: write_data.len(),
}),
})),
]));
let actual_block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner()
.into_iter()
.collect::<Vec<_>>();
expect!(block_list).to(equal(actual_block_list));
}
#[test]
fn write_append_with_hole() {
let mut block_store = MemoryBlockStore::new();
let file_id = FileId::from(1);
let DataBlock {
id: original_id, ..
} = expect!(block_store.insert_data(file_id, 0))
.to(be_ok())
.into_inner();
let mut data_store = ChunkingBlockStoreAdapter::without_chunking(block_store);
let block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner();
let gap_size = 10;
let write_offset = TEST_BLOCK_SIZE + gap_size;
let write_data = b"GAPPED";
let write_hash = BlockHasher::hash(write_data);
let block_list = expect!(data_store.write_block(
file_id,
write_offset as FileOffset,
block_list,
write_data.as_slice().into()
))
.to(be_ok())
.into_inner()
.into_iter()
.collect::<Vec<_>>();
expect!(&block_list).to(have_len(3));
expect!(block_list.clone()).to(match_elements([
match_fields(fields!(Block {
id: equal(original_id),
})),
match_fields(fields!(Block {
signature: equal(BlockSignature::Hole {
len: gap_size as HoleLen
}),
})),
match_fields(fields!(Block {
signature: equal(BlockSignature::Data {
hash: write_hash,
len: write_data.len(),
}),
})),
]));
let actual_block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner()
.into_iter()
.collect::<Vec<_>>();
expect!(block_list).to(equal(actual_block_list));
}
#[test]
fn write_hole_into_existing_data() {
let mut block_store = MemoryBlockStore::new();
let file_id = FileId::from(1);
expect!(block_store.insert_block(file_id, 0, b"AAAAAAAA".as_slice().into())).to(be_ok());
expect!(block_store.insert_block(file_id, 1, b"BBBBBBBB".as_slice().into())).to(be_ok());
let mut data_store = ChunkingBlockStoreAdapter::without_chunking(block_store);
let block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner();
let hole_len = 5;
let block_list = expect!(data_store.write_block(
file_id,
4,
block_list,
BlockStoreInput::Hole { len: hole_len }
))
.to(be_ok())
.into_inner()
.into_iter()
.collect::<Vec<_>>();
expect!(&block_list).to(have_len(3));
expect!(block_list.clone()).to(match_elements([
match_fields(fields!(Block {
signature: equal(BlockSignature::Data {
hash: BlockHasher::hash(b"AAAA"),
len: 4,
}),
})),
match_fields(fields!(Block {
signature: equal(BlockSignature::Hole { len: hole_len }),
})),
match_fields(fields!(Block {
signature: equal(BlockSignature::Data {
hash: BlockHasher::hash(b"BBBBBBB"),
len: 7,
}),
})),
]));
let actual_block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner()
.into_iter()
.collect::<Vec<_>>();
expect!(block_list).to(equal(actual_block_list));
}
#[test]
fn write_hole_at_file_start() {
let mut block_store = MemoryBlockStore::new();
let file_id = FileId::from(1);
expect!(block_store.insert_block(file_id, 0, b"AAAAAAAA".as_slice().into())).to(be_ok());
let mut data_store = ChunkingBlockStoreAdapter::without_chunking(block_store);
let block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner();
let hole_len = 3;
let block_list = expect!(data_store.write_block(
file_id,
0,
block_list,
BlockStoreInput::Hole { len: hole_len }
))
.to(be_ok())
.into_inner()
.into_iter()
.collect::<Vec<_>>();
expect!(&block_list).to(have_len(3));
expect!(block_list.get(1))
.to(be_some())
.map(|b| &b.signature)
.to(equal(&BlockSignature::Hole { len: hole_len }));
}
#[test]
fn write_hole_at_file_end() {
let mut block_store = MemoryBlockStore::new();
let file_id = FileId::from(1);
expect!(block_store.insert_block(file_id, 0, b"AAAAAAAA".as_slice().into())).to(be_ok());
let mut data_store = ChunkingBlockStoreAdapter::without_chunking(block_store);
let block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner();
let hole_len = 5;
let block_list = expect!(data_store.write_block(
file_id,
4,
block_list,
BlockStoreInput::Hole { len: hole_len }
))
.to(be_ok())
.into_inner()
.into_iter()
.collect::<Vec<_>>();
expect!(&block_list).to(have_len(3));
expect!(block_list.get(0))
.to(be_some())
.map(|b| &b.signature)
.to(equal(&BlockSignature::Data {
hash: BlockHasher::hash(b"AAAA"),
len: 4,
}));
expect!(block_list.get(1))
.to(be_some())
.map(|b| &b.signature)
.to(equal(&BlockSignature::Hole { len: hole_len }));
}
#[test]
fn write_hole_spanning_multiple_blocks() {
let mut block_store = MemoryBlockStore::new();
let file_id = FileId::from(1);
expect!(block_store.insert_block(file_id, 0, b"AAAAAAAA".as_slice().into())).to(be_ok());
expect!(block_store.insert_block(file_id, 1, b"BBBBBBBB".as_slice().into())).to(be_ok());
expect!(block_store.insert_block(file_id, 2, b"CCCCCCCC".as_slice().into())).to(be_ok());
let mut data_store = ChunkingBlockStoreAdapter::without_chunking(block_store);
let block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner();
let hole_len = 10;
let block_list = expect!(data_store.write_block(
file_id,
4,
block_list,
BlockStoreInput::Hole { len: hole_len }
))
.to(be_ok())
.into_inner()
.into_iter()
.collect::<Vec<_>>();
expect!(&block_list).to(have_len(4));
expect!(block_list.clone()).to(match_elements([
match_fields(fields!(Block {
signature: equal(BlockSignature::Data {
hash: BlockHasher::hash(b"AAAA"),
len: 4,
}),
})),
match_fields(fields!(Block {
signature: equal(BlockSignature::Hole { len: hole_len }),
})),
match_fields(fields!(Block {
signature: equal(BlockSignature::Data {
hash: BlockHasher::hash(b"BB"),
len: 2,
}),
})),
match_fields(fields!(Block {
signature: equal(BlockSignature::Data {
hash: BlockHasher::hash(b"CCCCCCCC"),
len: 8,
}),
})),
]));
}
#[test]
fn write_data_into_existing_hole_mid_block() {
let mut block_store = MemoryBlockStore::new();
let file_id = FileId::from(1);
let hole_len: HoleLen = 32;
expect!(block_store.insert_block(file_id, 0, BlockStoreInput::Hole { len: hole_len }))
.to(be_ok());
let mut data_store = ChunkingBlockStoreAdapter::without_chunking(block_store);
let block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner();
let write_data = b"DATA";
let block_list =
expect!(data_store.write_block(file_id, 8, block_list, write_data.as_slice().into()))
.to(be_ok())
.into_inner()
.into_iter()
.collect::<Vec<_>>();
expect!(&block_list).to(have_len(3));
expect!(block_list).to(match_elements([
match_fields(fields!(Block {
signature: equal(BlockSignature::Hole { len: 8 }),
})),
match_fields(fields!(Block {
signature: equal(BlockSignature::Data {
hash: BlockHasher::hash(b"DATA"),
len: 4,
}),
})),
match_fields(fields!(Block {
signature: equal(BlockSignature::Hole { len: 20 }),
})),
]));
}
#[test]
fn write_data_at_start_of_existing_hole() {
let mut block_store = MemoryBlockStore::new();
let file_id = FileId::from(1);
let hole_len: HoleLen = 16;
expect!(block_store.insert_block(file_id, 0, BlockStoreInput::Hole { len: hole_len }))
.to(be_ok());
let mut data_store = ChunkingBlockStoreAdapter::without_chunking(block_store);
let block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner();
let write_data = b"DATA";
let block_list =
expect!(data_store.write_block(file_id, 0, block_list, write_data.as_slice().into()))
.to(be_ok())
.into_inner()
.into_iter()
.collect::<Vec<_>>();
expect!(&block_list).to(have_len(2));
expect!(block_list).to(match_elements([
match_fields(fields!(Block {
signature: equal(BlockSignature::Data {
hash: BlockHasher::hash(b"DATA"),
len: 4,
}),
})),
match_fields(fields!(Block {
signature: equal(BlockSignature::Hole { len: 12 }),
})),
]));
}
#[test]
fn write_data_at_end_of_existing_hole() {
let mut block_store = MemoryBlockStore::new();
let file_id = FileId::from(1);
let hole_len: HoleLen = 16;
expect!(block_store.insert_block(file_id, 0, BlockStoreInput::Hole { len: hole_len }))
.to(be_ok());
let mut data_store = ChunkingBlockStoreAdapter::without_chunking(block_store);
let block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner();
let write_data = b"DATA";
let block_list =
expect!(data_store.write_block(file_id, 12, block_list, write_data.as_slice().into()))
.to(be_ok())
.into_inner()
.into_iter()
.collect::<Vec<_>>();
expect!(&block_list).to(have_len(2));
expect!(block_list).to(match_elements([
match_fields(fields!(Block {
signature: equal(BlockSignature::Hole { len: 12 }),
})),
match_fields(fields!(Block {
signature: equal(BlockSignature::Data {
hash: BlockHasher::hash(b"DATA"),
len: 4,
}),
})),
]));
}
#[test]
fn write_data_spanning_hole_and_data_blocks() {
let mut block_store = MemoryBlockStore::new();
let file_id = FileId::from(1);
let hole_len: HoleLen = 16;
expect!(block_store.insert_block(file_id, 0, BlockStoreInput::Hole { len: hole_len }))
.to(be_ok());
expect!(block_store.insert_block(file_id, 1, b"AAAAAAAA".as_slice().into())).to(be_ok());
let mut data_store = ChunkingBlockStoreAdapter::without_chunking(block_store);
let block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner();
let write_data = b"NEWNEW";
let block_list =
expect!(data_store.write_block(file_id, 12, block_list, write_data.as_slice().into()))
.to(be_ok())
.into_inner()
.into_iter()
.collect::<Vec<_>>();
expect!(&block_list).to(have_len(3));
expect!(block_list).to(match_elements([
match_fields(fields!(Block {
signature: equal(BlockSignature::Hole { len: 12 }),
})),
match_fields(fields!(Block {
signature: equal(BlockSignature::Data {
hash: BlockHasher::hash(b"NEWNEW"),
len: 6,
}),
})),
match_fields(fields!(Block {
signature: equal(BlockSignature::Data {
hash: BlockHasher::hash(b"AAAAAA"),
len: 6,
}),
})),
]));
}
#[test]
fn write_data_spanning_data_and_hole_blocks() {
let mut block_store = MemoryBlockStore::new();
let file_id = FileId::from(1);
expect!(block_store.insert_block(file_id, 0, b"AAAAAAAA".as_slice().into())).to(be_ok());
let hole_len: HoleLen = 16;
expect!(block_store.insert_block(file_id, 1, BlockStoreInput::Hole { len: hole_len }))
.to(be_ok());
let mut data_store = ChunkingBlockStoreAdapter::without_chunking(block_store);
let block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner();
let write_data = b"NEWNEW";
let block_list =
expect!(data_store.write_block(file_id, 6, block_list, write_data.as_slice().into()))
.to(be_ok())
.into_inner()
.into_iter()
.collect::<Vec<_>>();
expect!(&block_list).to(have_len(3));
expect!(block_list).to(match_elements([
match_fields(fields!(Block {
signature: equal(BlockSignature::Data {
hash: BlockHasher::hash(b"AAAAAA"),
len: 6,
}),
})),
match_fields(fields!(Block {
signature: equal(BlockSignature::Data {
hash: BlockHasher::hash(b"NEWNEW"),
len: 6,
}),
})),
match_fields(fields!(Block {
signature: equal(BlockSignature::Hole { len: 12 }),
})),
]));
}
#[test]
fn write_spanning_multiple_data_blocks_into_trailing_hole() {
let mut block_store = MemoryBlockStore::new();
let file_id = FileId::from(1);
expect!(block_store.insert_block(file_id, 0, b"AAAAAAAA".as_slice().into())).to(be_ok());
expect!(block_store.insert_block(file_id, 1, b"BBBBBBBB".as_slice().into())).to(be_ok());
expect!(block_store.insert_block(file_id, 2, b"CCCCCCCC".as_slice().into())).to(be_ok());
expect!(block_store.insert_block(file_id, 3, b"DDDDDDDD".as_slice().into())).to(be_ok());
let hole_len: HoleLen = 32;
expect!(block_store.insert_block(file_id, 4, BlockStoreInput::Hole { len: hole_len }))
.to(be_ok());
let mut data_store = ChunkingBlockStoreAdapter::with_small_write_threshold(block_store, 1);
let block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner();
expect!(block_list.clone().file_len()).to(equal(64 as FileOffset));
let write_data = random_buf(20, Case::Upper);
let block_list =
expect!(data_store.write_block(file_id, 12, block_list, write_data.as_slice().into()))
.to(be_ok())
.into_inner();
expect!(block_list.clone().file_len()).to(equal(64 as FileOffset));
let stored = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner();
expect!(stored.clone().file_len()).to(equal(64 as FileOffset));
}
#[test]
fn write_spanning_data_into_adjacent_trailing_hole_with_large_threshold() {
let mut block_store = MemoryBlockStore::new();
let file_id = FileId::from(1);
expect!(block_store.insert_block(file_id, 0, b"AAAAAAAAAAAAAAAA".as_slice().into()))
.to(be_ok());
let hole_len: HoleLen = 32;
expect!(block_store.insert_block(file_id, 1, BlockStoreInput::Hole { len: hole_len }))
.to(be_ok());
let mut data_store = ChunkingBlockStoreAdapter::with_small_write_threshold(block_store, 1024);
let block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner();
let write_data = b"NEW12345";
let block_list =
expect!(data_store.write_block(file_id, 12, block_list, write_data.as_slice().into()))
.to(be_ok())
.into_inner()
.into_iter()
.collect::<Vec<_>>();
expect!(&block_list).to(have_len(3));
expect!(block_list).to(match_elements([
match_fields(fields!(Block {
signature: equal(BlockSignature::Data {
hash: BlockHasher::hash(b"AAAAAAAAAAAA"),
len: 12,
}),
})),
match_fields(fields!(Block {
signature: equal(BlockSignature::Data {
hash: BlockHasher::hash(b"NEW12345"),
len: 8,
}),
})),
match_fields(fields!(Block {
signature: equal(BlockSignature::Hole { len: 28 }),
})),
]));
}
#[test]
fn write_large_data_into_existing_hole_does_not_panic() {
let mut block_store = MemoryBlockStore::new();
let file_id = FileId::from(1);
let hole_len: HoleLen = 1024;
expect!(block_store.insert_block(file_id, 0, BlockStoreInput::Hole { len: hole_len }))
.to(be_ok());
let mut data_store = ChunkingBlockStoreAdapter::with_small_write_threshold(block_store, 4);
let block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner();
let write_data = random_buf(256, Case::Upper);
let block_list =
expect!(data_store.write_block(file_id, 128, block_list, write_data.as_slice().into(),))
.to(be_ok())
.into_inner()
.into_iter()
.collect::<Vec<_>>();
expect!(&block_list).to(have_len(3));
expect!(block_list).to(match_elements([
match_fields(fields!(Block {
signature: equal(BlockSignature::Hole { len: 128 }),
})),
match_fields(fields!(Block {
signature: equal(BlockSignature::Data {
hash: BlockHasher::hash(&write_data),
len: 256,
}),
})),
match_fields(fields!(Block {
signature: equal(BlockSignature::Hole { len: 640 }),
})),
]));
}
#[cfg(test)]
mod small_write_threshold {
use super::*;
#[test]
fn small_write_creates_fragments() {
let mut block_store = MemoryBlockStore::new();
let file_id = FileId::from(1);
let original_data = random_string(8, Case::Lower);
expect!(block_store.insert_block(file_id, 0, original_data.as_bytes().into())).to(be_ok());
let mut data_store =
ChunkingBlockStoreAdapter::with_small_write_threshold(block_store, 100);
let block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner();
let write_data = random_string(4, Case::Upper);
let block_list =
expect!(data_store.write_block(file_id, 2, block_list, write_data.as_bytes().into()))
.to(be_ok())
.into_inner()
.into_iter()
.collect::<Vec<_>>();
expect!(&block_list).to(have_len(3));
expect!(block_list)
.iter_map(|block| block.signature)
.to(match_elements([have_len(2), have_len(4), have_len(2)]));
}
#[test]
fn large_write_rechunks() {
let mut block_store = MemoryBlockStore::new();
let file_id = FileId::from(1);
let original_data = random_string(8, Case::Lower);
expect!(block_store.insert_block(file_id, 0, original_data.as_bytes().into())).to(be_ok());
let mut data_store = ChunkingBlockStoreAdapter::with_small_write_threshold(block_store, 2);
let block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner();
let write_data = random_string(4, Case::Upper);
let block_list =
expect!(data_store.write_block(file_id, 2, block_list, write_data.as_bytes().into()))
.to(be_ok())
.into_inner()
.into_iter()
.collect::<Vec<_>>();
expect!(block_list).to(have_len(1));
}
#[test]
fn write_at_threshold_boundary_is_small() {
let mut block_store = MemoryBlockStore::new();
let file_id = FileId::from(1);
let original_data = random_string(8, Case::Lower);
expect!(block_store.insert_block(file_id, 0, original_data.as_bytes().into())).to(be_ok());
let mut data_store = ChunkingBlockStoreAdapter::with_small_write_threshold(block_store, 5);
let block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner();
let write_data = random_string(4, Case::Upper);
let block_list =
expect!(data_store.write_block(file_id, 2, block_list, write_data.as_bytes().into()))
.to(be_ok())
.into_inner()
.into_iter()
.collect::<Vec<_>>();
expect!(&block_list).to(have_len(3));
expect!(block_list.get(1))
.to(be_some())
.map(|block| &block.signature)
.to(have_len(4));
}
#[test]
fn write_above_threshold_is_large() {
let mut block_store = MemoryBlockStore::new();
let file_id = FileId::from(1);
let original_data = random_string(8, Case::Lower);
expect!(block_store.insert_block(file_id, 0, original_data.as_bytes().into())).to(be_ok());
let mut data_store = ChunkingBlockStoreAdapter::with_small_write_threshold(block_store, 4);
let block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner();
let write_data = random_string(5, Case::Upper);
let block_list =
expect!(data_store.write_block(file_id, 1, block_list, write_data.as_bytes().into()))
.to(be_ok())
.into_inner();
expect!(block_list).to(have_len(1));
}
#[test]
fn small_write_fragment_before_only() {
let mut block_store = MemoryBlockStore::new();
let file_id = FileId::from(1);
let original_data = random_string(8, Case::Lower);
expect!(block_store.insert_block(file_id, 0, original_data.as_bytes().into())).to(be_ok());
let mut data_store =
ChunkingBlockStoreAdapter::with_small_write_threshold(block_store, 100);
let block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner();
let write_data = random_string(4, Case::Upper);
let block_list =
expect!(data_store.write_block(file_id, 4, block_list, write_data.as_bytes().into()))
.to(be_ok())
.into_inner()
.into_iter()
.collect::<Vec<_>>();
expect!(&block_list).to(have_len(2));
expect!(block_list)
.iter_map(|block| block.signature)
.to(match_elements([have_len(4), have_len(4)]));
}
#[test]
fn small_write_fragment_after_only() {
let mut block_store = MemoryBlockStore::new();
let file_id = FileId::from(1);
let original_data = random_string(8, Case::Lower);
expect!(block_store.insert_block(file_id, 0, original_data.as_bytes().into())).to(be_ok());
let mut data_store =
ChunkingBlockStoreAdapter::with_small_write_threshold(block_store, 100);
let block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner();
let write_data = random_string(4, Case::Upper);
let block_list =
expect!(data_store.write_block(file_id, 0, block_list, write_data.as_bytes().into()))
.to(be_ok())
.into_inner()
.into_iter()
.collect::<Vec<_>>();
expect!(&block_list).to(have_len(2));
expect!(block_list)
.iter_map(|block| block.signature)
.to(match_elements([have_len(4), have_len(4)]));
}
#[test]
fn small_write_no_fragments_full_block() {
let mut block_store = MemoryBlockStore::new();
let file_id = FileId::from(1);
let original_data = random_string(8, Case::Lower);
expect!(block_store.insert_block(file_id, 0, original_data.as_bytes().into())).to(be_ok());
let mut data_store =
ChunkingBlockStoreAdapter::with_small_write_threshold(block_store, 100);
let block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner();
let write_data = random_string(8, Case::Upper);
let block_list =
expect!(data_store.write_block(file_id, 0, block_list, write_data.as_bytes().into()))
.to(be_ok())
.into_inner()
.into_iter()
.collect::<Vec<_>>();
expect!(&block_list).to(have_len(1));
expect!(block_list)
.iter_map(|block| block.signature)
.to(match_elements([have_len(8)]));
}
#[test]
fn small_write_spanning_multiple_blocks() {
let mut block_store = MemoryBlockStore::new();
let file_id = FileId::from(1);
for i in 0..3 {
let data = random_string(8, Case::Lower);
expect!(block_store.insert_block(file_id, i, data.as_bytes().into())).to(be_ok());
}
let mut data_store =
ChunkingBlockStoreAdapter::with_small_write_threshold(block_store, 100);
let block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner();
let write_data = random_string(10, Case::Upper);
let block_list =
expect!(data_store.write_block(file_id, 6, block_list, write_data.as_bytes().into()))
.to(be_ok())
.into_inner()
.into_iter()
.collect::<Vec<_>>();
expect!(&block_list).to(have_len(3));
expect!(block_list)
.iter_map(|block| block.signature)
.to(match_elements([have_len(6), have_len(10), have_len(8)]));
}
#[test]
fn zero_threshold_always_rechunks() {
let mut block_store = MemoryBlockStore::new();
let file_id = FileId::from(1);
let original_data = random_string(8, Case::Lower);
expect!(block_store.insert_block(file_id, 0, original_data.as_bytes().into())).to(be_ok());
let mut data_store = ChunkingBlockStoreAdapter::with_small_write_threshold(block_store, 0);
let block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner();
let write_data = random_string(1, Case::Upper);
expect!(data_store.write_block(file_id, 3, block_list, write_data.as_bytes().into()))
.to(be_ok())
.to(have_len(1));
}
#[test]
fn threshold_comparison_is_strict_less_than() {
let mut block_store = MemoryBlockStore::new();
let file_id = FileId::from(1);
for i in 0..3 {
let data = random_string(TEST_BLOCK_SIZE, Case::Lower);
expect!(block_store.insert_block(file_id, i, data.as_bytes().into())).to(be_ok());
}
let mut data_store = ChunkingBlockStoreAdapter::with_small_write_threshold(block_store, 10);
let block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner();
let write_data = random_string(9, Case::Upper);
let block_list_9 =
expect!(data_store.write_block(file_id, 5, block_list, write_data.as_bytes().into()))
.to(be_ok())
.into_inner()
.into_iter()
.collect::<Vec<_>>();
expect!(&block_list_9).to(have_len(4));
expect!(block_list_9)
.iter_map(|block| block.signature)
.to(match_elements([
have_len(5),
have_len(9),
have_len(2),
have_len(8),
]));
}
#[test]
fn small_write_preserves_surrounding_data() {
let mut block_store = MemoryBlockStore::new();
let file_id = FileId::from(1);
let original_data = b"abcdefgh";
expect!(block_store.insert_block(file_id, 0, original_data.as_slice().into())).to(be_ok());
let mut data_store =
ChunkingBlockStoreAdapter::with_small_write_threshold(block_store, 100);
let block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner();
let write_data = b"XX";
expect!(data_store.write_block(file_id, 3, block_list, write_data.as_slice().into()))
.to(be_ok());
let block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner();
let mut result = Vec::new();
for block in block_list.iter() {
BlockStore::read_block(&mut data_store, block.id, &mut result).ok();
}
expect!(result).to(equal(b"abcXXfgh".to_vec()));
}
#[test]
fn small_write_fragment_data_matches_original() {
let mut block_store = MemoryBlockStore::new();
let file_id = FileId::from(1);
let original_data = b"abcdefgh";
expect!(block_store.insert_block(file_id, 0, original_data.as_slice().into())).to(be_ok());
let mut data_store =
ChunkingBlockStoreAdapter::with_small_write_threshold(block_store, 100);
let block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner();
let write_data = b"XX";
let block_list =
expect!(data_store.write_block(file_id, 3, block_list, write_data.as_slice().into()))
.to(be_ok())
.into_inner()
.into_iter()
.collect::<Vec<_>>();
let first_block_id = expect!(block_list.get(0))
.to(be_some())
.map(|block| block.id)
.into_inner();
let mut fragment_before = Vec::new();
BlockStore::read_block(&mut data_store, first_block_id, &mut fragment_before).ok();
expect!(fragment_before).to(equal(b"abc".to_vec()));
let last_block_id = expect!(block_list.get(2))
.to(be_some())
.map(|block| block.id)
.into_inner();
let mut fragment_after = Vec::new();
BlockStore::read_block(&mut data_store, last_block_id, &mut fragment_after).ok();
expect!(fragment_after).to(equal(b"fgh".to_vec()));
}
#[test]
fn large_write_data_integrity() {
let mut block_store = MemoryBlockStore::new();
let file_id = FileId::from(1);
let original_data = b"abcdefgh";
expect!(block_store.insert_block(file_id, 0, original_data.as_slice().into())).to(be_ok());
let mut data_store = ChunkingBlockStoreAdapter::with_small_write_threshold(block_store, 2);
let block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner();
let write_data = b"XXXX";
expect!(data_store.write_block(file_id, 2, block_list, write_data.as_slice().into()))
.to(be_ok());
let block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner();
let mut result = Vec::new();
for block in block_list.iter() {
BlockStore::read_block(&mut data_store, block.id, &mut result).ok();
}
expect!(result).to(equal(b"abXXXXgh".to_vec()));
}
#[test]
fn small_write_at_file_start() {
let mut block_store = MemoryBlockStore::new();
let file_id = FileId::from(1);
let original_data = random_string(8, Case::Lower);
expect!(block_store.insert_block(file_id, 0, original_data.as_bytes().into())).to(be_ok());
let mut data_store =
ChunkingBlockStoreAdapter::with_small_write_threshold(block_store, 100);
let block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner();
let write_data = random_string(3, Case::Upper);
let block_list =
expect!(data_store.write_block(file_id, 0, block_list, write_data.as_bytes().into()))
.to(be_ok())
.into_inner()
.into_iter()
.collect::<Vec<_>>();
expect!(&block_list).to(have_len(2));
expect!(block_list)
.iter_map(|block| block.signature)
.to(match_elements([have_len(3), have_len(5)]));
}
#[test]
fn small_write_at_file_end() {
let mut block_store = MemoryBlockStore::new();
let file_id = FileId::from(1);
let original_data = random_string(8, Case::Lower);
expect!(block_store.insert_block(file_id, 0, original_data.as_bytes().into())).to(be_ok());
let mut data_store =
ChunkingBlockStoreAdapter::with_small_write_threshold(block_store, 100);
let block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner();
let write_data = random_string(3, Case::Upper);
let block_list =
expect!(data_store.write_block(file_id, 5, block_list, write_data.as_bytes().into()))
.to(be_ok())
.into_inner()
.into_iter()
.collect::<Vec<_>>();
expect!(&block_list).to(have_len(2));
expect!(block_list)
.iter_map(|block| block.signature)
.to(match_elements([have_len(5), have_len(3)]));
}
#[test]
fn small_write_beyond_file_end_appends() {
let mut block_store = MemoryBlockStore::new();
let file_id = FileId::from(1);
let original_data = random_string(8, Case::Lower);
expect!(block_store.insert_block(file_id, 0, original_data.as_bytes().into())).to(be_ok());
let mut data_store =
ChunkingBlockStoreAdapter::with_small_write_threshold(block_store, 100);
let block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner();
let write_data = random_string(4, Case::Upper);
expect!(data_store.write_block(file_id, 8, block_list, write_data.as_bytes().into()))
.to(be_ok())
.to(have_len(2));
}
#[test]
fn small_write_single_byte() {
let mut block_store = MemoryBlockStore::new();
let file_id = FileId::from(1);
let original_data = random_string(8, Case::Lower);
expect!(block_store.insert_block(file_id, 0, original_data.as_bytes().into())).to(be_ok());
let mut data_store =
ChunkingBlockStoreAdapter::with_small_write_threshold(block_store, 100);
let block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner();
let write_data = random_string(1, Case::Upper);
let block_list =
expect!(data_store.write_block(file_id, 4, block_list, write_data.as_bytes().into()))
.to(be_ok())
.into_inner()
.into_iter()
.collect::<Vec<_>>();
expect!(&block_list).to(have_len(3));
expect!(block_list)
.iter_map(|block| block.signature)
.to(match_elements([have_len(4), have_len(1), have_len(3)]));
}
#[test]
fn small_writes_increase_block_count() {
let mut block_store = MemoryBlockStore::new();
let file_id = FileId::from(1);
let original_data = random_string(8, Case::Lower);
expect!(block_store.insert_block(file_id, 0, original_data.as_bytes().into())).to(be_ok());
let mut data_store =
ChunkingBlockStoreAdapter::with_small_write_threshold(block_store, 100);
let block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner();
expect!(data_store.list_blocks(file_id))
.to(be_ok())
.to(have_len(1));
let write_data = random_string(2, Case::Upper);
expect!(data_store.write_block(file_id, 3, block_list, write_data.as_bytes().into()))
.to(be_ok());
expect!(data_store.list_blocks(file_id))
.to(be_ok())
.to(have_len(3));
}
#[test]
fn large_writes_maintain_or_reduce_block_count() {
let mut block_store = MemoryBlockStore::new();
let file_id = FileId::from(1);
for i in 0..3 {
let data = random_string(TEST_BLOCK_SIZE, Case::Lower);
expect!(block_store.insert_block(file_id, i, data.as_bytes().into())).to(be_ok());
}
let mut data_store = ChunkingBlockStoreAdapter::with_small_write_threshold(block_store, 2);
let block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner();
let initial_count = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.to(have_len(3))
.into_inner()
.len();
let write_data = random_string(10, Case::Upper);
expect!(data_store.write_block(file_id, 5, block_list, write_data.as_bytes().into()))
.to(be_ok());
let final_count = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner()
.len();
expect!(final_count <= initial_count).to(equal(true));
}
#[test]
fn repeated_small_writes_accumulate_fragments() {
let mut block_store = MemoryBlockStore::new();
let file_id = FileId::from(1);
let original_data = random_string(8, Case::Lower);
expect!(block_store.insert_block(file_id, 0, original_data.as_bytes().into())).to(be_ok());
let mut data_store =
ChunkingBlockStoreAdapter::with_small_write_threshold(block_store, 100);
let mut block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner();
expect!(data_store.list_blocks(file_id))
.to(be_ok())
.to(have_len(1));
block_list = expect!(data_store.write_block(
file_id,
1,
block_list,
random_string(1, Case::Upper).as_bytes().into()
))
.to(be_ok())
.into_inner();
block_list = expect!(data_store.write_block(
file_id,
3,
block_list,
random_string(1, Case::Upper).as_bytes().into()
))
.to(be_ok())
.into_inner();
expect!(data_store.write_block(
file_id,
5,
block_list,
random_string(1, Case::Upper).as_bytes().into()
))
.to(be_ok());
expect!(data_store.list_blocks(file_id))
.to(be_ok())
.to(have_len(7));
}
#[test]
fn small_write_multi_block_no_start_fragment() {
let mut block_store = MemoryBlockStore::new();
let file_id = FileId::from(1);
for i in 0..3 {
let data = random_string(8, Case::Lower);
expect!(block_store.insert_block(file_id, i, data.as_bytes().into())).to(be_ok());
}
let mut data_store =
ChunkingBlockStoreAdapter::with_small_write_threshold(block_store, 100);
let block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner();
let write_data = random_string(10, Case::Upper);
let block_list =
expect!(data_store.write_block(file_id, 8, block_list, write_data.as_bytes().into()))
.to(be_ok())
.into_inner()
.into_iter()
.collect::<Vec<_>>();
expect!(&block_list).to(have_len(3));
expect!(block_list)
.iter_map(|block| block.signature)
.to(match_elements([have_len(8), have_len(10), have_len(6)]));
}
#[test]
fn small_write_multi_block_no_fragments() {
let mut block_store = MemoryBlockStore::new();
let file_id = FileId::from(1);
for i in 0..4 {
let data = random_string(8, Case::Lower);
expect!(block_store.insert_block(file_id, i, data.as_bytes().into())).to(be_ok());
}
let mut data_store =
ChunkingBlockStoreAdapter::with_small_write_threshold(block_store, 100);
let block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner();
let write_data = random_string(16, Case::Upper);
let block_list =
expect!(data_store.write_block(file_id, 8, block_list, write_data.as_bytes().into()))
.to(be_ok())
.into_inner()
.into_iter()
.collect::<Vec<_>>();
expect!(&block_list).to(have_len(3));
expect!(block_list)
.iter_map(|block| block.signature)
.to(match_elements([have_len(8), have_len(16), have_len(8)]));
}
#[test]
fn small_write_multi_block_both_fragments() {
let mut block_store = MemoryBlockStore::new();
let file_id = FileId::from(1);
for i in 0..3 {
let data = random_string(8, Case::Lower);
expect!(block_store.insert_block(file_id, i, data.as_bytes().into())).to(be_ok());
}
let mut data_store =
ChunkingBlockStoreAdapter::with_small_write_threshold(block_store, 100);
let block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner();
let write_data = random_string(8, Case::Upper);
let block_list =
expect!(data_store.write_block(file_id, 4, block_list, write_data.as_bytes().into()))
.to(be_ok())
.into_inner()
.into_iter()
.collect::<Vec<_>>();
expect!(&block_list).to(have_len(4));
expect!(block_list)
.iter_map(|block| block.signature)
.to(match_elements([
have_len(4),
have_len(8),
have_len(4),
have_len(8),
]));
}
#[test]
fn small_write_multi_block_preserves_data() {
let mut block_store = MemoryBlockStore::new();
let file_id = FileId::from(1);
expect!(block_store.insert_block(file_id, 0, b"AAAAAAAA".as_slice().into())).to(be_ok());
expect!(block_store.insert_block(file_id, 1, b"BBBBBBBB".as_slice().into())).to(be_ok());
expect!(block_store.insert_block(file_id, 2, b"CCCCCCCC".as_slice().into())).to(be_ok());
let mut data_store =
ChunkingBlockStoreAdapter::with_small_write_threshold(block_store, 100);
let block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner();
expect!(data_store.write_block(file_id, 8, block_list, b"XXXXXXXXXX".as_slice().into()))
.to(be_ok());
let block_list = expect!(data_store.list_blocks(file_id))
.to(be_ok())
.into_inner();
let mut result = Vec::new();
for block in block_list.iter() {
BlockStore::read_block(&mut data_store, block.id, &mut result).ok();
}
expect!(result).to(equal(b"AAAAAAAAXXXXXXXXXXCCCCCC".to_vec()));
}
}