// 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(()) }