test: cover uniformChunk and busyChunk
This commit is contained in:
1 parent
67a5f3ff27
commit
3836db2624
1 file changed
+156
@@ -0,0 +1,156 @@
|
|||||||
|
#include "testHarness.h"
|
||||||
|
|
||||||
|
#include "vf/Block.h"
|
||||||
|
#include "vf/RegionCompact.h"
|
||||||
|
#include "vf/SaveIntegrity.h"
|
||||||
|
#include "vf/SaveMigration.h"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
using namespace vf;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
Chunk uniformChunk(Block block) {
|
||||||
|
Chunk chunk;
|
||||||
|
for (int x = 0; x < chunkSize; ++x)
|
||||||
|
for (int y = 0; y < chunkSize; ++y)
|
||||||
|
for (int z = 0; z < chunkSize; ++z) chunk.set(x, y, z, block);
|
||||||
|
return chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
Chunk busyChunk(int seed) {
|
||||||
|
Chunk chunk;
|
||||||
|
int cell = 0;
|
||||||
|
for (int x = 0; x < chunkSize; ++x)
|
||||||
|
for (int y = 0; y < chunkSize; ++y)
|
||||||
|
for (int z = 0; z < chunkSize; ++z) {
|
||||||
|
int value = (cell + seed) % static_cast<int>(Block::Count);
|
||||||
|
chunk.set(x, y, z, static_cast<Block>(value));
|
||||||
|
++cell;
|
||||||
|
}
|
||||||
|
return chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
SaveChunkRecord buildRecord(const Vec3i& coord, const Chunk& blocks) {
|
||||||
|
SaveChunkRecord record;
|
||||||
|
record.coord = coord;
|
||||||
|
record.blocks = blocks;
|
||||||
|
record.light = defaultChunkLight(blocks, saveDefaultSkyLight);
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
|
||||||
|
SaveContainer uniformContainer(int chunkTotal) {
|
||||||
|
SaveContainer container;
|
||||||
|
container.version = SaveVersion::V4;
|
||||||
|
for (int i = 0; i < chunkTotal; ++i)
|
||||||
|
container.chunks.push_back(buildRecord(Vec3i{i, 0, 0}, uniformChunk(Block::Stone)));
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Vec3i> coordsOf(const ByteBuffer& buffer) {
|
||||||
|
std::vector<Vec3i> coords;
|
||||||
|
SaveHeader header;
|
||||||
|
if (!readSaveHeader(buffer.data(), buffer.size(), header)) return coords;
|
||||||
|
std::vector<SaveTableEntry> entries;
|
||||||
|
if (!readSaveTable(buffer.data(), buffer.size(), header, entries)) return coords;
|
||||||
|
for (const SaveTableEntry& entry : entries) coords.push_back(entry.coord);
|
||||||
|
return coords;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A layout written by hand so the fragmentation arithmetic is known before the
|
||||||
|
// code runs: 13 sectors of 64 bytes, 2 header sectors, runs of 4 and 3 sectors
|
||||||
|
// at sector 2 and sector 10, so 4 sectors are holes and 1 sector inside the
|
||||||
|
// second run is reserved for nothing.
|
||||||
|
ByteBuffer handBuiltLayout() {
|
||||||
|
const uint32_t sectorBytes = 64;
|
||||||
|
ByteBuffer buffer(13 * static_cast<std::size_t>(sectorBytes), 0);
|
||||||
|
saveWriteU32At(buffer.data(), buffer.size(), 0, saveMagic);
|
||||||
|
saveWriteU32At(buffer.data(), buffer.size(), 4, static_cast<uint32_t>(SaveVersion::V4));
|
||||||
|
saveWriteU32At(buffer.data(), buffer.size(), 8, 0u);
|
||||||
|
saveWriteU32At(buffer.data(), buffer.size(), 12, 0u);
|
||||||
|
saveWriteU32At(buffer.data(), buffer.size(), 16, 2u);
|
||||||
|
saveWriteU32At(buffer.data(), buffer.size(), 20, sectorBytes);
|
||||||
|
|
||||||
|
std::size_t at = saveHeaderBytes;
|
||||||
|
saveWriteU32At(buffer.data(), buffer.size(), at + 0, 0u);
|
||||||
|
saveWriteU32At(buffer.data(), buffer.size(), at + 4, 0u);
|
||||||
|
saveWriteU32At(buffer.data(), buffer.size(), at + 8, 0u);
|
||||||
|
saveWriteU32At(buffer.data(), buffer.size(), at + 12, 2u);
|
||||||
|
saveWriteU32At(buffer.data(), buffer.size(), at + 16, 4u);
|
||||||
|
saveWriteU32At(buffer.data(), buffer.size(), at + 20, 200u);
|
||||||
|
|
||||||
|
at += saveEntryBytes;
|
||||||
|
saveWriteU32At(buffer.data(), buffer.size(), at + 0, 0u);
|
||||||
|
saveWriteU32At(buffer.data(), buffer.size(), at + 4, 0u);
|
||||||
|
saveWriteU32At(buffer.data(), buffer.size(), at + 8, 1u);
|
||||||
|
saveWriteU32At(buffer.data(), buffer.size(), at + 12, 10u);
|
||||||
|
saveWriteU32At(buffer.data(), buffer.size(), at + 16, 3u);
|
||||||
|
saveWriteU32At(buffer.data(), buffer.size(), at + 20, 100u);
|
||||||
|
|
||||||
|
uint32_t checksum = fnv1a32(buffer.data() + saveHeaderBytes, 2 * saveEntryBytes);
|
||||||
|
saveWriteU32At(buffer.data(), buffer.size(), 24, checksum);
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
VF_TEST(fragmentationOfAHandBuiltLayoutMatchesTheArithmetic) {
|
||||||
|
ByteBuffer buffer = handBuiltLayout();
|
||||||
|
RegionCompactor compactor;
|
||||||
|
|
||||||
|
CHECK_EQ(compactor.fragmentationPercent(buffer), 38);
|
||||||
|
|
||||||
|
std::vector<ChunkFragment> fragments = compactor.survey(buffer);
|
||||||
|
CHECK_EQ(fragments.size(), static_cast<std::size_t>(2));
|
||||||
|
if (fragments.size() != 2) return;
|
||||||
|
|
||||||
|
CHECK_EQ(fragments[0].sectorOffset, static_cast<uint32_t>(2));
|
||||||
|
CHECK_EQ(fragments[0].sectorCount, static_cast<uint32_t>(4));
|
||||||
|
CHECK_EQ(fragments[0].payloadBytes, static_cast<uint32_t>(200));
|
||||||
|
CHECK_EQ(fragments[0].neededSectors, static_cast<uint32_t>(4));
|
||||||
|
CHECK_EQ(fragments[0].excessSectors, static_cast<uint32_t>(0));
|
||||||
|
CHECK_EQ(fragments[0].slackBytes, static_cast<uint32_t>(56));
|
||||||
|
CHECK(fragments[0].reachable);
|
||||||
|
|
||||||
|
CHECK_EQ(fragments[1].sectorOffset, static_cast<uint32_t>(10));
|
||||||
|
CHECK_EQ(fragments[1].sectorCount, static_cast<uint32_t>(3));
|
||||||
|
CHECK_EQ(fragments[1].neededSectors, static_cast<uint32_t>(2));
|
||||||
|
CHECK_EQ(fragments[1].excessSectors, static_cast<uint32_t>(1));
|
||||||
|
CHECK_EQ(fragments[1].slackBytes, static_cast<uint32_t>(92));
|
||||||
|
CHECK(fragments[1].reachable);
|
||||||
|
}
|
||||||
|
|
||||||
|
VF_TEST(aFreshContainerIsCanonicalAndUnfragmented) {
|
||||||
|
ByteBuffer buffer = writeSaveContainer(uniformContainer(4));
|
||||||
|
RegionCompactor compactor;
|
||||||
|
CHECK_EQ(compactor.fragmentationPercent(buffer), 0);
|
||||||
|
CHECK(isCanonicalSaveLayout(buffer.data(), buffer.size()));
|
||||||
|
|
||||||
|
ByteBuffer out;
|
||||||
|
CompactionStats stats;
|
||||||
|
CHECK(compactor.compact(buffer, out, stats));
|
||||||
|
CHECK(stats.ok);
|
||||||
|
CHECK(out == buffer);
|
||||||
|
CHECK_EQ(stats.chunksMoved, static_cast<std::size_t>(0));
|
||||||
|
CHECK_EQ(stats.chunksDropped, static_cast<std::size_t>(0));
|
||||||
|
CHECK_EQ(stats.chunksKept, static_cast<std::size_t>(4));
|
||||||
|
CHECK_EQ(stats.bytesReclaimed, static_cast<std::size_t>(0));
|
||||||
|
CHECK_EQ(stats.holeSectors, static_cast<uint32_t>(0));
|
||||||
|
CHECK_EQ(stats.excessSectors, static_cast<uint32_t>(0));
|
||||||
|
CHECK_EQ(stats.fragmentationBefore, 0);
|
||||||
|
CHECK_EQ(stats.fragmentationAfter, 0);
|
||||||
|
CHECK_EQ(stats.operations, static_cast<std::size_t>(5));
|
||||||
|
}
|
||||||
|
|
||||||
|
VF_TEST(aChunkThatOutgrowsItsRunMovesToTheEndAndLeavesAHole) {
|
||||||
|
ByteBuffer buffer = writeSaveContainer(uniformContainer(3));
|
||||||
|
RegionCompactor compactor;
|
||||||
|
std::vector<ChunkFragment> before = compactor.survey(buffer);
|
||||||
|
uint32_t abandoned = before[1].sectorCount;
|
||||||
|
std::size_t sizeBefore = buffer.size();
|
||||||
|
|
||||||
|
CHECK(updateChunkInPlace(buffer, buildRecord(Vec3i{1, 0, 0}, busyChunk(3)), false));
|
||||||
|
CHECK(buffer.size() > sizeBefore);
|
||||||
|
|
||||||
Reference in new issue
Block a user