#include "testHarness.h" #include "vf/Block.h" #include "vf/RegionCompact.h" #include "vf/SaveIntegrity.h" #include "vf/SaveMigration.h" #include "vf/WorldBackup.h" #include #include #include 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 texturedChunk(int seed) { Chunk chunk; for (int x = 0; x < chunkSize; ++x) for (int y = 0; y < chunkSize; ++y) for (int z = 0; z < chunkSize; ++z) { int value = 1 + (x * 7 + y * 5 + z * 3 + seed) % 12; chunk.set(x, y, z, static_cast(value)); } return chunk; } SaveChunkRecord buildRecord(const Vec3i& coord, const Chunk& blocks, SaveVersion version) { SaveChunkRecord record; record.coord = coord; record.blocks = blocks; if (versionHasLight(version)) record.light = defaultChunkLight(blocks, saveDefaultSkyLight); return record; } SaveContainer baseContainer() { SaveContainer container; container.version = SaveVersion::V4; container.chunks.push_back(buildRecord({0, 0, 0}, uniformChunk(Block::Stone), SaveVersion::V4)); container.chunks.push_back(buildRecord({1, 0, 0}, uniformChunk(Block::Dirt), SaveVersion::V4)); container.chunks.push_back(buildRecord({2, 0, 0}, uniformChunk(Block::Sand), SaveVersion::V4)); return container; } void replaceChunk(SaveContainer& container, const Vec3i& coord, const Chunk& blocks) { for (SaveChunkRecord& record : container.chunks) { if (!(record.coord == coord)) continue; record.blocks = blocks; record.light = defaultChunkLight(blocks, saveDefaultSkyLight); return; } container.chunks.push_back(buildRecord(coord, blocks, container.version)); } void dropChunk(SaveContainer& container, const Vec3i& coord) { for (std::size_t i = 0; i < container.chunks.size(); ++i) { if (!(container.chunks[i].coord == coord)) continue; container.chunks.erase(container.chunks.begin() + static_cast(i)); return; } } } // namespace VF_TEST(aFullBackupRestoresTheExactBytesItCaptured) { ByteBuffer save = writeSaveContainer(baseContainer()); BackupStore store; uint32_t id = store.capture(BackupKind::Full, save, 0); CHECK(id != 0u); CHECK(store.lastFailure().empty()); RestoreResult restored = store.restore(id); CHECK(restored.ok); CHECK(restored.failure.empty()); CHECK(restored.save == save); CHECK_EQ(restored.chunksRestored, static_cast(3)); CHECK_EQ(restored.chunksFromFull, static_cast(3)); CHECK_EQ(restored.chunksFromIncrements, static_cast(0)); CHECK_EQ(restored.incrementsApplied, static_cast(0)); CHECK_EQ(restored.chain.size(), static_cast(1)); } VF_TEST(everyPointOfABackupChainRestoresByteForByte) { SaveContainer container = baseContainer(); ByteBuffer save0 = writeSaveContainer(container); replaceChunk(container, {1, 0, 0}, texturedChunk(3)); ByteBuffer save1 = writeSaveContainer(container); replaceChunk(container, {2, 0, 0}, texturedChunk(4)); replaceChunk(container, {3, 0, 0}, uniformChunk(Block::Gravel)); ByteBuffer save2 = writeSaveContainer(container); dropChunk(container, {0, 0, 0}); ByteBuffer save3 = writeSaveContainer(container); BackupStore store; uint32_t id0 = store.capture(BackupKind::Full, save0, 0); uint32_t id1 = store.capture(BackupKind::Incremental, save1, 1000); uint32_t id2 = store.capture(BackupKind::Incremental, save2, 2000); uint32_t id3 = store.capture(BackupKind::Incremental, save3, 3000); CHECK(id0 != 0u); CHECK(id1 != 0u); CHECK(id2 != 0u); CHECK(id3 != 0u); CHECK(store.restore(id0).save == save0); CHECK(store.restore(id1).save == save1); CHECK(store.restore(id2).save == save2); CHECK(store.restore(id3).save == save3); RestoreResult latest = store.restoreLatest(); CHECK(latest.ok); CHECK(latest.save == save3); CHECK_EQ(latest.chain.size(), static_cast(4)); CHECK_EQ(latest.incrementsApplied, static_cast(3)); CHECK_EQ(latest.chunksRestored, static_cast(3)); CHECK_EQ(latest.chunksRemoved, static_cast(1)); } VF_TEST(anIncrementalKeepsOnlyTheChunksThatMoved) { SaveContainer container = baseContainer(); ByteBuffer save0 = writeSaveContainer(container); replaceChunk(container, {1, 0, 0}, texturedChunk(3)); ByteBuffer save1 = writeSaveContainer(container); replaceChunk(container, {2, 0, 0}, texturedChunk(4)); replaceChunk(container, {3, 0, 0}, uniformChunk(Block::Gravel)); ByteBuffer save2 = writeSaveContainer(container); dropChunk(container, {0, 0, 0}); ByteBuffer save3 = writeSaveContainer(container); BackupStore store; uint32_t id0 = store.capture(BackupKind::Full, save0, 0); uint32_t id1 = store.capture(BackupKind::Incremental, save1, 1000); uint32_t id2 = store.capture(BackupKind::Incremental, save2, 2000); uint32_t id3 = store.capture(BackupKind::Incremental, save3, 3000); const BackupEntry* full = store.index().find(id0); const BackupEntry* first = store.index().find(id1); const BackupEntry* second = store.index().find(id2); const BackupEntry* third = store.index().find(id3); CHECK(full != nullptr); CHECK(first != nullptr); CHECK(second != nullptr); CHECK(third != nullptr); if (full == nullptr || first == nullptr || second == nullptr || third == nullptr) return; CHECK(full->kind == BackupKind::Full); CHECK_EQ(full->stored.size(), static_cast(3)); CHECK_EQ(full->state.size(), static_cast(3)); CHECK_EQ(full->parentId, static_cast(0));