From f58da5b2f85f86d8436e0b50096f2280255abb52 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Sat, 12 Sep 2026 20:26:25 +0200 Subject: [PATCH] test: cover texturedChunk and buildRecord --- tests/testSaveMigration.cpp | 158 ++++++++++++++++++++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 tests/testSaveMigration.cpp diff --git a/tests/testSaveMigration.cpp b/tests/testSaveMigration.cpp new file mode 100644 index 0000000..73c9818 --- /dev/null +++ b/tests/testSaveMigration.cpp @@ -0,0 +1,158 @@ +#include "testHarness.h" + +#include "vf/Block.h" +#include "vf/Region.h" +#include "vf/SaveIntegrity.h" +#include "vf/SaveMigration.h" + +#include +#include + +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(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(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(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(v), 4); + ByteBuffer buffer = writeSaveContainer(container); + CHECK(isCanonicalSaveLayout(buffer.data(), buffer.size())); + CHECK_EQ(buffer.size() % saveSectorBytes, static_cast(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(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(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(2)); +} +