diff --git a/tests/testRegionCompact.cpp b/tests/testRegionCompact.cpp index fcc3162..83137af 100644 --- a/tests/testRegionCompact.cpp +++ b/tests/testRegionCompact.cpp @@ -154,3 +154,164 @@ VF_TEST(aChunkThatOutgrowsItsRunMovesToTheEndAndLeavesAHole) { CHECK(updateChunkInPlace(buffer, buildRecord(Vec3i{1, 0, 0}, busyChunk(3)), false)); CHECK(buffer.size() > sizeBefore); + std::vector after = compactor.survey(buffer); + CHECK_EQ(after.size(), static_cast(3)); + CHECK_EQ(after[1].sectorOffset, static_cast(sizeBefore / saveSectorBytes)); + CHECK(after[1].sectorCount > abandoned); + CHECK(compactor.fragmentationPercent(buffer) > 0); + + SaveScanner scanner; + CHECK(scanner.scan(buffer).clean()); + CHECK(!isCanonicalSaveLayout(buffer.data(), buffer.size())); + + ByteBuffer out; + CompactionStats stats; + CHECK(compactor.compact(buffer, out, stats)); + CHECK_EQ(stats.holeSectors, abandoned); + CHECK(stats.bytesReclaimed >= abandoned * saveSectorBytes); + CHECK_EQ(stats.fragmentationAfter, 0); + CHECK(isCanonicalSaveLayout(out.data(), out.size())); +} + +VF_TEST(aChunkThatShrinksKeepsUnusedSectorsInsideItsOwnRun) { + SaveContainer container; + container.version = SaveVersion::V4; + container.chunks.push_back(buildRecord(Vec3i{0, 0, 0}, busyChunk(1))); + container.chunks.push_back(buildRecord(Vec3i{1, 0, 0}, uniformChunk(Block::Stone))); + ByteBuffer buffer = writeSaveContainer(container); + std::size_t sizeBefore = buffer.size(); + + RegionCompactor compactor; + uint32_t runBefore = compactor.survey(buffer)[0].sectorCount; + CHECK(updateChunkInPlace(buffer, buildRecord(Vec3i{0, 0, 0}, uniformChunk(Block::Dirt)), + false)); + CHECK_EQ(buffer.size(), sizeBefore); + + std::vector after = compactor.survey(buffer); + CHECK_EQ(after[0].sectorCount, runBefore); + CHECK(after[0].neededSectors < runBefore); + CHECK_EQ(after[0].excessSectors, runBefore - after[0].neededSectors); + CHECK(compactor.fragmentationPercent(buffer) > 0); + + ByteBuffer out; + CompactionStats stats; + CHECK(compactor.compact(buffer, out, stats)); + CHECK_EQ(stats.holeSectors, static_cast(0)); + CHECK_EQ(stats.excessSectors, runBefore - after[0].neededSectors); + CHECK(out.size() < buffer.size()); + CHECK_EQ(stats.fragmentationAfter, 0); +} + +VF_TEST(compactionRewritesEveryChunkByteIdentically) { + SaveContainer container; + container.version = SaveVersion::V4; + for (int i = 0; i < 5; ++i) + container.chunks.push_back(buildRecord(Vec3i{i, 1, i}, busyChunk(i * 17))); + ByteBuffer buffer = writeSaveContainer(container); + + CHECK(updateChunkInPlace(buffer, buildRecord(Vec3i{2, 1, 2}, uniformChunk(Block::Sand)), + false)); + CHECK(updateChunkInPlace(buffer, buildRecord(Vec3i{0, 1, 0}, busyChunk(5)), false)); + CHECK(updateChunkInPlace(buffer, buildRecord(Vec3i{4, 1, 4}, uniformChunk(Block::Gravel)), + false)); + + std::vector coords = coordsOf(buffer); + CHECK_EQ(coords.size(), static_cast(5)); + std::vector before; + for (const Vec3i& coord : coords) { + ByteBuffer payload; + CHECK(readChunkPayloadBytes(buffer.data(), buffer.size(), coord, payload)); + before.push_back(payload); + } + + RegionCompactor compactor; + ByteBuffer out; + CompactionStats stats; + CHECK(compactor.compact(buffer, out, stats)); + CHECK(stats.ok); + CHECK_EQ(stats.chunksKept, static_cast(5)); + CHECK_EQ(stats.chunksDropped, static_cast(0)); + CHECK_EQ(stats.chunksRepacked, static_cast(0)); + + for (std::size_t i = 0; i < coords.size(); ++i) { + ByteBuffer after; + CHECK(readChunkPayloadBytes(out.data(), out.size(), coords[i], after)); + CHECK(after == before[i]); + } + CHECK(isCanonicalSaveLayout(out.data(), out.size())); + SaveScanner scanner; + CHECK(scanner.scan(out).clean()); +} + +VF_TEST(compactionReportsTheSectorsItGaveBack) { + ByteBuffer buffer = writeSaveContainer(uniformContainer(4)); + CHECK(updateChunkInPlace(buffer, buildRecord(Vec3i{1, 0, 0}, busyChunk(2)), false)); + CHECK(updateChunkInPlace(buffer, buildRecord(Vec3i{3, 0, 0}, busyChunk(4)), false)); + + RegionCompactor compactor; + ByteBuffer out; + CompactionStats stats; + CHECK(compactor.compact(buffer, out, stats)); + CHECK_EQ(stats.sectorBytes, saveSectorBytes); + CHECK_EQ(stats.bytesBefore, buffer.size()); + CHECK_EQ(stats.bytesAfter, out.size()); + CHECK_EQ(stats.bytesReclaimed, buffer.size() - out.size()); + CHECK_EQ(stats.sectorsBefore, static_cast(buffer.size() / saveSectorBytes)); + CHECK_EQ(stats.sectorsAfter, static_cast(out.size() / saveSectorBytes)); + CHECK(stats.holeSectors > 0u); + CHECK(stats.chunksMoved > 0u); + CHECK_EQ(stats.operations, 4u + stats.chunksMoved + 1u); + CHECK(stats.fragmentationBefore > 0); + CHECK_EQ(stats.fragmentationAfter, 0); +} + +VF_TEST(compactionDropsChunksThatHoldNothing) { + SaveContainer container; + container.version = SaveVersion::V4; + container.chunks.push_back(buildRecord(Vec3i{0, 0, 0}, uniformChunk(Block::Stone))); + container.chunks.push_back(buildRecord(Vec3i{1, 0, 0}, uniformChunk(Block::Air))); + container.chunks.push_back(buildRecord(Vec3i{2, 0, 0}, uniformChunk(Block::Dirt))); + ByteBuffer buffer = writeSaveContainer(container); + + RegionCompactor compactor; + std::vector fragments = compactor.survey(buffer); + CHECK(!fragments[0].empty); + CHECK(fragments[1].empty); + CHECK(!fragments[2].empty); + + ByteBuffer out; + CompactionStats stats; + CHECK(compactor.compact(buffer, out, stats)); + CHECK_EQ(stats.chunksDropped, static_cast(1)); + CHECK_EQ(stats.chunksKept, static_cast(2)); + CHECK_EQ(coordsOf(out).size(), static_cast(2)); + ByteBuffer gone; + CHECK(!readChunkPayloadBytes(out.data(), out.size(), Vec3i{1, 0, 0}, gone)); + + RegionCompactor keeper; + keeper.dropEmptyChunks = false; + ByteBuffer kept; + CompactionStats keptStats; + CHECK(keeper.compact(buffer, kept, keptStats)); + CHECK_EQ(keptStats.chunksDropped, static_cast(0)); + CHECK_EQ(keptStats.chunksKept, static_cast(3)); + CHECK(kept == buffer); +} + +VF_TEST(repackingWithCompressionShrinksTheFileAndStillReadsBack) { + SaveContainer container; + container.version = SaveVersion::V4; + container.chunks.push_back(buildRecord(Vec3i{0, 0, 0}, uniformChunk(Block::Stone))); + container.chunks.push_back(buildRecord(Vec3i{1, 0, 0}, uniformChunk(Block::Dirt))); + container.chunks.push_back(buildRecord(Vec3i{2, 0, 0}, busyChunk(9))); + ByteBuffer buffer = writeSaveContainer(container); + + RegionCompactor compactor; + compactor.repackWithStrongerCompression = true; + ByteBuffer out; + CompactionStats stats; + CHECK(compactor.compact(buffer, out, stats)); + CHECK(stats.chunksRepacked > 0u); + CHECK(out.size() < buffer.size()); + CHECK(isCanonicalSaveLayout(out.data(), out.size())); +