diff --git a/tests/testWorldBackup.cpp b/tests/testWorldBackup.cpp index 24938be..a49945c 100644 --- a/tests/testWorldBackup.cpp +++ b/tests/testWorldBackup.cpp @@ -314,3 +314,139 @@ VF_TEST(captureMigratedLiftsAnOldSaveBeforeStoringIt) { legacy.chunks.push_back(buildRecord({1, 0, 0}, texturedChunk(2), SaveVersion::V1)); ByteBuffer ancient = writeSaveContainer(legacy); + BackupStore store; + uint32_t id = store.captureMigrated(BackupKind::Full, ancient, 0); + CHECK(id != 0u); + const BackupEntry* entry = store.index().find(id); + CHECK(entry != nullptr); + if (entry == nullptr) return; + CHECK(entry->version == currentSaveVersion); + + RestoreResult restored = store.restore(id); + CHECK(restored.ok); + SaveContainer reloaded; + CHECK(readSaveContainer(restored.save.data(), restored.save.size(), reloaded)); + CHECK(reloaded.version == SaveVersion::V4); + for (const SaveChunkRecord& before : legacy.chunks) { + const SaveChunkRecord* after = reloaded.find(before.coord); + CHECK(after != nullptr); + if (after == nullptr) continue; + for (std::size_t i = 0; i < before.blocks.raw().size(); ++i) + CHECK(before.blocks.raw()[i] == after->blocks.raw()[i]); + } + + ByteBuffer modern = writeSaveContainer(baseContainer()); + saveWriteU32At(modern.data(), modern.size(), 4, 9u); + CHECK_EQ(store.captureMigrated(BackupKind::Full, modern, 10), static_cast(0)); + CHECK(store.lastFailure().find("migration") != std::string::npos); +} + +VF_TEST(retentionKeepsTheLatestAndPrunesTheChainsNobodyNeeds) { + SaveContainer container = baseContainer(); + BackupStore store; + store.retention.keepLatest = 2; + store.retention.dailyBuckets = 1; + store.retention.weeklyBuckets = 1; + + std::vector ids; + std::vector saves; + const uint64_t ticks[6] = {0, 1000, 24000, 25000, 48000, 49000}; + const BackupKind kinds[6] = {BackupKind::Full, BackupKind::Incremental, + BackupKind::Full, BackupKind::Incremental, + BackupKind::Full, BackupKind::Incremental}; + for (int step = 0; step < 6; ++step) { + replaceChunk(container, {1, 0, 0}, texturedChunk(step)); + ByteBuffer save = writeSaveContainer(container); + uint32_t id = store.capture(kinds[step], save, ticks[step]); + CHECK(id != 0u); + ids.push_back(id); + saves.push_back(save); + } + CHECK_EQ(store.index().count(), static_cast(6)); + + std::vector removed = store.prune(49000); + CHECK_EQ(removed.size(), static_cast(4)); + if (removed.size() == 4) { + CHECK_EQ(removed[0], ids[0]); + CHECK_EQ(removed[1], ids[1]); + CHECK_EQ(removed[2], ids[2]); + CHECK_EQ(removed[3], ids[3]); + } + CHECK_EQ(store.index().count(), static_cast(2)); + CHECK(store.index().find(ids[4]) != nullptr); + CHECK(store.index().find(ids[5]) != nullptr); + + RestoreResult survivor = store.restore(ids[5]); + CHECK(survivor.ok); + CHECK(survivor.save == saves[5]); + CHECK(!store.restore(ids[1]).ok); +} + +VF_TEST(retentionAlsoKeepsOneBackupPerRecentDay) { + ByteBuffer save = writeSaveContainer(baseContainer()); + BackupStore store; + store.retention.keepLatest = 1; + store.retention.dailyBuckets = 2; + store.retention.weeklyBuckets = 0; + + uint32_t oldest = store.capture(BackupKind::Full, save, 0); + uint32_t sameDay = store.capture(BackupKind::Full, save, 100); + uint32_t nextDay = store.capture(BackupKind::Full, save, 30000); + CHECK(oldest != 0u); + CHECK(sameDay != 0u); + CHECK(nextDay != 0u); + + std::vector removed = store.prune(30000); + CHECK_EQ(removed.size(), static_cast(1)); + if (!removed.empty()) CHECK_EQ(removed[0], oldest); + CHECK(store.index().find(sameDay) != nullptr); + CHECK(store.index().find(nextDay) != nullptr); +} + +VF_TEST(retentionNeverPrunesAnAncestorAKeptBackupStillNeeds) { + 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)); + ByteBuffer save2 = writeSaveContainer(container); + + BackupStore store; + store.retention.keepLatest = 1; + store.retention.dailyBuckets = 0; + store.retention.weeklyBuckets = 0; + + 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); + + std::vector removed = store.prune(2000); + CHECK(removed.empty()); + CHECK_EQ(store.index().count(), static_cast(3)); + CHECK(store.index().find(id0) != nullptr); + CHECK(store.index().find(id1) != nullptr); + CHECK(store.restore(id2).save == save2); +} + +VF_TEST(aBrokenChainCannotBeExtendedWithANewIncremental) { + SaveContainer container = baseContainer(); + ByteBuffer save0 = writeSaveContainer(container); + replaceChunk(container, {1, 0, 0}, texturedChunk(3)); + ByteBuffer save1 = writeSaveContainer(container); + + BackupStore store; + uint32_t id0 = store.capture(BackupKind::Full, save0, 0); + uint32_t id1 = store.capture(BackupKind::Incremental, save1, 1000); + CHECK(id1 != 0u); + CHECK(store.drop(id0)); + + replaceChunk(container, {2, 0, 0}, texturedChunk(5)); + ByteBuffer save2 = writeSaveContainer(container); + CHECK_EQ(store.capture(BackupKind::Incremental, save2, 2000), static_cast(0)); + CHECK(store.lastFailure().find("broken chain") != std::string::npos); +} + +VF_TEST(backupKindNamesStayReadable) { + CHECK(std::string(backupKindName(BackupKind::Full)) == "full"); + CHECK(std::string(backupKindName(BackupKind::Incremental)) == "incremental"); +}