use std::io::{Cursor, Read, Seek, Write};
use crate::{
ChunkLocation, CompressionScheme::Uncompressed, Error, Region, CHUNK_HEADER_SIZE,
REGION_HEADER_SIZE, SECTOR_SIZE,
};
fn new_empty() -> Region<Cursor<Vec<u8>>> {
Region::create(Cursor::new(vec![])).unwrap()
}
fn assert_location<S>(r: &mut Region<S>, x: usize, z: usize, offset: u64, size: u64)
where
S: Read + Write + Seek,
{
let ChunkLocation {
offset: found_offset,
sectors: found_size,
} = r.location(x, z).unwrap().unwrap();
assert_eq!(offset, found_offset);
assert_eq!(size, found_size);
}
fn n_sector_chunk(n: usize) -> Vec<u8> {
assert!(n > 0);
vec![0; (n * SECTOR_SIZE) - CHUNK_HEADER_SIZE]
}
#[test]
fn new_region_should_be_empty() {
let mut r = new_empty();
for x in 0..32 {
for z in 0..32 {
let chunk = r.read_chunk(x, z);
assert!(matches!(chunk, Ok(None)))
}
}
}
#[test]
fn blank_write_chunk() {
let mut r = new_empty();
r.write_compressed_chunk(0, 0, Uncompressed, &[1, 2, 3])
.unwrap();
assert_location(&mut r, 0, 0, 2, 1);
}
#[test]
fn write_invalid_offset_errors() {
let mut r = new_empty();
assert!(matches!(
r.write_compressed_chunk(32, 0, Uncompressed, &[1, 2, 3]),
Err(Error::InvalidOffset(..))
));
assert!(matches!(
r.write_compressed_chunk(0, 32, Uncompressed, &[1, 2, 3]),
Err(Error::InvalidOffset(..))
));
}
#[test]
fn exact_sector_size_chunk_takes_one_sector() {
let mut r = new_empty();
r.write_compressed_chunk(0, 0, Uncompressed, &n_sector_chunk(1))
.unwrap();
assert_location(&mut r, 0, 0, 2, 1);
}
#[test]
fn over_one_sector_size_chunk_takes_two_sectors() {
let mut r = new_empty();
r.write_compressed_chunk(
0,
0,
Uncompressed,
&[0; SECTOR_SIZE - CHUNK_HEADER_SIZE + 1],
)
.unwrap();
assert_location(&mut r, 0, 0, 2, 2);
}
#[test]
fn several_sector_chunk_takes_correct_size() {
let mut r = new_empty();
r.write_compressed_chunk(0, 0, Uncompressed, &n_sector_chunk(5))
.unwrap();
assert_location(&mut r, 0, 0, 2, 5);
}
#[test]
fn oversized_chunk_fails() {
let mut r = new_empty();
let res = r.write_compressed_chunk(0, 0, Uncompressed, &n_sector_chunk(256));
assert!(matches!(res, Err(Error::ChunkTooLarge)))
}
#[test]
fn write_several_chunks() {
let mut r = new_empty();
r.write_compressed_chunk(0, 0, Uncompressed, &n_sector_chunk(2))
.unwrap();
r.write_compressed_chunk(0, 1, Uncompressed, &n_sector_chunk(3))
.unwrap();
assert_location(&mut r, 0, 0, 2, 2);
assert_location(&mut r, 0, 1, 4, 3);
}
#[test]
fn write_and_get_chunk() {
let mut r = new_empty();
r.write_compressed_chunk(0, 0, Uncompressed, &[1, 2, 3])
.unwrap();
let c = r.read_chunk(0, 0).unwrap().unwrap();
assert_eq!(c, &[1, 2, 3]);
}
#[test]
fn getting_other_chunks_404s() {
let mut r = new_empty();
r.write_compressed_chunk(1, 1, Uncompressed, &[1, 2, 3])
.unwrap();
assert!(matches!(r.read_chunk(0, 0), Ok(None)));
assert!(matches!(r.read_chunk(1, 0), Ok(None)));
assert!(matches!(r.read_chunk(1, 1), Ok(Some(_))));
}
#[test]
fn overwrite_with_smaller_chunk() {
let mut r = new_empty();
r.write_compressed_chunk(0, 0, Uncompressed, &n_sector_chunk(2))
.unwrap();
r.write_compressed_chunk(0, 0, Uncompressed, &n_sector_chunk(1))
.unwrap();
assert_location(&mut r, 0, 0, 2, 1);
}
#[test]
fn overwrite_with_larger_chunk() {
let mut r = new_empty();
r.write_compressed_chunk(0, 0, Uncompressed, &n_sector_chunk(2))
.unwrap();
r.write_compressed_chunk(0, 1, Uncompressed, &n_sector_chunk(1))
.unwrap();
r.write_compressed_chunk(0, 0, Uncompressed, &n_sector_chunk(3))
.unwrap();
assert_location(&mut r, 0, 0, 5, 3);
}
#[test]
fn chunk_can_fill_gap_left_by_moved_chunk_after_it() {
let mut r = new_empty();
r.write_compressed_chunk(0, 0, Uncompressed, &n_sector_chunk(3))
.unwrap();
r.write_compressed_chunk(0, 1, Uncompressed, &n_sector_chunk(3))
.unwrap();
r.write_compressed_chunk(0, 2, Uncompressed, &n_sector_chunk(3))
.unwrap();
r.write_compressed_chunk(0, 1, Uncompressed, &n_sector_chunk(4))
.unwrap();
r.write_compressed_chunk(0, 0, Uncompressed, &n_sector_chunk(6))
.unwrap();
assert_location(&mut r, 0, 0, 2, 6);
assert_location(&mut r, 0, 1, 11, 4);
assert_location(&mut r, 0, 2, 8, 3);
}
#[test]
fn load_from_existing_buffer() {
let mut r = new_empty();
r.write_compressed_chunk(0, 0, Uncompressed, &n_sector_chunk(1))
.unwrap();
r.write_compressed_chunk(0, 1, Uncompressed, &n_sector_chunk(2))
.unwrap();
let buf = r.into_inner().unwrap();
let mut r = Region::from_stream(buf).unwrap();
assert_location(&mut r, 0, 0, 2, 1);
assert_location(&mut r, 0, 1, 3, 2);
}
#[test]
fn deleted_chunk_doenst_exist() {
let mut r = new_empty();
r.write_compressed_chunk(0, 0, Uncompressed, &n_sector_chunk(3))
.unwrap();
r.write_compressed_chunk(0, 1, Uncompressed, &n_sector_chunk(3))
.unwrap();
r.write_compressed_chunk(0, 2, Uncompressed, &n_sector_chunk(3))
.unwrap();
r.remove_chunk(0, 1).unwrap();
assert!(matches!(r.read_chunk(0, 0), Ok(Some(_))));
assert!(matches!(r.read_chunk(0, 1), Ok(None)));
assert!(matches!(r.read_chunk(0, 2), Ok(Some(_))));
}
#[test]
fn deleting_non_existing_chunk_works() {
let mut r = new_empty();
r.write_compressed_chunk(0, 0, Uncompressed, &n_sector_chunk(3))
.unwrap();
r.write_compressed_chunk(0, 2, Uncompressed, &n_sector_chunk(3))
.unwrap();
r.remove_chunk(0, 1).unwrap();
assert!(matches!(r.read_chunk(0, 0), Ok(Some(_))));
assert!(matches!(r.read_chunk(0, 1), Ok(None)));
assert!(matches!(r.read_chunk(0, 2), Ok(Some(_))));
}
#[test]
fn into_inner_rewinds_to_correct_position() {
let mut r = new_empty();
r.write_compressed_chunk(0, 0, Uncompressed, &n_sector_chunk(3))
.unwrap();
r.write_compressed_chunk(0, 1, Uncompressed, &n_sector_chunk(3))
.unwrap();
r.write_compressed_chunk(0, 2, Uncompressed, &n_sector_chunk(3))
.unwrap();
let expected_position = REGION_HEADER_SIZE + SECTOR_SIZE * 3 * 3;
let inner = r.into_inner().unwrap();
assert_eq!(inner.position(), expected_position as u64);
}
#[test]
fn into_inner_rewinds_behind_header_if_empty_region() {
let r = new_empty();
let inner = r.into_inner().unwrap();
assert_eq!(inner.position(), REGION_HEADER_SIZE as u64);
}