Files
voxelforge/tests/testSaveMigration.cpp
T

159 lines
5.5 KiB
C++

#include "testHarness.h"
#include "vf/Block.h"
#include "vf/Region.h"
#include "vf/SaveIntegrity.h"
#include "vf/SaveMigration.h"
#include <cstdint>
#include <vector>
using namespace vf;
namespace {
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<Block>(value));
}
return chunk;
}
SaveChunkRecord buildRecord(const Vec3i& coord, int seed, SaveVersion version) {
SaveChunkRecord record;
record.coord = coord;
record.blocks = texturedChunk(seed);
if (versionHasLight(version))
record.light = defaultChunkLight(record.blocks, saveDefaultSkyLight);
return record;
}
SaveContainer buildContainer(SaveVersion version, int chunkTotal) {
SaveContainer container;
container.version = version;
for (int i = 0; i < chunkTotal; ++i) {
Vec3i coord{i % 4, i / 4, (i * 3) % 5};
container.chunks.push_back(buildRecord(coord, i, version));
}
return container;
}
std::size_t distinctBlocks(const Chunk& chunk) {
bool seen[256] = {false};
std::size_t total = 0;
for (Block block : chunk.raw()) {
std::size_t slot = static_cast<std::size_t>(block);
if (seen[slot]) continue;
seen[slot] = true;
++total;
}
return total;
}
bool sameBlocks(const Chunk& a, const Chunk& b) {
for (std::size_t i = 0; i < a.raw().size(); ++i)
if (a.raw()[i] != b.raw()[i]) return false;
return true;
}
} // namespace
VF_TEST(containerRoundTripsAtEveryFormatVersion) {
for (uint32_t v = 1; v <= 4; ++v) {
SaveVersion version = static_cast<SaveVersion>(v);
SaveContainer original = buildContainer(version, 3);
ByteBuffer buffer = writeSaveContainer(original);
SaveContainer reloaded;
CHECK(readSaveContainer(buffer.data(), buffer.size(), reloaded));
CHECK(reloaded.version == version);
CHECK_EQ(reloaded.chunks.size(), original.chunks.size());
for (const SaveChunkRecord& record : original.chunks) {
const SaveChunkRecord* back = reloaded.find(record.coord);
CHECK(back != nullptr);
if (back == nullptr) continue;
CHECK(sameBlocks(record.blocks, back->blocks));
CHECK(record.light == back->light);
}
}
}
VF_TEST(everyFreshContainerUsesTheCanonicalLayout) {
for (uint32_t v = 1; v <= 4; ++v) {
SaveContainer container = buildContainer(static_cast<SaveVersion>(v), 4);
ByteBuffer buffer = writeSaveContainer(container);
CHECK(isCanonicalSaveLayout(buffer.data(), buffer.size()));
CHECK_EQ(buffer.size() % saveSectorBytes, static_cast<std::size_t>(0));
}
}
VF_TEST(versionFeatureFlagsMatchTheDocumentedFormatTable) {
CHECK(!versionHasPalette(SaveVersion::V1));
CHECK(versionHasPalette(SaveVersion::V2));
CHECK(!versionHasLight(SaveVersion::V2));
CHECK(versionHasLight(SaveVersion::V3));
CHECK(!versionHasChecksum(SaveVersion::V2));
CHECK(versionHasChecksum(SaveVersion::V3));
CHECK(!versionHasMetadata(SaveVersion::V3));
CHECK(versionHasMetadata(SaveVersion::V4));
CHECK(currentSaveVersion == SaveVersion::V4);
CHECK(oldestSaveVersion == SaveVersion::V1);
CHECK(std::string(saveVersionName(SaveVersion::V1)) == "v1");
CHECK(std::string(saveVersionName(SaveVersion::V4)) == "v4");
CHECK(isKnownSaveVersion(1u));
CHECK(isKnownSaveVersion(4u));
CHECK(!isKnownSaveVersion(0u));
CHECK(!isKnownSaveVersion(5u));
}
VF_TEST(migrationChainFromV1ToV4KeepsEveryBlockIdentical) {
SaveContainer original = buildContainer(SaveVersion::V1, 5);
ByteBuffer legacy = writeSaveContainer(original);
SaveMigrator migrator;
MigrationResult result = migrator.migrateToCurrent(legacy);
CHECK(result.ok);
CHECK(result.failure.empty());
CHECK(result.from == SaveVersion::V1);
CHECK(result.to == SaveVersion::V4);
CHECK_EQ(result.chunksMigrated, static_cast<std::size_t>(5));
SaveContainer lifted;
CHECK(readSaveContainer(result.output.data(), result.output.size(), lifted));
CHECK(lifted.version == SaveVersion::V4);
CHECK_EQ(lifted.chunks.size(), original.chunks.size());
for (const SaveChunkRecord& before : original.chunks) {
const SaveChunkRecord* after = lifted.find(before.coord);
CHECK(after != nullptr);
if (after == nullptr) continue;
CHECK(sameBlocks(before.blocks, after->blocks));
}
}
VF_TEST(migrationAppliesOneStepPerVersionHop) {
SaveContainer original = buildContainer(SaveVersion::V1, 2);
ByteBuffer legacy = writeSaveContainer(original);
SaveMigrator migrator;
MigrationResult result = migrator.migrateToCurrent(legacy);
CHECK(result.ok);
CHECK_EQ(result.applied.size(), static_cast<std::size_t>(3));
CHECK(result.applied[0].from == SaveVersion::V1);
CHECK(result.applied[0].to == SaveVersion::V2);
CHECK(result.applied[1].from == SaveVersion::V2);
CHECK(result.applied[1].to == SaveVersion::V3);
CHECK(result.applied[2].from == SaveVersion::V3);
CHECK(result.applied[2].to == SaveVersion::V4);
for (const MigrationStep& step : result.applied) CHECK(!step.summary.empty());
MigrationPlan plan = SaveMigrator::planFor(SaveVersion::V2, SaveVersion::V4);
CHECK(plan.possible);
CHECK(plan.refusal.empty());
CHECK_EQ(plan.steps.size(), static_cast<std::size_t>(2));
}