Files
bitsery/tests/serialization_ext_pointer.cpp
Mindaugas Vinkelis 105aa5f9e5 simplified usage by merging adapter writer/reader with input/output
adapter and ability to disable checks on deserialization
2019-07-02 08:03:34 +03:00

533 lines
19 KiB
C++

//MIT License
//
//Copyright (c) 2017 Mindaugas Vinkelis
//
//Permission is hereby granted, free of charge, to any person obtaining a copy
//of this software and associated documentation files (the "Software"), to deal
//in the Software without restriction, including without limitation the rights
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//copies of the Software, and to permit persons to whom the Software is
//furnished to do so, subject to the following conditions:
//
//The above copyright notice and this permission notice shall be included in all
//copies or substantial portions of the Software.
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
//SOFTWARE.
#include <bitsery/ext/pointer.h>
#include <gmock/gmock.h>
#include "serialization_test_utils.h"
using bitsery::ext::PointerOwner;
using bitsery::ext::PointerObserver;
using bitsery::ext::ReferencedByPointer;
using bitsery::ext::PointerLinkingContext;
using bitsery::ext::PointerType;
using testing::Eq;
using SerContext = BasicSerializationContext<PointerLinkingContext>;
class SerializeExtensionPointerSerialization : public testing::Test {
public:
//data used for serialization
int16_t d1{1597};
int16_t *pd1 = &d1;
MyEnumClass d2{MyEnumClass::E2};
MyEnumClass *pd2 = &d2;
MyStruct1 d3{184, 897};
MyStruct1 *pd3 = &d3;
//data used for deserialization
int16_t r1{-84};
int16_t *pr1 = &r1;
MyEnumClass r2{MyEnumClass::E4};
MyEnumClass *pr2 = &r2;
MyStruct1 r3{-4984, -14597};
MyStruct1 *pr3 = &r3;
//null pointers
int16_t *p1null = nullptr;
MyEnumClass *p2null = nullptr;
MyStruct1 *p3null = nullptr;
PointerLinkingContext plctx1{};
SerContext sctx1{};
typename SerContext::TSerializer& createSerializer() {
return sctx1.createSerializer(plctx1);
}
typename SerContext::TDeserializer& createDeserializer() {
return sctx1.createDeserializer(plctx1);
}
bool isPointerContextValid() {
return plctx1.isValid();
}
};
TEST(SerializeExtensionPointer, RequiresPointerLinkingContext) {
MyStruct1 *data = nullptr;
//linking context
PointerLinkingContext plctx1{};
SerContext sctx1;
sctx1.createSerializer(plctx1).ext(data, PointerOwner{});
sctx1.createDeserializer(plctx1).ext(data, PointerOwner{});
//linking context in tuple
using ContextInTuple = std::tuple<int, PointerLinkingContext, float, char>;
ContextInTuple plctx2(0, PointerLinkingContext{}, 0.0f, 'a');
BasicSerializationContext<ContextInTuple> sctx2;
sctx2.createSerializer(plctx2).ext(data, PointerObserver{});
sctx2.createDeserializer(plctx2).ext(data, PointerObserver{});
}
TEST(SerializeExtensionPointer, PointerLinkingContextAcceptsMultipleSharedOwnersAndReturnSameId) {
MyStruct1 data{};
//pretend that this is shared ptr
MyStruct1 *sharedPtr = &data;
//linking context
PointerLinkingContext plctx1{};
EXPECT_THAT(plctx1.getInfoByPtr(sharedPtr, bitsery::ext::PointerOwnershipType::SharedOwner).id, Eq(1));
EXPECT_THAT(plctx1.getInfoByPtr(sharedPtr, bitsery::ext::PointerOwnershipType::SharedObserver).id, Eq(1));
EXPECT_THAT(plctx1.getInfoByPtr(sharedPtr, bitsery::ext::PointerOwnershipType::SharedOwner).id, Eq(1));
}
TEST(SerializeExtensionPointer, WhenOnlySharedObserverThenPointerLinkingContextIsInvalid) {
MyStruct1 data1{};
MyStruct1 data2{};
//pretend that this is shared ptr
MyStruct1 *sharedPtr1 = &data1;
MyStruct1 *sharedPtr2 = &data2;
//linking context
PointerLinkingContext plctx1{};
EXPECT_THAT(plctx1.getInfoByPtr(sharedPtr1, bitsery::ext::PointerOwnershipType::SharedObserver).id, Eq(1));
EXPECT_THAT(plctx1.getInfoByPtr(sharedPtr2, bitsery::ext::PointerOwnershipType::SharedObserver).id, Eq(2));
EXPECT_THAT(plctx1.getInfoByPtr(sharedPtr1, bitsery::ext::PointerOwnershipType::SharedObserver).id, Eq(1));
EXPECT_FALSE(plctx1.isValid());
EXPECT_THAT(plctx1.getInfoByPtr(sharedPtr1, bitsery::ext::PointerOwnershipType::SharedOwner).id, Eq(1));
EXPECT_THAT(plctx1.getInfoByPtr(sharedPtr2, bitsery::ext::PointerOwnershipType::SharedOwner).id, Eq(2));
EXPECT_TRUE(plctx1.isValid());
}
TEST_F(SerializeExtensionPointerSerialization, WhenPointersAreNullThenIsValid) {
auto& ser = createSerializer();
ser.ext2b(p1null, PointerOwner{});
ser.ext2b(p1null, PointerObserver{});
ser.ext(p3null, PointerOwner{});
ser.ext(p3null, PointerObserver{});
createDeserializer();
EXPECT_THAT(sctx1.ser->adapter().writtenBytesCount(), Eq(4));
EXPECT_THAT(plctx1.isValid(), Eq(true));
}
#ifndef NDEBUG
TEST_F(SerializeExtensionPointerSerialization, WhenPointerOwnerIsNotUniqueThenAssert) {
auto& ser = createSerializer();
ser.ext2b(p1null, PointerOwner{});
ser.ext2b(pd1, PointerOwner{});
ser.ext4b(pd2, PointerOwner{});
ser.ext2b(p1null, PointerOwner{});
//dublicating pointer
EXPECT_DEATH(ser.ext2b(pd1, PointerOwner{}), "");
}
TEST_F(SerializeExtensionPointerSerialization, WhenRererencedByPointerIsSameAsPointerOwnerThenAssert1) {
auto& ser1 = createSerializer();
ser1.ext4b(pd2, PointerOwner{});
ser1.ext(d3, ReferencedByPointer{});
EXPECT_DEATH(ser1.ext(pd3, PointerOwner{}), "");
}
TEST_F(SerializeExtensionPointerSerialization, WhenRererencedByPointerIsSameAsPointerOwnerThenAssert2) {
auto& ser1 = createSerializer();
ser1.ext2b(pd1, PointerOwner{});
ser1.ext4b(d2, ReferencedByPointer{});
EXPECT_DEATH(ser1.ext2b(d1, ReferencedByPointer{}), "");
}
TEST_F(SerializeExtensionPointerSerialization, WhenNonNullPointerIsNullThenAssert) {
auto& ser1 = createSerializer();
EXPECT_DEATH(ser1.ext2b(p1null, PointerOwner{PointerType::NotNull}), "");
EXPECT_DEATH(ser1.ext2b(p1null, PointerObserver{PointerType::NotNull}), "");
}
#endif
TEST_F(SerializeExtensionPointerSerialization, WhenPointerObserverPointsToOwnerThenIsValid) {
auto& ser1 = createSerializer();
ser1.ext2b(pd1, PointerOwner{});
ser1.ext2b(p1null, PointerObserver{});
EXPECT_THAT(plctx1.isValid(), Eq(true));
ser1.ext4b(pd2, PointerObserver{});//points to d2, and d2 is not still marked as owner
EXPECT_THAT(plctx1.isValid(), Eq(false));
ser1.ext4b(pd2, PointerOwner{});//now d2 is owning pointer
ser1.ext4b(pd2, PointerObserver{});//points to d2, but this time d2 has owner
ser1.ext2b(p1null, PointerObserver{});
EXPECT_THAT(plctx1.isValid(), Eq(true));
}
TEST_F(SerializeExtensionPointerSerialization, ReferenceTypeCanAlsoBeReferencedByPointerObservers) {
auto& ser1 = createSerializer();
ser1.ext2b(p1null, PointerObserver{});
EXPECT_THAT(plctx1.isValid(), Eq(true));
ser1.ext4b(pd2, PointerObserver{});//points to d2, and d2 is not still marked as owner
EXPECT_THAT(plctx1.isValid(), Eq(false));
ser1.ext4b(d2, ReferencedByPointer{});//now d2 is marked by marked as owning pointer
ser1.ext4b(pd2, PointerObserver{});//points to d2, but this time d2 has owner
ser1.ext(p3null, PointerObserver{});
EXPECT_THAT(plctx1.isValid(), Eq(true));
}
TEST_F(SerializeExtensionPointerSerialization, WhenPointerIsNullThenPointerIdIsZero) {
auto& ser1 = createSerializer();
ser1.ext(p3null, PointerOwner{});
ser1.ext2b(p1null, PointerObserver{});
createDeserializer();
EXPECT_THAT(sctx1.ser->adapter().writtenBytesCount(), Eq(2));
size_t res;
bitsery::details::readSize(sctx1.des->adapter(), res, 0, std::false_type{});
EXPECT_THAT(res, Eq(0));
bitsery::details::readSize(sctx1.des->adapter(), res, 0, std::false_type{});
EXPECT_THAT(res, Eq(0));
}
TEST_F(SerializeExtensionPointerSerialization, PointerIdsStartsFromOne) {
auto& ser1 = createSerializer();
ser1.ext2b(pd1, PointerObserver{});
ser1.ext4b(pd2, PointerObserver{});
ser1.ext4b(pd2, PointerObserver{});
ser1.ext2b(p1null, PointerObserver{});
createDeserializer();
EXPECT_THAT(sctx1.ser->adapter().writtenBytesCount(), Eq(4));
size_t res;
bitsery::details::readSize(sctx1.des->adapter(), res, 0, std::false_type{});
EXPECT_THAT(res, Eq(1));
bitsery::details::readSize(sctx1.des->adapter(), res, 0, std::false_type{});
EXPECT_THAT(res, Eq(2));
bitsery::details::readSize(sctx1.des->adapter(), res, 0, std::false_type{});
EXPECT_THAT(res, Eq(2));
bitsery::details::readSize(sctx1.des->adapter(), res, 0, std::false_type{});
EXPECT_THAT(res, Eq(0));
}
TEST_F(SerializeExtensionPointerSerialization, PointerObserversDoesntSerializeObject) {
auto& ser1 = createSerializer();
ser1.ext2b(pd1, PointerObserver{});
ser1.ext4b(pd2, PointerObserver{});
ser1.ext4b(pd2, PointerObserver{});
createDeserializer();
EXPECT_THAT(sctx1.ser->adapter().writtenBytesCount(), Eq(3));
}
TEST_F(SerializeExtensionPointerSerialization, ReferencedByPointerSerializesIdAndObject) {
auto& ser1 = createSerializer();
ser1.ext2b(d1, ReferencedByPointer{});
ser1.ext4b(d2, ReferencedByPointer{});
ser1.ext4b(pd2, PointerObserver{});
auto& des = createDeserializer();
EXPECT_THAT(sctx1.ser->adapter().writtenBytesCount(), Eq(3 + 6));
size_t id{};
bitsery::details::readSize(sctx1.des->adapter(), id, 0, std::false_type{});
EXPECT_THAT(id, Eq(1));
des.value2b(r1);
EXPECT_THAT(r1, Eq(d1));
bitsery::details::readSize(sctx1.des->adapter(), id, 0, std::false_type{});
EXPECT_THAT(id, Eq(2));
des.value4b(r2);
EXPECT_THAT(r2, Eq(d2));
bitsery::details::readSize(sctx1.des->adapter(), id, 0, std::false_type{});
EXPECT_THAT(id, Eq(2));
}
TEST_F(SerializeExtensionPointerSerialization, PointerOwnerSerializesIdAndObject) {
auto& ser1 = createSerializer();
ser1.ext4b(pd2, PointerOwner{});
ser1.ext(pd3, PointerOwner{});
auto& des1 = createDeserializer();
//2x ids + int32_t + MyStruct1
EXPECT_THAT(sctx1.ser->adapter().writtenBytesCount(), Eq(2 + 4 + MyStruct1::SIZE));
size_t id;
bitsery::details::readSize(sctx1.des->adapter(), id, 0, std::false_type{});
des1.value4b(r2);
EXPECT_THAT(r2, Eq(*pd2));
bitsery::details::readSize(sctx1.des->adapter(), id, 0, std::false_type{});
des1.object(r3);
EXPECT_THAT(r3, Eq(*pd3));
}
class SerializeExtensionPointerDeserialization : public SerializeExtensionPointerSerialization {
public:
};
TEST_F(SerializeExtensionPointerDeserialization, ReferencedByPointer) {
auto& ser = createSerializer();
ser.ext2b(d1, ReferencedByPointer{});
ser.ext4b(d2, ReferencedByPointer{});
ser.ext(d3, ReferencedByPointer{});
auto& des = createDeserializer();
des.ext2b(r1, ReferencedByPointer{});
des.ext4b(r2, ReferencedByPointer{});
des.ext(r3, ReferencedByPointer{});
EXPECT_THAT(r1, Eq(d1));
EXPECT_THAT(r2, Eq(d2));
EXPECT_THAT(r3, Eq(d3));
}
TEST_F(SerializeExtensionPointerDeserialization, WhenReferencedByPointerReadsNullPointerThenInvalidPointerError) {
auto& ser = createSerializer();
bitsery::details::writeSize(sctx1.ser->adapter(), 0u);
ser.ext2b(d1, ReferencedByPointer{});
auto& des = createDeserializer();
des.ext2b(r1, ReferencedByPointer{});
EXPECT_THAT(sctx1.des->adapter().error(), Eq(bitsery::ReaderError::InvalidPointer));
}
TEST_F(SerializeExtensionPointerDeserialization, WhenNonNullPointerIsNullThenInvalidPointerError) {
createSerializer();
bitsery::details::writeSize(sctx1.ser->adapter(), 0u);
auto& des1 = createDeserializer();
des1.ext2b(p1null, PointerOwner{PointerType::NotNull});
EXPECT_THAT(sctx1.des->adapter().error(), Eq(bitsery::ReaderError::InvalidPointer));
auto& des2 = createDeserializer();
des2.ext2b(p1null, PointerObserver{PointerType::NotNull});
EXPECT_THAT(sctx1.des->adapter().error(), Eq(bitsery::ReaderError::InvalidPointer));
}
TEST_F(SerializeExtensionPointerDeserialization, PointerOwnerCreatesObjects) {
auto& ser = createSerializer();
ser.ext2b(pd1, PointerOwner{});
ser.ext4b(pd2, PointerOwner{});
ser.ext(pd3, PointerOwner{});
auto& des = createDeserializer();
des.ext2b(p1null, PointerOwner{});
des.ext4b(p2null, PointerOwner{});
des.ext(p3null, PointerOwner{});
EXPECT_THAT(isPointerContextValid(), Eq(true));
EXPECT_THAT(p1null, ::testing::NotNull());
EXPECT_THAT(p2null, ::testing::NotNull());
EXPECT_THAT(p3null, ::testing::NotNull());
EXPECT_THAT(*p1null, Eq(*pd1));
EXPECT_THAT(*p2null, Eq(*pd2));
EXPECT_THAT(*p3null, Eq(*pd3));
}
TEST_F(SerializeExtensionPointerDeserialization, PointerOwnerDestroysObjects) {
auto& ser = createSerializer();
ser.ext2b(p1null, PointerOwner{});
ser.ext4b(p2null, PointerOwner{});
ser.ext(p3null, PointerOwner{});
auto& des = createDeserializer();
//pr cannot link to local variables, need to allocate them separately
pr1 = new int16_t{};
pr2 = new MyEnumClass{};
pr3 = new MyStruct1{3, 4};
des.ext2b(pr1, PointerOwner{});
des.ext4b(pr2, PointerOwner{});
des.ext(pr3, PointerOwner{});
EXPECT_THAT(isPointerContextValid(), Eq(true));
EXPECT_THAT(pr1, ::testing::IsNull());
EXPECT_THAT(pr2, ::testing::IsNull());
EXPECT_THAT(pr3, ::testing::IsNull());
}
TEST_F(SerializeExtensionPointerDeserialization, PointerObserver) {
auto& ser = createSerializer();
//first owner, than observer
ser.ext4b(d2, ReferencedByPointer{});
ser.ext2b(p1null, PointerObserver{});
ser.ext4b(pd2, PointerObserver{});
//first observer, than owner
ser.ext(pd3, PointerObserver{});
ser.ext(pd3, PointerOwner{});
auto& des = createDeserializer();
des.ext4b(r2, ReferencedByPointer{});
des.ext2b(pr1, PointerObserver{});
des.ext4b(p2null, PointerObserver{});
des.ext(pr3, PointerObserver{});
des.ext(pr3, PointerOwner{});
EXPECT_THAT(isPointerContextValid(), Eq(true));
//serialize null, override non-null
EXPECT_THAT(pr1, Eq(p1null));
//serialize non-null, override null
EXPECT_THAT(*p2null, Eq(*pd2));
EXPECT_THAT(p2null, Eq(&r2));
//serialize non-null override non-null
EXPECT_THAT(*pr3, Eq(*pd3));
EXPECT_THAT(pr3, Eq(&r3));
}
struct Test1Data {
std::vector<MyStruct1> vdata;
std::vector<MyStruct1 *> vptr;
MyStruct1 o1;
MyStruct1 *po1;
int32_t i1;
int32_t *pi1;
template<typename S>
void serialize(S &s) {
//set container elements to be candidates for non-owning pointers
s.container(vdata, 100, [](S& s, MyStruct1 &d) {
s.ext(d, ReferencedByPointer{});
});
//contains non owning pointers
//
//IMPORTANT !!!
// ALWAYS ACCEPT BY REFERENCE like this: T* (&obj)
//
s.container(vptr, 100, [](S& s, MyStruct1 *(&d)) {
s.ext(d, PointerObserver{});
});
//just a regular fields
s.object(o1);
s.value4b(i1);
//observer
s.ext(po1, PointerObserver{});
//owner
s.ext4b(pi1, PointerOwner{});
}
};
TEST(SerializeExtensionPointer, IntegrationTest) {
Test1Data data{};
data.vdata.push_back({165, -45});
data.vdata.push_back({7895, -1576});
data.vdata.push_back({5987, -798});
//container of non owning pointers (observers)
data.vptr.push_back(nullptr);
data.vptr.push_back(std::addressof(data.vdata[0]));
data.vptr.push_back(std::addressof(data.vdata[2]));
//regular fields
data.o1 = MyStruct1{145, 948};
data.i1 = 945415;
//observer
data.po1 = std::addressof(data.vdata[1]);
//owning pointer
data.pi1 = new int32_t{};
Test1Data res{};
PointerLinkingContext plctx1{};
SerContext sctx1;
sctx1.createSerializer(plctx1).object(data);
sctx1.createDeserializer(plctx1).object(res);
EXPECT_THAT(plctx1.isValid(), Eq(true));
//check regular fields
EXPECT_THAT(res.i1, Eq(data.i1));
EXPECT_THAT(res.o1, Eq(data.o1));
//check data container
EXPECT_THAT(res.vdata, ::testing::ContainerEq(data.vdata));
//check owning pointers
EXPECT_THAT(*res.pi1, Eq(*data.pi1));
EXPECT_THAT(res.pi1, ::testing::Ne(data.pi1));
//check if observers points to correct data
EXPECT_THAT(res.po1, Eq(std::addressof(res.vdata[1])));
EXPECT_THAT(res.vptr[0], ::testing::IsNull());
EXPECT_THAT(res.vptr[1], Eq(std::addressof(res.vdata[0])));
EXPECT_THAT(res.vptr[2], Eq(std::addressof(res.vdata[2])));
//free owning raw pointers
delete data.pi1;
delete res.pi1;
}
TEST(SerializeExtensionPointer, PointerOwnerWithNonPolymorphicTypeCanUseLambdaOverload) {
const int32_t NEW_VALUE = 2;
const int32_t OLD_VALUE = 1;
MyStruct1 *data = new MyStruct1{NEW_VALUE, NEW_VALUE};
MyStruct1 *res = new MyStruct1{OLD_VALUE, OLD_VALUE};
//linking context
PointerLinkingContext plctx1{};
SerContext sctx1;
auto& ser = sctx1.createSerializer(plctx1);
ser.ext(data, PointerOwner{}, [](decltype(ser)& ser, MyStruct1 &o) {
//serialize only one field
ser.value4b(o.i1);
});
auto& des = sctx1.createDeserializer(plctx1);
des.ext(res, PointerOwner{}, [](decltype(des)& des,MyStruct1 &o) {
//deserialize only one field
des.value4b(o.i1);
});
EXPECT_THAT(res->i1, Eq(NEW_VALUE));
EXPECT_THAT(res->i2, Eq(OLD_VALUE));//we didn't serialized that
delete data;
delete res;
}
TEST(SerializeExtensionPointer, ReferencedByPointerCanUseLambdaOverload) {
const int32_t NEW_VALUE = 2;
const int32_t OLD_VALUE = 1;
MyStruct1 data = MyStruct1{NEW_VALUE, NEW_VALUE};
MyStruct1 res = MyStruct1{OLD_VALUE, OLD_VALUE};
//linking context
PointerLinkingContext plctx1{};
SerContext sctx1;
auto& ser = sctx1.createSerializer(plctx1);
ser.ext(data, ReferencedByPointer{}, [](decltype(ser)& ser,MyStruct1 &o) {
//serialize only one field
ser.value4b(o.i1);
});
auto& des = sctx1.createDeserializer(plctx1);
des.ext(res, ReferencedByPointer{}, [](decltype(des)& des,MyStruct1 &o) {
//deserialize only one field
des.value4b(o.i1);
});
EXPECT_THAT(res.i1, Eq(NEW_VALUE));
EXPECT_THAT(res.i2, Eq(OLD_VALUE));//we didn't serialized that
}
TEST(SerializeExtensionPointer, PointerOwnerCanUseValueOverload) {
auto *data = new int64_t{49845894};
auto *res = new int64_t{-78548415};
PointerLinkingContext plctx1{};
SerContext sctx1;
sctx1.createSerializer(plctx1).ext8b(data, PointerOwner{});
sctx1.createDeserializer(plctx1).ext8b(res, PointerOwner{});
EXPECT_THAT(*res, Eq(*data));
delete data;
delete res;
}
TEST(SerializeExtensionPointer, ReferencedByPointerCanUseValueOverload) {
int64_t data{49845894};
int64_t res{-78548415};
PointerLinkingContext plctx1{};
SerContext sctx1;
sctx1.createSerializer(plctx1).ext8b(data, ReferencedByPointer{});
sctx1.createDeserializer(plctx1).ext8b(res, ReferencedByPointer{});
EXPECT_THAT(res, Eq(data));
}