137 lines
4.3 KiB
Rust
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(())
|
|
}
|