diff --git a/tests/testWorldBackup.cpp b/tests/testWorldBackup.cpp new file mode 100644 index 0000000..cce79e1 --- /dev/null +++ b/tests/testWorldBackup.cpp @@ -0,0 +1,160 @@ +#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)); +