Synvael/crates/server/src/tests/region_file.rs

137 lines
4.3 KiB
Rust

// SPDX-License-Identifier: AGPL-3.0-only
use super::*;
use shared::world::BlockId;
/// Builds a representative modified chunk with a few edits spanning the local index range.
fn sample(pos: ChunkPos) -> ChunkData {
let mut data = ChunkData::new(pos, 7);
data.set(0, BlockId(4));
data.set(1000, BlockId(9));
data.set(32_767, BlockId(2));
data
}
#[test]
fn region_coords_floor_negative_chunks() {
// Truncating division would map -1 to region 0; Euclidean flooring maps it to region -1. Every axis, including Y, floors identically under the 3D region grid.
assert_eq!(region_coords(0, 0, 0), (0, 0, 0));
assert_eq!(region_coords(31, 31, 31), (0, 0, 0));
assert_eq!(region_coords(-1, -1, -1), (-1, -1, -1));
assert_eq!(region_coords(-32, -33, 64), (-1, -2, 2));
}
#[test]
fn region_path_names_the_region_file() {
let dir = Path::new("/saves/world/region");
assert_eq!(
region_path(dir, -1, 40, 5),
Path::new("/saves/world/region/r.-1.1.0.region")
);
}
#[test]
fn open_missing_file_is_empty() -> Result<(), SaveError> {
let dir = tempfile::tempdir()?;
let region = RegionFile::open(dir.path().join("r.0.0.0.region"))?;
assert!(region.is_empty());
assert_eq!(region.read_chunk(ChunkPos::new(0, 0, 0))?, None);
Ok(())
}
#[test]
fn round_trips_chunks_through_disk() -> Result<(), SaveError> {
let dir = tempfile::tempdir()?;
let path = dir.path().join("r.0.0.0.region");
let positions = [
ChunkPos::new(0, 0, 0),
ChunkPos::new(1, 2, 3),
ChunkPos::new(-5, 10, -30),
];
let mut region = RegionFile::open(path.clone())?;
for pos in positions {
region.write_chunk(pos, &sample(pos), 123)?;
}
assert!(region.is_dirty());
region.save()?;
assert!(!region.is_dirty());
// Reopen from disk in a fresh instance and confirm every chunk decodes byte-identically.
let reopened = RegionFile::open(path)?;
assert_eq!(reopened.len(), positions.len());
for pos in positions {
assert_eq!(reopened.read_chunk(pos)?, Some(sample(pos)));
}
// A position never written has no record.
assert_eq!(reopened.read_chunk(ChunkPos::new(9, 9, 9))?, None);
Ok(())
}
#[test]
fn record_offsets_are_valid_and_contiguous() -> Result<(), SaveError> {
let dir = tempfile::tempdir()?;
let path = dir.path().join("r.0.0.0.region");
let mut region = RegionFile::open(path.clone())?;
for pos in [
ChunkPos::new(0, 0, 0),
ChunkPos::new(2, 0, 1),
ChunkPos::new(-1, 4, -1),
] {
region.write_chunk(pos, &sample(pos), 0)?;
}
region.save()?;
let reopened = RegionFile::open(path)?;
let index_len = reopened.index.encode()?.len() as u64;
// Records are packed contiguously immediately after the index, in ascending position order.
let mut expected_offset = index_len;
for (_pos, entry) in reopened.index.entries() {
assert_eq!(entry.offset, expected_offset);
expected_offset += u64::from(entry.length);
}
Ok(())
}
#[test]
fn remove_drops_only_the_named_chunk() -> Result<(), SaveError> {
let dir = tempfile::tempdir()?;
let path = dir.path().join("r.0.0.0.region");
let kept = ChunkPos::new(0, 0, 0);
let dropped = ChunkPos::new(1, 1, 1);
let mut region = RegionFile::open(path.clone())?;
region.write_chunk(kept, &sample(kept), 0)?;
region.write_chunk(dropped, &sample(dropped), 0)?;
region.save()?;
let mut region = RegionFile::open(path.clone())?;
region.remove_chunk(dropped);
region.save()?;
let reopened = RegionFile::open(path)?;
assert_eq!(reopened.read_chunk(dropped)?, None);
assert_eq!(reopened.read_chunk(kept)?, Some(sample(kept)));
Ok(())
}
#[test]
fn stray_tmp_file_does_not_corrupt_reads() -> Result<(), SaveError> {
let dir = tempfile::tempdir()?;
let path = dir.path().join("r.0.0.0.region");
let pos = ChunkPos::new(0, 0, 0);
let mut region = RegionFile::open(path.clone())?;
region.write_chunk(pos, &sample(pos), 0)?;
region.save()?;
// A leftover .tmp from an interrupted save must be ignored: only the renamed target is read.
fs::write(dir.path().join("r.0.0.0.region.tmp"), b"garbage")?;
let reopened = RegionFile::open(path)?;
assert_eq!(reopened.read_chunk(pos)?, Some(sample(pos)));
Ok(())
}