test: extend coverage of testSaveIntegrity
This commit is contained in:
1 parent
6bcdf07562
commit
a7065034d1
1 file changed
+161
@@ -314,3 +314,164 @@ VF_TEST(lightThatDisagreesWithItsBlocksIsOnlyAWarning) {
|
|||||||
container.chunks.push_back(std::move(record));
|
container.chunks.push_back(std::move(record));
|
||||||
ByteBuffer buffer = writeSaveContainer(container);
|
ByteBuffer buffer = writeSaveContainer(container);
|
||||||
|
|
||||||
|
SaveScanner scanner;
|
||||||
|
IntegrityReport report = scanner.scan(buffer);
|
||||||
|
CHECK_EQ(report.issues.size(), static_cast<std::size_t>(1));
|
||||||
|
CHECK(report.issues[0].kind == IntegrityIssueKind::LightingInconsistent);
|
||||||
|
CHECK(report.issues[0].severity == IssueSeverity::Warning);
|
||||||
|
CHECK(report.issues[0].detail.find("2 light cells") != std::string::npos);
|
||||||
|
CHECK(!report.repairable());
|
||||||
|
CHECK_EQ(report.countAtLeast(IssueSeverity::Recoverable), static_cast<std::size_t>(0));
|
||||||
|
CHECK_EQ(report.countAtLeast(IssueSeverity::Warning), static_cast<std::size_t>(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
VF_TEST(bytesPastTheLastSectorRunAreOnlyInformational) {
|
||||||
|
ByteBuffer buffer = writeSaveContainer(buildContainer(SaveVersion::V4, 2));
|
||||||
|
std::size_t before = buffer.size();
|
||||||
|
buffer.resize(before + 300, 0);
|
||||||
|
|
||||||
|
SaveScanner scanner;
|
||||||
|
IntegrityReport report = scanner.scan(buffer);
|
||||||
|
CHECK_EQ(report.issues.size(), static_cast<std::size_t>(1));
|
||||||
|
CHECK(report.issues[0].kind == IntegrityIssueKind::TrailingBytes);
|
||||||
|
CHECK(report.issues[0].severity == IssueSeverity::Info);
|
||||||
|
CHECK_EQ(report.issues[0].byteOffset, before);
|
||||||
|
CHECK(!report.repairable());
|
||||||
|
CHECK(!report.fatal());
|
||||||
|
}
|
||||||
|
|
||||||
|
VF_TEST(aRecordThatDoesNotDecodeIsRecoverable) {
|
||||||
|
ByteBuffer buffer = writeSaveContainer(buildContainer(SaveVersion::V4, 3));
|
||||||
|
EntrySlot slot = slotOf(buffer, 1);
|
||||||
|
saveWriteU32At(buffer.data(), buffer.size(), slot.payloadAt + 8, 0u);
|
||||||
|
|
||||||
|
SaveScanner scanner;
|
||||||
|
IntegrityReport report = scanner.scan(buffer);
|
||||||
|
CHECK_EQ(report.countOf(IntegrityIssueKind::MalformedRecord), static_cast<std::size_t>(1));
|
||||||
|
const IntegrityIssue* issue = report.firstOf(IntegrityIssueKind::MalformedRecord);
|
||||||
|
CHECK(issue != nullptr);
|
||||||
|
if (issue == nullptr) return;
|
||||||
|
CHECK(issue->severity == IssueSeverity::Recoverable);
|
||||||
|
CHECK_EQ(issue->byteOffset, slot.payloadAt);
|
||||||
|
CHECK_EQ(issue->entryIndex, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
VF_TEST(repairLeavesEveryHealthyChunkByteIdentical) {
|
||||||
|
SaveContainer container = buildContainer(SaveVersion::V4, 4);
|
||||||
|
ByteBuffer healthy = writeSaveContainer(container);
|
||||||
|
|
||||||
|
ByteBuffer damaged = healthy;
|
||||||
|
EntrySlot slot = slotOf(damaged, 2);
|
||||||
|
saveWriteU32At(damaged.data(), damaged.size(), slot.payloadAt + 8, 0u);
|
||||||
|
|
||||||
|
SaveScanner scanner;
|
||||||
|
IntegrityReport report = scanner.scan(damaged);
|
||||||
|
RepairPlan plan = scanner.planRepair(damaged.data(), damaged.size(), report);
|
||||||
|
CHECK(plan.possible);
|
||||||
|
CHECK_EQ(plan.countOf(RepairActionKind::DropChunk), static_cast<std::size_t>(1));
|
||||||
|
|
||||||
|
RepairResult repaired = scanner.repair(damaged.data(), damaged.size(), plan);
|
||||||
|
CHECK(repaired.ok);
|
||||||
|
CHECK_EQ(repaired.chunksDropped, static_cast<std::size_t>(1));
|
||||||
|
CHECK_EQ(repaired.chunksPreserved, static_cast<std::size_t>(3));
|
||||||
|
CHECK_EQ(repaired.bytesLost, slot.payloadBytes);
|
||||||
|
|
||||||
|
const Vec3i survivors[3] = {{0, 0, 0}, {1, 0, 2}, {3, 0, 6}};
|
||||||
|
for (const Vec3i& coord : survivors) {
|
||||||
|
ByteBuffer before;
|
||||||
|
ByteBuffer after;
|
||||||
|
CHECK(readChunkPayloadBytes(healthy.data(), healthy.size(), coord, before));
|
||||||
|
CHECK(readChunkPayloadBytes(repaired.output.data(), repaired.output.size(), coord, after));
|
||||||
|
CHECK(before == after);
|
||||||
|
}
|
||||||
|
ByteBuffer gone;
|
||||||
|
CHECK(!readChunkPayloadBytes(repaired.output.data(), repaired.output.size(), Vec3i{2, 0, 4},
|
||||||
|
gone));
|
||||||
|
CHECK(scanner.scan(repaired.output).clean());
|
||||||
|
CHECK(isCanonicalSaveLayout(repaired.output.data(), repaired.output.size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
VF_TEST(repairRewritesAStaleChecksumBackToTheOriginalBytes) {
|
||||||
|
ByteBuffer healthy = writeSaveContainer(buildContainer(SaveVersion::V4, 3));
|
||||||
|
ByteBuffer damaged = healthy;
|
||||||
|
EntrySlot slot = slotOf(damaged, 1);
|
||||||
|
std::size_t checksumAt = slot.payloadAt + slot.payloadBytes - 4;
|
||||||
|
damaged[checksumAt] = static_cast<uint8_t>(damaged[checksumAt] ^ 0xFFu);
|
||||||
|
|
||||||
|
SaveScanner scanner;
|
||||||
|
IntegrityReport report = scanner.scan(damaged);
|
||||||
|
RepairPlan plan = scanner.planRepair(damaged.data(), damaged.size(), report);
|
||||||
|
CHECK(plan.possible);
|
||||||
|
CHECK_EQ(plan.countOf(RepairActionKind::RecomputeChecksum), static_cast<std::size_t>(1));
|
||||||
|
|
||||||
|
RepairResult repaired = scanner.repair(damaged.data(), damaged.size(), plan);
|
||||||
|
CHECK(repaired.ok);
|
||||||
|
CHECK_EQ(repaired.checksumsRewritten, static_cast<std::size_t>(1));
|
||||||
|
CHECK_EQ(repaired.chunksDropped, static_cast<std::size_t>(0));
|
||||||
|
CHECK_EQ(repaired.chunksPreserved, static_cast<std::size_t>(3));
|
||||||
|
CHECK_EQ(repaired.bytesLost, static_cast<std::size_t>(0));
|
||||||
|
CHECK(repaired.output == healthy);
|
||||||
|
}
|
||||||
|
|
||||||
|
VF_TEST(repairRebuildsAChunkWhoseIndicesLeftThePalette) {
|
||||||
|
SaveContainer container;
|
||||||
|
container.version = SaveVersion::V2;
|
||||||
|
Chunk blocks = uniformChunk(Block::Stone);
|
||||||
|
blocks.set(1, 0, 0, Block::Grass);
|
||||||
|
container.chunks.push_back(buildRecord(Vec3i{1, 2, 1}, blocks, SaveVersion::V2));
|
||||||
|
container.chunks.push_back(
|
||||||
|
buildRecord(Vec3i{2, 2, 1}, uniformChunk(Block::Dirt), SaveVersion::V2));
|
||||||
|
ByteBuffer buffer = writeSaveContainer(container);
|
||||||
|
|
||||||
|
EntrySlot slot = slotOf(buffer, 0);
|
||||||
|
buffer[slot.payloadAt + 10 + 5] = 200;
|
||||||
|
|
||||||
|
SaveScanner scanner;
|
||||||
|
scanner.worldSeed = 0x5EED1234u;
|
||||||
|
IntegrityReport report = scanner.scan(buffer);
|
||||||
|
RepairPlan plan = scanner.planRepair(buffer.data(), buffer.size(), report);
|
||||||
|
CHECK(plan.possible);
|
||||||
|
CHECK_EQ(plan.countOf(RepairActionKind::RegenerateChunk), static_cast<std::size_t>(1));
|
||||||
|
CHECK_EQ(plan.worldSeed, static_cast<uint64_t>(0x5EED1234u));
|
||||||
|
|
||||||
|
RepairResult repaired = scanner.repair(buffer.data(), buffer.size(), plan);
|
||||||
|
CHECK(repaired.ok);
|
||||||
|
CHECK_EQ(repaired.chunksRegenerated, static_cast<std::size_t>(1));
|
||||||
|
CHECK_EQ(repaired.chunksPreserved, static_cast<std::size_t>(1));
|
||||||
|
CHECK(repaired.bytesLost > 0u);
|
||||||
|
|
||||||
|
SaveContainer rebuilt;
|
||||||
|
CHECK(readSaveContainer(repaired.output.data(), repaired.output.size(), rebuilt));
|
||||||
|
CHECK_EQ(rebuilt.chunks.size(), static_cast<std::size_t>(2));
|
||||||
|
const SaveChunkRecord* restored = rebuilt.find({1, 2, 1});
|
||||||
|
CHECK(restored != nullptr);
|
||||||
|
if (restored != nullptr)
|
||||||
|
CHECK(sameBlocks(restored->blocks,
|
||||||
|
regenerateChunkFromSeed(0x5EED1234u, Vec3i{1, 2, 1})));
|
||||||
|
CHECK(scanner.scan(repaired.output).clean());
|
||||||
|
}
|
||||||
|
|
||||||
|
VF_TEST(repairDropsInsteadOfRebuildingWhenRegenerationIsTurnedOff) {
|
||||||
|
SaveContainer container;
|
||||||
|
container.version = SaveVersion::V2;
|
||||||
|
Chunk blocks = uniformChunk(Block::Stone);
|
||||||
|
blocks.set(1, 0, 0, Block::Grass);
|
||||||
|
container.chunks.push_back(buildRecord(Vec3i{1, 2, 1}, blocks, SaveVersion::V2));
|
||||||
|
container.chunks.push_back(
|
||||||
|
buildRecord(Vec3i{2, 2, 1}, uniformChunk(Block::Dirt), SaveVersion::V2));
|
||||||
|
ByteBuffer buffer = writeSaveContainer(container);
|
||||||
|
buffer[slotOf(buffer, 0).payloadAt + 10 + 5] = 200;
|
||||||
|
|
||||||
|
SaveScanner scanner;
|
||||||
|
scanner.allowRegeneration = false;
|
||||||
|
IntegrityReport report = scanner.scan(buffer);
|
||||||
|
RepairPlan plan = scanner.planRepair(buffer.data(), buffer.size(), report);
|
||||||
|
CHECK_EQ(plan.countOf(RepairActionKind::RegenerateChunk), static_cast<std::size_t>(0));
|
||||||
|
CHECK_EQ(plan.countOf(RepairActionKind::DropChunk), static_cast<std::size_t>(1));
|
||||||
|
|
||||||
|
RepairResult repaired = scanner.repair(buffer.data(), buffer.size(), plan);
|
||||||
|
CHECK(repaired.ok);
|
||||||
|
CHECK_EQ(repaired.chunksRegenerated, static_cast<std::size_t>(0));
|
||||||
|
CHECK_EQ(repaired.chunksDropped, static_cast<std::size_t>(1));
|
||||||
|
}
|
||||||
|
|
||||||
Reference in new issue
Block a user