diff --git a/tests/testSchematic.cpp b/tests/testSchematic.cpp index 7e0568f..408ca8c 100644 --- a/tests/testSchematic.cpp +++ b/tests/testSchematic.cpp @@ -159,3 +159,159 @@ VF_TEST(rotatingFourTimesByNinetyDegreesReturnsTheOriginal) { twoQuarters.rotate(Rotation::Cw90); CHECK(half.buffer().sameContent(twoQuarters.buffer())); + Schematic backAgain = original; + backAgain.rotate(Rotation::Cw90); + backAgain.rotate(Rotation::Cw270); + CHECK(backAgain.buffer().sameContent(original.buffer())); +} + +VF_TEST(aQuarterTurnSwapsTheHorizontalDimensionsAndMovesTheMarker) { + ClipboardBuffer buffer({4, 1, 2}, {0, 0, 0}); + for (int z = 0; z < 2; ++z) + for (int x = 0; x < 4; ++x) buffer.put(x, 0, z, Block::Stone); + buffer.put(0, 0, 0, Block::Glowstone); + + ClipboardBuffer turned = buffer.rotatedY(Rotation::Cw90); + CHECK(turned.dims() == Vec3i(2, 1, 4)); + CHECK_EQ(turned.filledCount(), 8u); + // (0,0,0) maps to (dimZ - 1, 0, 0) under a clockwise quarter turn. + CHECK(turned.at(1, 0, 0) == Block::Glowstone); + CHECK(turned.at(0, 0, 0) == Block::Stone); +} + +VF_TEST(mirroringTwiceOnTheSameAxisReturnsTheOriginal) { + ClipboardBuffer buffer({3, 2, 4}, {1, 0, 2}); + for (int y = 0; y < 2; ++y) + for (int z = 0; z < 4; ++z) + for (int x = 0; x < 3; ++x) buffer.put(x, y, z, Block::Planks); + buffer.put(0, 0, 0, Block::Chest); + + ClipboardBuffer flipped = buffer.mirrored(MirrorAxis::X); + CHECK(flipped.at(2, 0, 0) == Block::Chest); + CHECK(flipped.at(0, 0, 0) == Block::Planks); + CHECK(flipped.anchor() == Vec3i(1, 0, 2)); + + CHECK(flipped.mirrored(MirrorAxis::X).sameContent(buffer)); + CHECK(buffer.mirrored(MirrorAxis::Y).mirrored(MirrorAxis::Y).sameContent(buffer)); + CHECK(buffer.mirrored(MirrorAxis::Z).mirrored(MirrorAxis::Z).sameContent(buffer)); + + ClipboardBuffer alongZ = buffer.mirrored(MirrorAxis::Z); + CHECK(alongZ.at(0, 0, 3) == Block::Chest); + CHECK(alongZ.anchor() == Vec3i(1, 0, 1)); +} + +VF_TEST(trimmingDropsEmptyMarginsAndKeepsTheAnchorOnTheSameBlock) { + ClipboardBuffer padded({8, 8, 8}, {5, 5, 5}); + for (int y = 4; y <= 5; ++y) + for (int z = 4; z <= 6; ++z) + for (int x = 5; x <= 6; ++x) padded.put(x, y, z, Block::Bricks); + + ClipboardBuffer tight = padded.trimmed(); + CHECK(tight.dims() == Vec3i(2, 2, 3)); + CHECK_EQ(tight.filledCount(), 12u); + CHECK(tight.anchor() == Vec3i(0, 1, 1)); + CHECK(tight.at(0, 0, 0) == Block::Bricks); + + World empty; + Schematic schematic = Schematic::fromBuffer(padded, "pad", "foksiu", 3u); + schematic.trim(); + CHECK(schematic.header().dims == Vec3i(2, 2, 3)); + CHECK_EQ(schematic.pasteInto(empty, {0, 0, 0}, ClipboardBuffer::PasteMode::Overwrite), 12u); +} + +VF_TEST(versionOneDataIsMigratedToVersionTwoWhenLoaded) { + World source = terrainWorld(6, 6); + Schematic saved = Schematic::capture(source, Selection::box({0, 0, 0}, {5, 4, 5}), "legacy", + "foksiu", 777u); + + ByteBuffer oldBytes = saved.encode(SchematicVersion::V1); + ByteBuffer newBytes = saved.encode(SchematicVersion::V2); + CHECK(oldBytes.size() < newBytes.size()); + + SchematicError error = SchematicError::BadMagic; + Schematic loaded = decodedCopy(oldBytes, error); + CHECK(error == SchematicError::None); + CHECK(loaded.migratedFromV1()); + CHECK(loaded.header().version == SchematicVersion::V2); + CHECK(loaded.header().name.empty()); + CHECK(loaded.header().author.empty()); + CHECK_EQ(loaded.header().tick, 0u); + CHECK(loaded.buffer().sameContent(saved.buffer())); + + // Re-saving the migrated schematic yields a v2 buffer that carries metadata. + loaded.setName("legacy"); + loaded.setAuthor("foksiu"); + loaded.setTick(777u); + SchematicError second = SchematicError::BadMagic; + Schematic again = decodedCopy(loaded.encode(), second); + CHECK(second == SchematicError::None); + CHECK(!again.migratedFromV1()); + CHECK(again.header().name == std::string("legacy")); +} + +VF_TEST(theVoidMaskKeepsHolesInVersionTwoAndIsLostInVersionOne) { + World stoneBlock; + for (int y = 0; y < 9; ++y) + for (int z = 0; z < 9; ++z) + for (int x = 0; x < 9; ++x) stoneBlock.setBlock(x, y, z, Block::Stone); + + Selection ball = Selection::sphere({4, 4, 4}, 4); + Schematic saved = Schematic::capture(stoneBlock, ball, "ball", "foksiu", 5u); + CHECK(saved.header().hasVoidMask); + CHECK(saved.buffer().filledCount() < saved.buffer().volume()); + + SchematicError error = SchematicError::BadMagic; + Schematic loaded = decodedCopy(saved.encode(), error); + CHECK(error == SchematicError::None); + CHECK(loaded.header().hasVoidMask); + CHECK_EQ(loaded.buffer().filledCount(), saved.buffer().filledCount()); + CHECK(!loaded.buffer().filledAt(0, 0, 0)); + + SchematicError legacyError = SchematicError::BadMagic; + Schematic legacy = decodedCopy(saved.encode(SchematicVersion::V1), legacyError); + CHECK(legacyError == SchematicError::None); + CHECK(!legacy.header().hasVoidMask); + CHECK_EQ(legacy.buffer().filledCount(), legacy.buffer().volume()); + CHECK(legacy.buffer().at(0, 0, 0) == Block::Air); + + World target; + loaded.pasteInto(target, {0, 0, 0}, ClipboardBuffer::PasteMode::Overwrite); + CHECK(target.blockAt(4, 4, 4) == Block::Stone); + CHECK(target.blockAt(0, 0, 0) == Block::Air); + CHECK(target.blockAt(8, 8, 8) == Block::Air); +} + +VF_TEST(aBufferWithTheWrongMagicNumberIsRejected) { + World source = terrainWorld(3, 3); + ByteBuffer bytes = + Schematic::capture(source, Selection::box({0, 0, 0}, {2, 2, 2}), "m", "f", 1u).encode(); + bytes[0] = 0x00; + + Schematic loaded; + CHECK(Schematic::decode(bytes.data(), bytes.size(), loaded) == SchematicError::BadMagic); + + ByteBuffer garbage = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; + CHECK(Schematic::decode(garbage.data(), garbage.size(), loaded) == SchematicError::BadMagic); +} + +VF_TEST(anUnknownFormatVersionIsRejected) { + World source = terrainWorld(3, 3); + ByteBuffer bytes = + Schematic::capture(source, Selection::box({0, 0, 0}, {2, 2, 2}), "v", "f", 1u).encode(); + writeU32(bytes, 4, 7u); + + Schematic loaded; + CHECK(Schematic::decode(bytes.data(), bytes.size(), loaded) == + SchematicError::UnsupportedVersion); + + writeU32(bytes, 4, 0u); + CHECK(Schematic::decode(bytes.data(), bytes.size(), loaded) == + SchematicError::UnsupportedVersion); +} + +VF_TEST(aTruncatedBufferIsRejectedAtEveryCutPoint) { + World source = terrainWorld(5, 5); + ByteBuffer bytes = + Schematic::capture(source, Selection::box({0, 0, 0}, {4, 4, 4}), "cut", "f", 1u).encode(); + CHECK(bytes.size() > 40u); +