diff --git a/tests/testWorldBackup.cpp b/tests/testWorldBackup.cpp index cce79e1..24938be 100644 --- a/tests/testWorldBackup.cpp +++ b/tests/testWorldBackup.cpp @@ -158,3 +158,159 @@ VF_TEST(anIncrementalKeepsOnlyTheChunksThatMoved) { CHECK_EQ(full->state.size(), static_cast(3)); CHECK_EQ(full->parentId, static_cast(0)); + CHECK(first->kind == BackupKind::Incremental); + CHECK_EQ(first->parentId, id0); + CHECK_EQ(first->stored.size(), static_cast(1)); + CHECK(first->findStored({1, 0, 0}) != nullptr); + CHECK(first->findStored({0, 0, 0}) == nullptr); + CHECK(first->removed.empty()); + + CHECK_EQ(second->stored.size(), static_cast(2)); + CHECK(second->findStored({2, 0, 0}) != nullptr); + CHECK(second->findStored({3, 0, 0}) != nullptr); + CHECK_EQ(second->state.size(), static_cast(4)); + + CHECK(third->stored.empty()); + CHECK_EQ(third->removed.size(), static_cast(1)); + CHECK(third->removed[0] == Vec3i(0, 0, 0)); + + CHECK(first->storedBytes() < full->storedBytes()); + CHECK_EQ(third->storedBytes(), static_cast(0)); + CHECK_EQ(store.storedBytes(), + full->storedBytes() + first->storedBytes() + second->storedBytes()); +} + +VF_TEST(aChainMissingALinkIsReportedInsteadOfRestored) { + 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; + 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); + CHECK(store.restore(id2).ok); + + CHECK(store.drop(id1)); + CHECK(!store.drop(id1)); + CHECK_EQ(store.index().count(), static_cast(2)); + + RestoreResult broken = store.restore(id2); + CHECK(!broken.ok); + CHECK(broken.save.empty()); + CHECK(broken.failure.find("chain broken") != std::string::npos); + CHECK(broken.failure.find("#" + std::to_string(id1)) != std::string::npos); + + bool complete = true; + std::string walkFailure; + std::vector walk = store.index().chainTo(id2, complete, walkFailure); + CHECK(!complete); + CHECK(walk.empty()); + CHECK(!walkFailure.empty()); + + RestoreResult stillGood = store.restore(id0); + CHECK(stillGood.ok); + CHECK(stillGood.save == save0); + + RestoreResult unknown = store.restore(9999); + CHECK(!unknown.ok); + CHECK(unknown.failure.find("not in the index") != std::string::npos); +} + +VF_TEST(chainWalkListsTheFullBackupFirstAndTheAskedForEntryLast) { + 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; + 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); + + bool complete = false; + std::string walkFailure; + std::vector walk = store.index().chainTo(id2, complete, walkFailure); + CHECK(complete); + CHECK(walkFailure.empty()); + CHECK_EQ(walk.size(), static_cast(3)); + if (walk.size() != 3) return; + CHECK_EQ(walk[0], id0); + CHECK_EQ(walk[1], id1); + CHECK_EQ(walk[2], id2); + CHECK_EQ(store.index().latestId(), id2); +} + +VF_TEST(captureRefusesAFragmentedContainerAndPointsAtTheCompactor) { + ByteBuffer save = writeSaveContainer(baseContainer()); + CHECK(updateChunkInPlace(save, buildRecord({1, 0, 0}, texturedChunk(8), SaveVersion::V4), + false)); + CHECK(!isCanonicalSaveLayout(save.data(), save.size())); + + BackupStore store; + CHECK_EQ(store.capture(BackupKind::Full, save, 0), static_cast(0)); + CHECK(store.lastFailure().find("compact") != std::string::npos); + CHECK_EQ(store.index().count(), static_cast(0)); + + RegionCompactor compactor; + ByteBuffer tidy; + CompactionStats stats; + CHECK(compactor.compact(save, tidy, stats)); + + uint32_t id = store.capture(BackupKind::Full, tidy, 0); + CHECK(id != 0u); + CHECK(store.restore(id).save == tidy); +} + +VF_TEST(captureRefusesASaveTheScannerCallsDamaged) { + ByteBuffer save = writeSaveContainer(baseContainer()); + SaveHeader header; + CHECK(readSaveHeader(save.data(), save.size(), header)); + std::vector entries; + CHECK(readSaveTable(save.data(), save.size(), header, entries)); + std::size_t payloadAt = static_cast(entries[1].sectorOffset) * header.sectorBytes; + saveWriteU32At(save.data(), save.size(), payloadAt + 8, 0u); + + BackupStore store; + CHECK_EQ(store.capture(BackupKind::Full, save, 0), static_cast(0)); + CHECK(store.lastFailure().find("integrity") != std::string::npos); + + ByteBuffer rubbish(30, 0); + CHECK_EQ(store.capture(BackupKind::Full, rubbish, 0), static_cast(0)); + CHECK(store.lastFailure().find("readable container") != std::string::npos); +} + +VF_TEST(anIncrementalWithoutAFullBehindItIsRefused) { + ByteBuffer save = writeSaveContainer(baseContainer()); + BackupStore store; + CHECK_EQ(store.capture(BackupKind::Incremental, save, 0), static_cast(0)); + CHECK(store.lastFailure().find("no full backup") != std::string::npos); + + CHECK(store.capture(BackupKind::Full, save, 100) != 0u); + CHECK_EQ(store.capture(BackupKind::Incremental, save, 50), static_cast(0)); + CHECK(store.lastFailure().find("runs behind") != std::string::npos); + + SaveContainer elsewhere = baseContainer(); + elsewhere.regionX = 4; + for (SaveChunkRecord& record : elsewhere.chunks) record.coord.x += 64; + ByteBuffer other = writeSaveContainer(elsewhere); + CHECK_EQ(store.capture(BackupKind::Incremental, other, 200), static_cast(0)); + CHECK(store.lastFailure().find("chain covers region") != std::string::npos); + + RestoreResult empty = BackupStore{}.restoreLatest(); + CHECK(!empty.ok); + CHECK(empty.failure.find("empty") != std::string::npos); +} + +VF_TEST(captureMigratedLiftsAnOldSaveBeforeStoringIt) { + SaveContainer legacy; + legacy.version = SaveVersion::V1; + legacy.chunks.push_back(buildRecord({0, 0, 0}, texturedChunk(1), SaveVersion::V1)); + legacy.chunks.push_back(buildRecord({1, 0, 0}, texturedChunk(2), SaveVersion::V1)); + ByteBuffer ancient = writeSaveContainer(legacy); +