test: cover readU32 and writeU32
This commit is contained in:
1 parent
b20f464b72
commit
0f6bb179c7
1 file changed
+161
@@ -0,0 +1,161 @@
|
|||||||
|
#include "testHarness.h"
|
||||||
|
#include "vf/Codec.h"
|
||||||
|
#include "vf/EditHistory.h"
|
||||||
|
#include "vf/Schematic.h"
|
||||||
|
#include "vf/Selection.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
using namespace vf;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// Byte offsets into an encoded schematic. The frame is magic, version, body
|
||||||
|
// length, body, checksum - all big-endian, all fixed width, which is what lets
|
||||||
|
// these tests damage one field at a time.
|
||||||
|
constexpr std::size_t bodyOffset = 12;
|
||||||
|
constexpr std::size_t paletteOffsetInBody = 26;
|
||||||
|
|
||||||
|
uint32_t readU32(const ByteBuffer& bytes, std::size_t at) {
|
||||||
|
return (static_cast<uint32_t>(bytes[at]) << 24) | (static_cast<uint32_t>(bytes[at + 1]) << 16) |
|
||||||
|
(static_cast<uint32_t>(bytes[at + 2]) << 8) | static_cast<uint32_t>(bytes[at + 3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void writeU32(ByteBuffer& bytes, std::size_t at, uint32_t value) {
|
||||||
|
bytes[at] = static_cast<uint8_t>(value >> 24);
|
||||||
|
bytes[at + 1] = static_cast<uint8_t>(value >> 16);
|
||||||
|
bytes[at + 2] = static_cast<uint8_t>(value >> 8);
|
||||||
|
bytes[at + 3] = static_cast<uint8_t>(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void refreshChecksum(ByteBuffer& bytes) {
|
||||||
|
uint32_t bodyLength = readU32(bytes, 8);
|
||||||
|
writeU32(bytes, bodyOffset + bodyLength, fnv1a32(bytes.data() + bodyOffset, bodyLength));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rewrites one body byte and repairs the checksum, so the decoder has to reject
|
||||||
|
// the buffer on its own merits rather than on a broken hash.
|
||||||
|
void patchBody(ByteBuffer& bytes, std::size_t offsetInBody, uint8_t value) {
|
||||||
|
bytes[bodyOffset + offsetInBody] = value;
|
||||||
|
refreshChecksum(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
World terrainWorld(int spanX, int spanZ) {
|
||||||
|
World world;
|
||||||
|
for (int z = 0; z < spanZ; ++z)
|
||||||
|
for (int x = 0; x < spanX; ++x) {
|
||||||
|
int height = 2 + ((x * 3 + z * 5) % 3);
|
||||||
|
for (int y = 0; y <= height; ++y) {
|
||||||
|
Block block = Block::Stone;
|
||||||
|
if (y == height) block = Block::Grass;
|
||||||
|
else if (y > height - 2) block = Block::Dirt;
|
||||||
|
world.setBlock(x, y, z, block);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return world;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sameRegion(const World& left, const World& right, const Vec3i& lo, const Vec3i& hi) {
|
||||||
|
for (int y = lo.y; y <= hi.y; ++y)
|
||||||
|
for (int z = lo.z; z <= hi.z; ++z)
|
||||||
|
for (int x = lo.x; x <= hi.x; ++x)
|
||||||
|
if (left.blockAt(x, y, z) != right.blockAt(x, y, z)) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Schematic decodedCopy(const ByteBuffer& bytes, SchematicError& error) {
|
||||||
|
Schematic loaded;
|
||||||
|
error = Schematic::decode(bytes.data(), bytes.size(), loaded);
|
||||||
|
return loaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
VF_TEST(aSchematicRoundTripRebuildsTheExactSameBlocks) {
|
||||||
|
World source = terrainWorld(9, 9);
|
||||||
|
Selection region = Selection::box({0, 0, 0}, {8, 5, 8});
|
||||||
|
Schematic saved = Schematic::capture(source, region, "ridge", "foksiu", 900u);
|
||||||
|
|
||||||
|
CHECK(saved.header().dims == Vec3i(9, 6, 9));
|
||||||
|
CHECK_EQ(saved.buffer().filledCount(), 9u * 6u * 9u);
|
||||||
|
|
||||||
|
ByteBuffer bytes = saved.encode();
|
||||||
|
SchematicError error = SchematicError::BadMagic;
|
||||||
|
Schematic loaded = decodedCopy(bytes, error);
|
||||||
|
CHECK(error == SchematicError::None);
|
||||||
|
CHECK(loaded.buffer().sameContent(saved.buffer()));
|
||||||
|
|
||||||
|
World rebuilt;
|
||||||
|
std::size_t written = loaded.pasteInto(rebuilt, {0, 0, 0}, ClipboardBuffer::PasteMode::Overwrite);
|
||||||
|
CHECK(written > 0u);
|
||||||
|
CHECK(sameRegion(source, rebuilt, {0, 0, 0}, {8, 5, 8}));
|
||||||
|
}
|
||||||
|
|
||||||
|
VF_TEST(aSchematicCarriesItsMetadataThroughTheRoundTrip) {
|
||||||
|
World source = terrainWorld(4, 4);
|
||||||
|
Schematic saved = Schematic::capture(source, Selection::box({0, 0, 0}, {3, 3, 3}), "watchtower",
|
||||||
|
"vulpes", 1234567890123ull);
|
||||||
|
saved.setAnchor({1, 2, 3});
|
||||||
|
|
||||||
|
ByteBuffer bytes = saved.encode();
|
||||||
|
SchematicError error = SchematicError::BadMagic;
|
||||||
|
Schematic loaded = decodedCopy(bytes, error);
|
||||||
|
CHECK(error == SchematicError::None);
|
||||||
|
CHECK(loaded.header().name == std::string("watchtower"));
|
||||||
|
CHECK(loaded.header().author == std::string("vulpes"));
|
||||||
|
CHECK_EQ(loaded.header().tick, 1234567890123ull);
|
||||||
|
CHECK(loaded.header().anchor == Vec3i(1, 2, 3));
|
||||||
|
CHECK(loaded.header().version == SchematicVersion::V2);
|
||||||
|
CHECK(!loaded.migratedFromV1());
|
||||||
|
}
|
||||||
|
|
||||||
|
VF_TEST(theIndexWidthShrinksWithTheNumberOfDistinctBlocks) {
|
||||||
|
CHECK_EQ(Schematic::bitsForPalette(1u), 1u);
|
||||||
|
CHECK_EQ(Schematic::bitsForPalette(2u), 1u);
|
||||||
|
CHECK_EQ(Schematic::bitsForPalette(3u), 2u);
|
||||||
|
CHECK_EQ(Schematic::bitsForPalette(4u), 2u);
|
||||||
|
CHECK_EQ(Schematic::bitsForPalette(5u), 4u);
|
||||||
|
CHECK_EQ(Schematic::bitsForPalette(16u), 4u);
|
||||||
|
CHECK_EQ(Schematic::bitsForPalette(17u), 8u);
|
||||||
|
|
||||||
|
ClipboardBuffer twoTone({8, 8, 8}, {0, 0, 0});
|
||||||
|
for (int y = 0; y < 8; ++y)
|
||||||
|
for (int z = 0; z < 8; ++z)
|
||||||
|
for (int x = 0; x < 8; ++x) twoTone.put(x, y, z, Block::Stone);
|
||||||
|
Schematic plain = Schematic::fromBuffer(twoTone, "plain", "foksiu", 1u);
|
||||||
|
CHECK_EQ(plain.header().paletteSize, 2u);
|
||||||
|
CHECK_EQ(plain.header().indexBits, 1u);
|
||||||
|
|
||||||
|
ClipboardBuffer manyTone = twoTone;
|
||||||
|
for (int i = 0; i < 20; ++i) manyTone.put(i % 8, i / 8, 0, static_cast<Block>(20 + i));
|
||||||
|
Schematic mixed = Schematic::fromBuffer(manyTone, "mixed", "foksiu", 1u);
|
||||||
|
CHECK_EQ(mixed.header().paletteSize, 22u);
|
||||||
|
CHECK_EQ(mixed.header().indexBits, 8u);
|
||||||
|
|
||||||
|
SchematicError error = SchematicError::BadMagic;
|
||||||
|
Schematic reloaded = decodedCopy(mixed.encode(), error);
|
||||||
|
CHECK(error == SchematicError::None);
|
||||||
|
CHECK(reloaded.buffer().sameContent(manyTone));
|
||||||
|
CHECK_EQ(reloaded.header().indexBits, 8u);
|
||||||
|
}
|
||||||
|
|
||||||
|
VF_TEST(rotatingFourTimesByNinetyDegreesReturnsTheOriginal) {
|
||||||
|
World source = terrainWorld(7, 5);
|
||||||
|
Schematic original = Schematic::capture(source, Selection::box({0, 0, 0}, {6, 3, 4}), "spin",
|
||||||
|
"foksiu", 10u);
|
||||||
|
original.setAnchor({2, 1, 3});
|
||||||
|
|
||||||
|
Schematic turning = original;
|
||||||
|
for (int step = 0; step < 4; ++step) turning.rotate(Rotation::Cw90);
|
||||||
|
CHECK(turning.buffer().sameContent(original.buffer()));
|
||||||
|
CHECK(turning.header().dims == original.header().dims);
|
||||||
|
CHECK(turning.header().anchor == original.header().anchor);
|
||||||
|
|
||||||
|
Schematic half = original;
|
||||||
|
half.rotate(Rotation::Cw180);
|
||||||
|
Schematic twoQuarters = original;
|
||||||
|
twoQuarters.rotate(Rotation::Cw90);
|
||||||
|
twoQuarters.rotate(Rotation::Cw90);
|
||||||
|
CHECK(half.buffer().sameContent(twoQuarters.buffer()));
|
||||||
|
|
||||||
Reference in new issue
Block a user