//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 #include #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; 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); } 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; ContextInTuple plctx2(0, PointerLinkingContext{}, 0.0f, 'a'); BasicSerializationContext 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{}); sctx1.createDeserializer(); EXPECT_THAT(sctx1.bw->writtenBytesCount(), Eq(4)); EXPECT_THAT(plctx1.isValid(), Eq(true)); } #ifndef NDEBUG TEST(SerializeExtensionPointer, WhenPointerLinkingContextIsNullAndPointerIsNotNullThenAssert) { MyStruct1 tmp; MyStruct1 *data = &tmp; //linking context PointerLinkingContext plctx1{}; SerContext sctx1; EXPECT_DEATH(sctx1.createSerializer(nullptr).ext(data, PointerOwner{}), ""); } 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{}); sctx1.createDeserializer(); EXPECT_THAT(sctx1.bw->writtenBytesCount(), Eq(2)); size_t res; bitsery::details::readSize(*sctx1.br, res, 10000u); EXPECT_THAT(res, Eq(0)); bitsery::details::readSize(*sctx1.br, res, 10000u); 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{}); sctx1.createDeserializer(); EXPECT_THAT(sctx1.bw->writtenBytesCount(), Eq(4)); size_t res; bitsery::details::readSize(*sctx1.br, res, 10000u); EXPECT_THAT(res, Eq(1)); bitsery::details::readSize(*sctx1.br, res, 10000u); EXPECT_THAT(res, Eq(2)); bitsery::details::readSize(*sctx1.br, res, 10000u); EXPECT_THAT(res, Eq(2)); bitsery::details::readSize(*sctx1.br, res, 10000u); EXPECT_THAT(res, Eq(0)); } TEST_F(SerializeExtensionPointerSerialization, PointerObserversDoesntSerializeObject) { auto &ser1 = createSerializer(); ser1.ext2b(pd1, PointerObserver{}); ser1.ext4b(pd2, PointerObserver{}); ser1.ext4b(pd2, PointerObserver{}); sctx1.createDeserializer(); EXPECT_THAT(sctx1.bw->writtenBytesCount(), Eq(3)); } TEST_F(SerializeExtensionPointerSerialization, ReferencedByPointerSerializesIdAndObject) { auto &ser1 = createSerializer(); ser1.ext2b(d1, ReferencedByPointer{}); ser1.ext4b(d2, ReferencedByPointer{}); ser1.ext4b(pd2, PointerObserver{}); auto &des = sctx1.createDeserializer(); EXPECT_THAT(sctx1.bw->writtenBytesCount(), Eq(3 + 6)); size_t id{}; bitsery::details::readSize(*sctx1.br, id, 10000u); EXPECT_THAT(id, Eq(1)); des.value2b(r1); EXPECT_THAT(r1, Eq(d1)); bitsery::details::readSize(*sctx1.br, id, 10000u); EXPECT_THAT(id, Eq(2)); des.value4b(r2); EXPECT_THAT(r2, Eq(d2)); bitsery::details::readSize(*sctx1.br, id, 10000u); EXPECT_THAT(id, Eq(2)); } TEST_F(SerializeExtensionPointerSerialization, PointerOwnerSerializesIdAndObject) { auto &ser1 = createSerializer(); ser1.ext4b(pd2, PointerOwner{}); ser1.ext(pd3, PointerOwner{}); auto &des1 = sctx1.createDeserializer(); //2x ids + int32_t + MyStruct1 EXPECT_THAT(sctx1.bw->writtenBytesCount(), Eq(2 + 4 + MyStruct1::SIZE)); size_t id; bitsery::details::readSize(*sctx1.br, id, 10000u); des1.value4b(r2); EXPECT_THAT(r2, Eq(*pd2)); bitsery::details::readSize(*sctx1.br, id, 10000u); des1.object(r3); EXPECT_THAT(r3, Eq(*pd3)); } class SerializeExtensionPointerDeserialization : public SerializeExtensionPointerSerialization { public: typename SerContext::TSerializer &createSerializer() { return sctx1.createSerializer(&plctx1); } typename SerContext::TDeserializer &createDeserializer() { return sctx1.createDeserializer(&plctx1); } }; 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.bw, 0u); ser.ext2b(d1, ReferencedByPointer{}); auto &des = createDeserializer(); des.ext2b(r1, ReferencedByPointer{}); EXPECT_THAT(sctx1.br->error(), Eq(bitsery::ReaderError::InvalidPointer)); } TEST_F(SerializeExtensionPointerDeserialization, WhenNonNullPointerIsNullThenInvalidPointerError) { createSerializer(); bitsery::details::writeSize(*sctx1.bw, 0u); auto &des1 = createDeserializer(); des1.ext2b(p1null, PointerOwner{PointerType::NotNull}); EXPECT_THAT(sctx1.br->error(), Eq(bitsery::ReaderError::InvalidPointer)); auto &des2 = createDeserializer(); des2.ext2b(p1null, PointerObserver{PointerType::NotNull}); EXPECT_THAT(sctx1.br->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 vdata; std::vector vptr; MyStruct1 o1; MyStruct1 *po1; int32_t i1; int32_t *pi1; template void serialize(S &s) { //set container elements to be candidates for non-owning pointers s.container(vdata, 100, [&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](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{}, [&ser](MyStruct1 &o) { //serialize only one field ser.value4b(o.i1); }); auto &des = sctx1.createDeserializer(&plctx1); des.ext(res, PointerOwner{}, [&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{}, [&ser](MyStruct1 &o) { //serialize only one field ser.value4b(o.i1); }); auto &des = sctx1.createDeserializer(&plctx1); des.ext(res, ReferencedByPointer{}, [&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)); }