diff --git a/tests/testSaveMigration.cpp b/tests/testSaveMigration.cpp index 73c9818..8313d3c 100644 --- a/tests/testSaveMigration.cpp +++ b/tests/testSaveMigration.cpp @@ -156,3 +156,160 @@ VF_TEST(migrationAppliesOneStepPerVersionHop) { CHECK_EQ(plan.steps.size(), static_cast(2)); } +VF_TEST(everyIntermediateVersionIsItselfAValidContainer) { + SaveContainer original = buildContainer(SaveVersion::V1, 3); + ByteBuffer legacy = writeSaveContainer(original); + SaveMigrator migrator; + SaveScanner scanner; + + for (uint32_t target = 2; target <= 4; ++target) { + MigrationResult result = migrator.migrate(legacy.data(), legacy.size(), + static_cast(target)); + CHECK(result.ok); + IntegrityReport report = scanner.scan(result.output); + CHECK(report.clean()); + CHECK(report.version == static_cast(target)); + CHECK_EQ(report.readableChunks, static_cast(3)); + CHECK(isCanonicalSaveLayout(result.output.data(), result.output.size())); + } +} + +VF_TEST(migrationDownwardIsRefusedWithAStatedReason) { + SaveContainer modern = buildContainer(SaveVersion::V4, 2); + ByteBuffer buffer = writeSaveContainer(modern); + + SaveMigrator migrator; + MigrationResult result = migrator.migrate(buffer.data(), buffer.size(), SaveVersion::V2); + CHECK(!result.ok); + CHECK(result.output.empty()); + CHECK(result.failure.find("downgrade") != std::string::npos); + CHECK(result.failure.find("v4 -> v2") != std::string::npos); + + MigrationPlan plan = SaveMigrator::planFor(SaveVersion::V4, SaveVersion::V1); + CHECK(!plan.possible); + CHECK(plan.steps.empty()); + CHECK(!plan.refusal.empty()); +} + +VF_TEST(migrationToTheSameVersionCopiesTheBufferVerbatim) { + SaveContainer modern = buildContainer(SaveVersion::V4, 3); + ByteBuffer buffer = writeSaveContainer(modern); + + SaveMigrator migrator; + MigrationResult result = migrator.migrate(buffer.data(), buffer.size(), SaveVersion::V4); + CHECK(result.ok); + CHECK(result.applied.empty()); + CHECK(result.output == buffer); +} + +VF_TEST(fieldsAddedByAMigrationGetTheirDocumentedDefaults) { + SaveContainer original = buildContainer(SaveVersion::V2, 2); + ByteBuffer buffer = writeSaveContainer(original); + + SaveMigrator migrator; + MigrationResult toV3 = migrator.migrate(buffer.data(), buffer.size(), SaveVersion::V3); + CHECK(toV3.ok); + SaveContainer lit; + CHECK(readSaveContainer(toV3.output.data(), toV3.output.size(), lit)); + for (const SaveChunkRecord& record : lit.chunks) { + CHECK_EQ(record.light.size(), static_cast(chunkVolume)); + CHECK(record.light == defaultChunkLight(record.blocks, saveDefaultSkyLight)); + } + + MigrationResult toV4 = migrator.migrate(buffer.data(), buffer.size(), SaveVersion::V4); + CHECK(toV4.ok); + SaveContainer stamped; + CHECK(readSaveContainer(toV4.output.data(), toV4.output.size(), stamped)); + for (const SaveChunkRecord& record : stamped.chunks) { + CHECK_EQ(record.meta.inhabitedTicks, static_cast(0)); + CHECK_EQ(record.meta.lastSavedTick, static_cast(0)); + CHECK_EQ(record.meta.generationStage, saveStageComplete); + } +} + +VF_TEST(migrationCountsTheFieldsEachStepCreated) { + SaveContainer original = buildContainer(SaveVersion::V1, 3); + ByteBuffer legacy = writeSaveContainer(original); + + std::size_t expectedPaletteEntries = 0; + for (const SaveChunkRecord& record : original.chunks) + expectedPaletteEntries += distinctBlocks(record.blocks); + + SaveMigrator migrator; + MigrationResult result = migrator.migrateToCurrent(legacy); + CHECK(result.ok); + CHECK_EQ(result.paletteEntriesCreated, expectedPaletteEntries); + CHECK_EQ(result.lightCellsInitialized, static_cast(3 * chunkVolume)); + CHECK_EQ(result.metadataRecordsCreated, static_cast(3)); +} + +VF_TEST(migratorHonoursItsConfiguredDefaults) { + SaveContainer original = buildContainer(SaveVersion::V2, 2); + ByteBuffer buffer = writeSaveContainer(original); + + SaveMigrator migrator; + migrator.skyLightForNewPlanes = 7; + migrator.metadataDefaults.inhabitedTicks = 1234; + migrator.metadataDefaults.lastSavedTick = 9999; + migrator.metadataDefaults.generationStage = 1; + + MigrationResult result = migrator.migrateToCurrent(buffer); + CHECK(result.ok); + SaveContainer lifted; + CHECK(readSaveContainer(result.output.data(), result.output.size(), lifted)); + for (const SaveChunkRecord& record : lifted.chunks) { + CHECK(record.light == defaultChunkLight(record.blocks, 7)); + CHECK_EQ(record.meta.inhabitedTicks, static_cast(1234)); + CHECK_EQ(record.meta.lastSavedTick, static_cast(9999)); + CHECK_EQ(record.meta.generationStage, static_cast(1)); + } +} + +VF_TEST(migratorCanCompressTheFinalHopAndStillReadItBack) { + SaveContainer original = buildContainer(SaveVersion::V1, 3); + ByteBuffer legacy = writeSaveContainer(original); + + SaveMigrator plain; + MigrationResult loose = plain.migrateToCurrent(legacy); + CHECK(loose.ok); + + SaveMigrator packing; + packing.compressMigratedPayloads = true; + MigrationResult tight = packing.migrateToCurrent(legacy); + CHECK(tight.ok); + CHECK(tight.output.size() < loose.output.size()); + + SaveContainer reloaded; + CHECK(readSaveContainer(tight.output.data(), tight.output.size(), reloaded)); + CHECK(reloaded.compressPayloads); + for (const SaveChunkRecord& before : original.chunks) { + const SaveChunkRecord* after = reloaded.find(before.coord); + CHECK(after != nullptr); + if (after != nullptr) CHECK(sameBlocks(before.blocks, after->blocks)); + } + + SaveScanner scanner; + CHECK(scanner.scan(tight.output).clean()); +} + +VF_TEST(migrationRefusesAHeaderItCannotIdentify) { + SaveContainer original = buildContainer(SaveVersion::V1, 2); + ByteBuffer buffer = writeSaveContainer(original); + saveWriteU32At(buffer.data(), buffer.size(), 4, 7u); + + SaveMigrator migrator; + MigrationResult result = migrator.migrateToCurrent(buffer); + CHECK(!result.ok); + CHECK(result.output.empty()); + CHECK(result.failure.find("header") != std::string::npos); + + ByteBuffer stump(20, 0); + MigrationResult tiny = migrator.migrateToCurrent(stump); + CHECK(!tiny.ok); +} + +VF_TEST(peekVersionReadsTheHeaderWithoutTheBody) { + SaveContainer original = buildContainer(SaveVersion::V3, 2); + ByteBuffer buffer = writeSaveContainer(original); + ByteBuffer headerOnly(buffer.begin(), buffer.begin() + saveHeaderBytes); +