mirror of https://github.com/SeanOMik/libki.git
test: SerializerBinary tests
This commit is contained in:
parent
4872d1a2a4
commit
19324c06ba
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,463 @@
|
||||||
|
#define CATCH_CONFIG_MAIN
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
#include <catch.hpp>
|
||||||
|
#include <ki/pclass/TypeSystem.h>
|
||||||
|
#include <ki/pclass/PropertyClass.h>
|
||||||
|
#include <ki/pclass/StaticProperty.h>
|
||||||
|
#include <ki/pclass/VectorProperty.h>
|
||||||
|
#include <ki/serialization/SerializerBinary.h>
|
||||||
|
|
||||||
|
using namespace ki;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Structure used to test custom primitive serialization.
|
||||||
|
*/
|
||||||
|
struct Vector3D
|
||||||
|
{
|
||||||
|
Vector3D(
|
||||||
|
const float x = 0.0f,
|
||||||
|
const float y = 0.0f,
|
||||||
|
const float z = 0.0f)
|
||||||
|
{
|
||||||
|
this->m_x = x;
|
||||||
|
this->m_y = y;
|
||||||
|
this->m_z = z;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3D &operator=(const Vector3D &that)
|
||||||
|
{
|
||||||
|
m_x = that.m_x;
|
||||||
|
m_y = that.m_y;
|
||||||
|
m_z = that.m_z;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const Vector3D &that) const
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
m_x == that.m_x,
|
||||||
|
m_y == that.m_y,
|
||||||
|
m_z == that.m_z
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void write_to(BitStream &stream) const
|
||||||
|
{
|
||||||
|
pclass::PrimitiveTypeWriter<float>::write_to(stream, m_x);
|
||||||
|
pclass::PrimitiveTypeWriter<float>::write_to(stream, m_y);
|
||||||
|
pclass::PrimitiveTypeWriter<float>::write_to(stream, m_z);
|
||||||
|
}
|
||||||
|
|
||||||
|
void read_from(BitStream &stream)
|
||||||
|
{
|
||||||
|
pclass::PrimitiveTypeReader<float>::read_from(stream, m_x);
|
||||||
|
pclass::PrimitiveTypeReader<float>::read_from(stream, m_y);
|
||||||
|
pclass::PrimitiveTypeReader<float>::read_from(stream, m_z);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
float m_x;
|
||||||
|
float m_y;
|
||||||
|
float m_z;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type Writer for custom primitive type (Vector3D).
|
||||||
|
*/
|
||||||
|
template <>
|
||||||
|
struct pclass::PrimitiveTypeWriter<Vector3D>
|
||||||
|
{
|
||||||
|
static void write_to(BitStream &stream, const Vector3D &value)
|
||||||
|
{
|
||||||
|
value.write_to(stream);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type Reader for custom primitive type (Vector3D).
|
||||||
|
*/
|
||||||
|
template <>
|
||||||
|
struct pclass::PrimitiveTypeReader<Vector3D>
|
||||||
|
{
|
||||||
|
static void read_from(BitStream &stream, Vector3D &value)
|
||||||
|
{
|
||||||
|
value.read_from(stream);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enumeration used to test enum serialization.
|
||||||
|
*/
|
||||||
|
enum class NestedObjectKind
|
||||||
|
{
|
||||||
|
NONE = 0,
|
||||||
|
OBJECT = 1,
|
||||||
|
OBJECT_A = 2,
|
||||||
|
OBJECT_B = 3
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class used to test object nesting and inheritance.
|
||||||
|
* get_kind() will always return OBJECT.
|
||||||
|
*/
|
||||||
|
PCLASS(NestedTestObject)
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
PCLASS_CONSTRUCTOR(NestedTestObject)
|
||||||
|
INIT_PROPERTY(m_kind, "enum NestedObjectKind")
|
||||||
|
{
|
||||||
|
m_kind = NestedObjectKind::OBJECT;
|
||||||
|
}
|
||||||
|
|
||||||
|
PCLASS_COPY_CONSTRUCTOR(NestedTestObject)
|
||||||
|
INIT_PROPERTY_COPY(m_kind)
|
||||||
|
{}
|
||||||
|
|
||||||
|
NestedObjectKind get_kind() const
|
||||||
|
{
|
||||||
|
return m_kind;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/**
|
||||||
|
* Test statically defined enums.
|
||||||
|
*/
|
||||||
|
pclass::StaticProperty<NestedObjectKind> m_kind;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One of two nested objects used to test inheritance.
|
||||||
|
* get_kind() will always return OBJECT_A.
|
||||||
|
*/
|
||||||
|
DERIVED_PCLASS(NestedTestObjectA, NestedTestObject)
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
DERIVED_PCLASS_CONSTRUCTOR(NestedTestObjectA, NestedTestObject)
|
||||||
|
{
|
||||||
|
m_kind = NestedObjectKind::OBJECT_A;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One of two nested objects used to test inheritance.
|
||||||
|
* get_kind() will always return OBJECT_B.
|
||||||
|
*/
|
||||||
|
DERIVED_PCLASS(NestedTestObjectB, NestedTestObject)
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
DERIVED_PCLASS_CONSTRUCTOR(NestedTestObjectB, NestedTestObject)
|
||||||
|
{
|
||||||
|
m_kind = NestedObjectKind::OBJECT_B;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class used to test object serialization.
|
||||||
|
* Contains properties of all primitive types, as well as a
|
||||||
|
* custom primitive type, and a user-defined class type.
|
||||||
|
*/
|
||||||
|
PCLASS(TestObject)
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
PCLASS_CONSTRUCTOR(TestObject)
|
||||||
|
INIT_PROPERTY(int4, "bi4")
|
||||||
|
INIT_PROPERTY(uint4, "bui4")
|
||||||
|
INIT_PROPERTY(int8, "char")
|
||||||
|
INIT_PROPERTY(int16, "short")
|
||||||
|
INIT_PROPERTY(int24, "s24")
|
||||||
|
INIT_PROPERTY(int32, "int")
|
||||||
|
INIT_PROPERTY(int64, "long")
|
||||||
|
INIT_PROPERTY(uint8, "unsigned char")
|
||||||
|
INIT_PROPERTY(uint16, "unsigned short")
|
||||||
|
INIT_PROPERTY(uint24, "u24")
|
||||||
|
INIT_PROPERTY(uint32, "unsigned int")
|
||||||
|
INIT_PROPERTY(uint64, "unsigned long")
|
||||||
|
INIT_PROPERTY(string, "std::string")
|
||||||
|
INIT_PROPERTY(wstring, "std::wstring")
|
||||||
|
INIT_PROPERTY(float32, "float")
|
||||||
|
INIT_PROPERTY(float64, "double")
|
||||||
|
INIT_PROPERTY(vector3d, "struct Vector3D")
|
||||||
|
INIT_PROPERTY(not_null_object, "class NestedTestObject")
|
||||||
|
INIT_PROPERTY(null_object, "class NestedTestObject")
|
||||||
|
INIT_PROPERTY(objects, "class NestedTestObject")
|
||||||
|
{}
|
||||||
|
|
||||||
|
// Test signed and unsigned integers with a bit length less than 8
|
||||||
|
pclass::StaticProperty<bi<4>> int4;
|
||||||
|
pclass::StaticProperty<bui<4>> uint4;
|
||||||
|
|
||||||
|
// Test signed and unsigned integers with a bit length greater than 8
|
||||||
|
pclass::StaticProperty<int8_t> int8;
|
||||||
|
pclass::StaticProperty<int16_t> int16;
|
||||||
|
pclass::StaticProperty<bi<24>> int24;
|
||||||
|
pclass::StaticProperty<int32_t> int32;
|
||||||
|
pclass::StaticProperty<int64_t> int64;
|
||||||
|
pclass::StaticProperty<uint8_t> uint8;
|
||||||
|
pclass::StaticProperty<uint16_t> uint16;
|
||||||
|
pclass::StaticProperty<bui<24>> uint24;
|
||||||
|
pclass::StaticProperty<uint32_t> uint32;
|
||||||
|
pclass::StaticProperty<uint64_t> uint64;
|
||||||
|
|
||||||
|
// Test strings
|
||||||
|
pclass::StaticProperty<std::string> string;
|
||||||
|
pclass::StaticProperty<std::wstring> wstring;
|
||||||
|
|
||||||
|
// Test single precision and double precision floating point integers
|
||||||
|
pclass::StaticProperty<float> float32;
|
||||||
|
pclass::StaticProperty<double> float64;
|
||||||
|
|
||||||
|
// Test writing custom defined primitives
|
||||||
|
pclass::StaticProperty<Vector3D> vector3d;
|
||||||
|
|
||||||
|
// Test writing a single instance of another object
|
||||||
|
pclass::StaticProperty<NestedTestObject *> not_null_object;
|
||||||
|
pclass::StaticProperty<NestedTestObject *> null_object;
|
||||||
|
|
||||||
|
// Test writing multiple instances of another object
|
||||||
|
pclass::VectorProperty<NestedTestObject *> objects;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Setup a global TypeSystem instance
|
||||||
|
auto *g_hash_calculator = new pclass::WizardHashCalculator();
|
||||||
|
pclass::TypeSystem *g_type_system = new pclass::TypeSystem(g_hash_calculator);
|
||||||
|
bool g_types_defined = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Populate the given TypeSystem with Types necessary to run the tests.
|
||||||
|
*/
|
||||||
|
void define_types()
|
||||||
|
{
|
||||||
|
if (g_types_defined)
|
||||||
|
return;
|
||||||
|
|
||||||
|
g_type_system->define_primitive<Vector3D>("struct Vector3D");
|
||||||
|
g_type_system->define_enum<NestedObjectKind>("enum NestedObjectKind");
|
||||||
|
auto &nested_test_object = g_type_system->define_class<NestedTestObject>("class NestedTestObject");
|
||||||
|
g_type_system->define_class<NestedTestObjectA>("class NestedTestObjectA", nested_test_object);
|
||||||
|
g_type_system->define_class<NestedTestObjectB>("class NestedTestObjectB", nested_test_object);
|
||||||
|
g_type_system->define_class<TestObject>("class TestObject");
|
||||||
|
g_types_defined = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Macros for configuring/validating TestObject instances.
|
||||||
|
*/
|
||||||
|
#define EXPECTED_int4 -6
|
||||||
|
#define EXPECTED_uint4 5
|
||||||
|
#define EXPECTED_int8 0x01
|
||||||
|
#define EXPECTED_int16 0x0203
|
||||||
|
#define EXPECTED_int24 0x040506
|
||||||
|
#define EXPECTED_int32 0x0708090A
|
||||||
|
#define EXPECTED_int64 0x0B0C0D0E0F101112
|
||||||
|
#define EXPECTED_uint8 0x01
|
||||||
|
#define EXPECTED_uint16 0x0203
|
||||||
|
#define EXPECTED_uint24 0x040506
|
||||||
|
#define EXPECTED_uint32 0x0708090A
|
||||||
|
#define EXPECTED_uint64 0x0B0C0D0E0F101112
|
||||||
|
#define EXPECTED_string "This is a test value"
|
||||||
|
#define EXPECTED_wstring L"This is a test value"
|
||||||
|
#define EXPECTED_float32 3.1415927410125732421875f
|
||||||
|
#define EXPECTED_float64 3.141592653589793115997963468544185161590576171875
|
||||||
|
#define EXPECTED_vector3d Vector3D(24.0f, 61.0f, 3.62f)
|
||||||
|
#define SET_EXPECTED(object, identifier) object.identifier = EXPECTED_##identifier
|
||||||
|
#define IS_EXPECTED(object, identifier) object.identifier.get() == EXPECTED_##identifier
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure a TestObject instance with the expected values.
|
||||||
|
*/
|
||||||
|
void configure_test_object(TestObject &object)
|
||||||
|
{
|
||||||
|
// Set primitive values
|
||||||
|
SET_EXPECTED(object, int4);
|
||||||
|
SET_EXPECTED(object, uint4);
|
||||||
|
SET_EXPECTED(object, int8);
|
||||||
|
SET_EXPECTED(object, int16);
|
||||||
|
SET_EXPECTED(object, int24);
|
||||||
|
SET_EXPECTED(object, int32);
|
||||||
|
SET_EXPECTED(object, int64);
|
||||||
|
SET_EXPECTED(object, uint8);
|
||||||
|
SET_EXPECTED(object, uint16);
|
||||||
|
SET_EXPECTED(object, uint24);
|
||||||
|
SET_EXPECTED(object, uint32);
|
||||||
|
SET_EXPECTED(object, uint64);
|
||||||
|
SET_EXPECTED(object, string);
|
||||||
|
SET_EXPECTED(object, wstring);
|
||||||
|
SET_EXPECTED(object, float32);
|
||||||
|
SET_EXPECTED(object, float64);
|
||||||
|
SET_EXPECTED(object, vector3d);
|
||||||
|
|
||||||
|
// Configure nested objects
|
||||||
|
object.not_null_object = g_type_system->instantiate<NestedTestObject>("class NestedTestObject");
|
||||||
|
object.null_object = nullptr;
|
||||||
|
object.objects.push_back(
|
||||||
|
g_type_system->instantiate<NestedTestObjectA>("class NestedTestObjectA")
|
||||||
|
);
|
||||||
|
object.objects.push_back(
|
||||||
|
g_type_system->instantiate<NestedTestObjectB>("class NestedTestObjectB")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate that a TestObject instance is configured with the expected values.
|
||||||
|
*/
|
||||||
|
void validate_test_object(TestObject &object)
|
||||||
|
{
|
||||||
|
// Validate primitive values
|
||||||
|
REQUIRE(IS_EXPECTED(object, int4));
|
||||||
|
REQUIRE(IS_EXPECTED(object, uint4));
|
||||||
|
REQUIRE(IS_EXPECTED(object, int8));
|
||||||
|
REQUIRE(IS_EXPECTED(object, int16));
|
||||||
|
REQUIRE(IS_EXPECTED(object, int24));
|
||||||
|
REQUIRE(IS_EXPECTED(object, int32));
|
||||||
|
REQUIRE(IS_EXPECTED(object, int64));
|
||||||
|
REQUIRE(IS_EXPECTED(object, uint8));
|
||||||
|
REQUIRE(IS_EXPECTED(object, uint16));
|
||||||
|
REQUIRE(IS_EXPECTED(object, uint24));
|
||||||
|
REQUIRE(IS_EXPECTED(object, uint32));
|
||||||
|
REQUIRE(IS_EXPECTED(object, uint64));
|
||||||
|
REQUIRE(IS_EXPECTED(object, string));
|
||||||
|
REQUIRE(IS_EXPECTED(object, wstring));
|
||||||
|
REQUIRE(IS_EXPECTED(object, float32));
|
||||||
|
REQUIRE(IS_EXPECTED(object, float64));
|
||||||
|
REQUIRE(IS_EXPECTED(object, vector3d));
|
||||||
|
|
||||||
|
// Validate nested objects
|
||||||
|
REQUIRE(object.not_null_object.get() != nullptr);
|
||||||
|
REQUIRE(object.not_null_object.get()->get_kind() == NestedObjectKind::OBJECT);
|
||||||
|
REQUIRE(object.null_object.get() == nullptr);
|
||||||
|
REQUIRE(object.objects.size() == 2);
|
||||||
|
REQUIRE(object.objects[0]->get_kind() == NestedObjectKind::OBJECT_A);
|
||||||
|
REQUIRE(object.objects[1]->get_kind() == NestedObjectKind::OBJECT_B);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Conduct save/load tests with a SerializerBinary instance.
|
||||||
|
*/
|
||||||
|
void test_serializer(
|
||||||
|
TestObject *&test_object,
|
||||||
|
serialization::SerializerBinary &serializer,
|
||||||
|
const std::string &file_suffix)
|
||||||
|
{
|
||||||
|
BitBuffer buffer;
|
||||||
|
BitStream stream(buffer);
|
||||||
|
const auto start_pos = stream.tell();
|
||||||
|
|
||||||
|
// Open the sample data
|
||||||
|
std::ifstream sample(
|
||||||
|
"samples/serialization_binary" + file_suffix + ".bin",
|
||||||
|
std::ios::binary
|
||||||
|
);
|
||||||
|
REQUIRE(sample.is_open());
|
||||||
|
|
||||||
|
// Load the sample data
|
||||||
|
const auto begin = sample.tellg();
|
||||||
|
sample.seekg(0, std::ios::end);
|
||||||
|
const auto end = sample.tellg();
|
||||||
|
const size_t sample_size = end - begin;
|
||||||
|
sample.seekg(std::ios::beg);
|
||||||
|
auto *sample_data = new char[sample_size];
|
||||||
|
sample.read(sample_data, sample_size);
|
||||||
|
sample.close();
|
||||||
|
|
||||||
|
SECTION("Saving objects")
|
||||||
|
{
|
||||||
|
// Create a test object, configure it, and write it to our stream
|
||||||
|
test_object = g_type_system->instantiate<TestObject>("class TestObject");
|
||||||
|
configure_test_object(*test_object);
|
||||||
|
serializer.save(test_object, stream);
|
||||||
|
const auto end_pos = stream.tell();
|
||||||
|
|
||||||
|
// Delete the test object here so that it is not
|
||||||
|
// unnecessarily validated by the caller
|
||||||
|
delete test_object;
|
||||||
|
test_object = nullptr;
|
||||||
|
|
||||||
|
// Validate the contents of the stream
|
||||||
|
const auto stream_size = (end_pos - start_pos).as_bytes();
|
||||||
|
|
||||||
|
REQUIRE(stream_size == sample_size);
|
||||||
|
auto *stream_data = new uint8_t[stream_size];
|
||||||
|
stream.seek(start_pos);
|
||||||
|
stream.read_copy(stream_data, stream_size * 8);
|
||||||
|
REQUIRE(memcmp(stream_data, sample_data, stream_size) == 0);
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
delete[] stream_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Loading objects")
|
||||||
|
{
|
||||||
|
// Write the sample data to the bit stream
|
||||||
|
stream.write_copy(reinterpret_cast<uint8_t *>(sample_data), sample_size * 8);
|
||||||
|
stream.seek(start_pos);
|
||||||
|
|
||||||
|
// Load an object from the bit stream's contents
|
||||||
|
pclass::PropertyClass *object = nullptr;
|
||||||
|
serializer.load(object, stream, sample_size);
|
||||||
|
|
||||||
|
// Set test_object so that it is validated by the caller
|
||||||
|
REQUIRE(object != nullptr);
|
||||||
|
test_object = dynamic_cast<TestObject *>(object);
|
||||||
|
REQUIRE(test_object != nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup the sample
|
||||||
|
delete[] sample_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Serialization tests", "[serialization]")
|
||||||
|
{
|
||||||
|
TestObject *test_object = nullptr;
|
||||||
|
define_types();
|
||||||
|
|
||||||
|
SECTION("SerializerBinary")
|
||||||
|
{
|
||||||
|
SECTION("Regular format without compression")
|
||||||
|
{
|
||||||
|
serialization::SerializerBinary serializer(
|
||||||
|
*g_type_system, false,
|
||||||
|
serialization::SerializerBinary::flags::NONE
|
||||||
|
);
|
||||||
|
test_serializer(test_object, serializer, "_regular");
|
||||||
|
}
|
||||||
|
SECTION("File format without compression")
|
||||||
|
{
|
||||||
|
serialization::SerializerBinary serializer(
|
||||||
|
*g_type_system, true,
|
||||||
|
serialization::SerializerBinary::flags::WRITE_SERIALIZER_FLAGS
|
||||||
|
);
|
||||||
|
test_serializer(test_object, serializer, "_file");
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
TODO: Test compression
|
||||||
|
SECTION("Regular format with compression")
|
||||||
|
{
|
||||||
|
serialization::SerializerBinary serializer(
|
||||||
|
*g_type_system, false,
|
||||||
|
serialization::SerializerBinary::flags::COMPRESSED
|
||||||
|
);
|
||||||
|
test_serializer(test_object, serializer, "_regular_compressed");
|
||||||
|
}
|
||||||
|
SECTION("File format with compression")
|
||||||
|
{
|
||||||
|
serialization::SerializerBinary serializer(
|
||||||
|
*g_type_system, true,
|
||||||
|
serialization::SerializerBinary::flags::WRITE_SERIALIZER_FLAGS |
|
||||||
|
serialization::SerializerBinary::flags::COMPRESSED
|
||||||
|
);
|
||||||
|
test_serializer(test_object, serializer, "_file_compressed");
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
// If one of the above sections leaves test_object set, then
|
||||||
|
// we're expected to validate it
|
||||||
|
if (test_object)
|
||||||
|
{
|
||||||
|
validate_test_object(*test_object);
|
||||||
|
delete test_object;
|
||||||
|
test_object = nullptr;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue