5 Commits

Author SHA1 Message Date
Mindaugas Vinkelis
1ca45aab79 updated travis script 2018-03-09 22:03:14 +02:00
fraillt
952635ff70 improved configuration to follow modern cmake practices 2018-03-09 15:59:28 +02:00
fraillt
507b5ae01d added polymorphism support for raw pointers 2018-02-28 16:06:03 +02:00
fraillt
f6d02aba38 added inheritance extension, and ability to have internal contexts within serializer/deserializer 2017-11-12 12:09:12 +02:00
fraillt
be9ccf08d9 added NotNull pointer to to pointer extensions 2017-10-30 09:01:08 +02:00
46 changed files with 2127 additions and 808 deletions

1
.gitignore vendored
View File

@@ -2,3 +2,4 @@
.vs/
build/
cmake-build-debug/
CTestConfig.cmake

View File

@@ -9,15 +9,22 @@ before_install:
- sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test
- sudo apt-get update -qq
install:
install:
- sudo apt-get install -qq g++-5 lcov
- sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-5 90
- sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-5 90
- wget https://github.com/google/googletest/archive/release-1.8.0.tar.gz
- tar xf release-1.8.0.tar.gz
- cd googletest-release-1.8.0
- cmake -DBUILD_SHARED_LIBS=ON .
- make
- sudo make install
- cd ..
before_script:
  - mkdir build
  - cd build
  - cmake ..
  - cmake -DBITSERY_BUILD_TESTS=ON ..
script: make && make test
script: make && (cd tests && ctest)

View File

@@ -1,3 +1,36 @@
# [4.2.1](https://github.com/fraillt/bitsery/compare/v4.2.0...v4.2.1) (2018-03-09)
### Improvements
* changed CMake structure, to follow **Modern CMake** principles.
* bitsery now has *install* target and **find_package(Bitsery)** exports **Bitsery::bitsery** target.
* *GTest* no longer downloads as external application, but tries to find via *find_package*.
* removed *ext* folder, and instead added *scripts* folder that contains few helper scripts for development, currently tested on Ubuntu.
* fixed/added few tests cases.
### Other notes
* some work was done on polymorphism support, but current solution, although working, but has many design and performance issues, and interfaces for extensibility might change drastically.
# [4.2.0](https://github.com/fraillt/bitsery/compare/v4.1.0...v4.2.0) (2017-11-12)
### Features
* serializer/deserializer can now have **internal context(s)** via configuration.
It is convenient way to pass context, when it doesn't convey useful information outside of serializer/deserializer and is default constructable.
* added **contextOrNull\<T\>()** overload to *BasicSerializer/BasicDeserializer*.
Difference between *contextOrNull\<T\>()* and *context\<T\>()* is, that using *context\<T\>()* code doesn't compile if T doesn't exists at all, while using *contextOrNull\<T\>()* code compiles, but returns *nullptr* at runtime.
* added inheritance support via extensions.
In order to correctly manage virtual inheritance two extensions was created in **<bitsery/ext/inheritance.h>** header:
* **BaseClass\<TBase\>** - use when inheriting from objects without virtual inheritance.
* **VirtualBaseClass\<TBase\>** - ensures that only one copy of each virtual base class is serialized.
To keep track of virtual base classes **InheritanceContext** is required, but it is optional if no virtual bases exists in serialization flow.
I.e. if context is not defined, code will not compile only if virtual inheritance is used.
See [inheritance](examples/inheritance.cpp) for usage example.
### Improvements
* added optional ctor parameter for *PointerOwner* and *PointerObserver* - **PointerType**, which specifies if pointer can be null or not.
Default is **Nullable**.
# [4.1.0](https://github.com/fraillt/bitsery/compare/v4.0.1...v4.1.0) (2017-10-27)
### Features
@@ -12,9 +45,9 @@ In order to correctly manage pointer ownership, three extensions was created in
*Currently polimorphism and std::shared_ptr, std::unique_ptr is not supported.*
* added **context\<T\>()** overload to *BasicSerializer/BasicDeserializer* and now serializers is typesafe.
Also for better pointers support, added posibility to have multiple types in context with *std::tuple*.
E.g. when using pointers together with your custom context, you can define your context as *std::tuple\<PointerLinkingContext, MyContext\>* and in serialization function you can correctly get your data via *context\<MyContext\>()*.
* added **context\<T\>()** overload to *BasicSerializer/BasicDeserializer* and now they became typesafe.
For better extensions support, added posibility to have multiple types in context with *std::tuple*.
E.g. when using multiple extensions, that requires specific contexts, together with your custom context, you can define your context as *std::tuple\<PointerLinkingContext, MyContext\>* and in serialization function you can correctly get your data via *context\<MyContext\>()*.
### Improvements

View File

@@ -1,18 +1,64 @@
cmake_minimum_required(VERSION 3.2)
cmake_minimum_required(VERSION 3.1)
project(bitsery
LANGUAGES CXX
VERSION 4.2.1)
project(bitsery)
#======== build options ===================================
option(BITSERY_BUILD_EXAMPLES "Build examples" OFF)
option(BITSERY_BUILD_TESTS "Build tests" OFF)
if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
set(COMPILE_FLAGS "")
#============= setup target ======================
add_library(bitsery INTERFACE)
# create alias, so that user could always write target_link_libraries(... Bitsery::bitsery)
# despite of bitsery target is imported or not
add_library(Bitsery::bitsery ALIAS bitsery)
include(GNUInstallDirs)
target_include_directories(bitsery INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>)
target_compile_features(bitsery INTERFACE
cxx_auto_type
cxx_constexpr
cxx_lambdas
cxx_nullptr
cxx_variadic_templates)
#=============== setup installation =======================
include(CMakePackageConfigHelpers)
write_basic_package_version_file(${CMAKE_CURRENT_BINARY_DIR}/BitseryConfigVersion.cmake
COMPATIBILITY SameMajorVersion)
install(TARGETS bitsery
EXPORT bitseryTargets
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
install(EXPORT bitseryTargets
FILE "BitseryConfig.cmake"
NAMESPACE Bitsery::
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/bitsery)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/BitseryConfigVersion.cmake
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/bitsery)
install(DIRECTORY include/bitsery
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
#================ handle sub-projects =====================
# examples and tests are independend directories, that can be built separatelly.
# make find_package to no-op, when searching for "Bitsery", because it is created here.
macro(find_package)
if (NOT ${ARGV0} STREQUAL Bitsery)
_find_package(${ARGV})
endif()
endmacro()
if (BITSERY_BUILD_EXAMPLES)
message("build bitsery examples")
add_subdirectory(examples)
else()
set(COMPILE_FLAGS "-Wall -Wextra -Wno-missing-braces -Wpedantic -Weffc++")
message("skip bitsery examples")
endif()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${COMPILE_FLAGS}")
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_subdirectory(examples)
enable_testing()
add_subdirectory(tests)
if (BITSERY_BUILD_TESTS)
message("build bitsery tests")
add_subdirectory(tests)
else()
message("skip bitsery tests")
endif()

View File

@@ -8,15 +8,19 @@ you contribute:
1. Fork the repository.
2. Create new branch based on the *master* branch (`git checkout -b your_branch master`). If your contribution is a bug fix, you should name your branch `bugfix/xxx`; for a feature, it should be `feature/xxx`. Otherwise, just use your good judgment. Consistent naming of branches is appreciated since it makes the output of `git branch` easier to understand with a single glance.
3. Do your modifications on that branch. Except for special cases, your contribution should include proper unit tests and documentation.
4. Make sure your modifications did not break anything by building, running tests and checking code coverage (test coverage should not be less than 100%):
4. Make sure your modifications did not break anything by building, running tests:
```shell
mkdir build
cd build
cmake ..
cmake -DBITSERY_BUILD_TESTS=ON ..
make
ctest
make tests_coverage
x-www-browser ./coverage/index.html
(cd tests; ctest)
```
or run CTest scripts and view code coverage (scripts tested on ubuntu, requires lcov for coverage):
```shell
cd scripts
ctest -S build.bitsery.cmake
./show_coverage.sh build
```
5. Commit your changes, and push to your fork (`git push origin your_branch`). Commit message should be one line short description. When applicable, please squash adjacent *wip* commits into a single *logical* commit.
6. Open a pull request against Bitsery *master* branch. Currently ongoing development is on *master*. At some point an integration branch will be set-up, and pull-requests should target that, but for now its all against master. You may see feature branches come and go, too.

View File

@@ -10,6 +10,7 @@ Library design:
* `errors handling`
* `forward/backward compatibility via Growable extension`
* `pointers`
* `inheritance`
Core Serializer/Deserializer functions (alphabetical order):
@@ -18,11 +19,14 @@ Core Serializer/Deserializer functions (alphabetical order):
* `container`
* `ext`
* `context`
* `context<T>`
* `contextOrNull<T>`
* `object`
* `text`
* `value`
Serializer/Deserializer extensions via `ext` method (alphabetical order):
* `BaseClass`
* `Entropy`
* `Growable`
* `PointerOwner`
@@ -34,6 +38,7 @@ Serializer/Deserializer extensions via `ext` method (alphabetical order):
* `StdSet`
* `StdStack`
* `ValueRange`
* `VirtualBaseClass`
AdapterWriter/Reader functions:
* `writeBits/readBits`
@@ -62,9 +67,6 @@ Output adapters (buffer and stream) functions:
Tips and tricks:
* if you're getting static assert "please define 'serialize' function", most likely it is because your **serialize** function is not defined in same namespace as object.
Limitations:
* max **text** or **container** size can be 2^(n-2) (where n = sizeof(std::size_t) * 8) for 32-bit systems it is 1073741823 (0x3FFFFFF).
Other:
* [Contributing](../CONTRIBUTING.md)
* [Change log](../CHANGELOG.md)

View File

@@ -20,10 +20,12 @@
#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
#SOFTWARE.
cmake_minimum_required(VERSION 3.1)
project(bitsery_examples CXX)
cmake_minimum_required(VERSION 3.2)
include_directories(${CMAKE_SOURCE_DIR}/include)
if (NOT TARGET Bitsery::bitsery)
message(FATAL_ERROR "Bitsery::bitsery alias not set. Please generate CMake from bitsery root directory.")
endif()
file(GLOB ExampleFiles ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
@@ -32,10 +34,9 @@ if (WIN32)
list(REMOVE_ITEM ExampleFiles ${CMAKE_CURRENT_SOURCE_DIR}/flexible_assert_linux_x64.cpp)
endif()
FOREACH(ExampleFile ${ExampleFiles})
foreach(ExampleFile ${ExampleFiles})
get_filename_component(ExampleName ${ExampleFile} NAME_WE)
add_executable(${ExampleName} ${ExampleFile})
ENDFOREACH()
add_executable(bitsery.example.${ExampleName} ${ExampleFile})
target_link_libraries(bitsery.example.${ExampleName} PRIVATE Bitsery::bitsery)
endforeach()

117
examples/inheritance.cpp Normal file
View File

@@ -0,0 +1,117 @@
//
//this example coverls all the corner cases that can happen using inherintace
//in reality virtual inherintance is usually avoided, so your code would look much simpler.
//
#include <bitsery/bitsery.h>
#include <bitsery/adapter/buffer.h>
#include <bitsery/traits/vector.h>
//include inheritance extension
//this header contains two extensions, that specifies inheritance type of base class
// BaseClass - normal inheritance
// VirtualBaseClass - when virtual inheritance is used
//in order for virtual inheritance to work, InheritanceContext is required.
//it can be created either internally (via configuration) or externally (pointer to context).
#include <bitsery/ext/inheritance.h>
using bitsery::ext::BaseClass;
using bitsery::ext::VirtualBaseClass;
struct Base {
uint8_t x{};
//Base doesn't have to be polymorphic class, inheritance works at compile-time.
};
template <typename S>
void serialize(S& s, Base& o) {
s.value1b(o.x);
}
struct Derive1:virtual Base {// virtually inherits from base
uint8_t y1{};
};
template <typename S>
void serialize(S& s, Derive1& o) {
//define virtual inheritance, it will not compile if InheritanceContext is not defined in serializer/deserialzer
s.ext(o, VirtualBaseClass<Base>{});
s.value1b(o.y1);
}
//to make it more interesting, serialize private member
struct Derived2:virtual Base {
explicit Derived2(uint8_t y):y2{y} {}
uint8_t getY2() const {
return y2;
};
private:
friend bitsery::Access;
uint8_t y2{};
template <typename S>
void serialize(S& s) {
//notice virtual inheritance
s.ext(*this, VirtualBaseClass<Base>{});
s.value1b(y2);
}
};
struct MultipleInheritance: Derive1, Derived2 {
explicit MultipleInheritance(uint8_t y2):Derived2{y2} {}
uint8_t z{};
};
template <typename S>
void serialize(S& s, MultipleInheritance& o) {
//has two bases, serialize them separately
s.ext(o, BaseClass<Derive1>{});
s.ext(o, BaseClass<Derived2>{});
s.value1b(o.z);
}
namespace bitsery {
// call to serialize function with Derived2 and MultipleInheritance is ambiguous,
// it matches two serialize functions: Base classes non-member fnc and Derived2 member fnc
// we need explicitly select which function to use
template <>
struct SelectSerializeFnc<Derived2>:UseMemberFnc {};
//multiple inheritance has non-member serialize function defined
template <>
struct SelectSerializeFnc<MultipleInheritance>:UseNonMemberFnc {};
}
using namespace bitsery;
// since in this example we're using virtual inheritance we need InheritanceContext as well.
// InheritanceContext is default constructable and has no useful information outside of serializer/deserializer
// lets create it internally, via configuration
struct ConfigWithContext:DefaultConfig {
//always add internal contexts to tuple
using InternalContext = std::tuple<ext::InheritanceContext>;
};
//some helper types
using Buffer = std::vector<uint8_t>;
using Writer = AdapterWriter<OutputBufferAdapter<Buffer>, ConfigWithContext>;
using Reader = AdapterReader<InputBufferAdapter<Buffer>, ConfigWithContext>;
int main() {
MultipleInheritance data{98};
data.x = 254;
data.y1 = 47;
data.z = 1;
Buffer buf{};
BasicSerializer<Writer> ser{OutputBufferAdapter<Buffer>{buf}, nullptr};
ser.object(data);
auto writtenSize = AdapterAccess::getWriter(ser).writtenBytesCount();
MultipleInheritance res{0};
BasicDeserializer<Reader> des{InputBufferAdapter<Buffer>{buf.begin(), writtenSize}};
des.object(res);
assert(AdapterAccess::getReader(des).error() == ReaderError::NoError && AdapterAccess::getReader(des).isCompletedSuccessfully());
assert(data.x == res.x && data.y1 == res.y1 && data.getY2() == res.getY2() && data.z == res.z);
assert(writtenSize == 4);//base is serialized once, because it is inherited virtually
}

View File

@@ -11,6 +11,7 @@
using bitsery::ext::ReferencedByPointer;
using bitsery::ext::PointerObserver;
using bitsery::ext::PointerOwner;
using bitsery::ext::PointerType ;
enum class MyEnum:uint16_t { V1,V2,V3 };
struct MyStruct {
@@ -71,8 +72,8 @@ private:
});
//observer
s.ext(po1, PointerObserver{});
//owner
s.ext4b(pi1, PointerOwner{});
//owner, mark it as not null
s.ext4b(pi1, PointerOwner{PointerType::NotNull});
}
};

View File

@@ -1,198 +0,0 @@
# Copyright (c) 2012 - 2015, Lars Bilke
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its contributors
# may be used to endorse or promote products derived from this software without
# specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
#
#
# 2012-01-31, Lars Bilke
# - Enable Code Coverage
#
# 2013-09-17, Joakim Söderberg
# - Added support for Clang.
# - Some additional usage instructions.
#
# USAGE:
# 0. (Mac only) If you use Xcode 5.1 make sure to patch geninfo as described here:
# http://stackoverflow.com/a/22404544/80480
#
# 1. Copy this file into your cmake modules path.
#
# 2. Add the following line to your CMakeLists.txt:
# INCLUDE(CodeCoverage)
#
# 3. Set compiler flags to turn off optimization and enable coverage:
# SET(CMAKE_CXX_FLAGS "-g -O0 -fprofile-arcs -ftest-coverage")
# SET(CMAKE_C_FLAGS "-g -O0 -fprofile-arcs -ftest-coverage")
#
# 3. Use the function SETUP_TARGET_FOR_COVERAGE to create a custom make target
# which runs your test executable and produces a lcov code coverage report:
# Example:
# SETUP_TARGET_FOR_COVERAGE(
# my_coverage_target # Name for custom target.
# test_driver # Name of the test driver executable that runs the tests.
# # NOTE! This should always have a ZERO as exit code
# # otherwise the coverage generation will not complete.
# coverage # Name of output directory.
# )
#
# 4. Build a Debug build:
# cmake -DCMAKE_BUILD_TYPE=Debug ..
# make
# make my_coverage_target
#
#
# Check prereqs
FIND_PROGRAM( GCOV_PATH gcov )
FIND_PROGRAM( LCOV_PATH lcov )
FIND_PROGRAM( GENHTML_PATH genhtml )
FIND_PROGRAM( GCOVR_PATH gcovr PATHS ${CMAKE_SOURCE_DIR}/tests)
IF(NOT GCOV_PATH)
MESSAGE(FATAL_ERROR "gcov not found! Aborting...")
ENDIF() # NOT GCOV_PATH
IF("${CMAKE_CXX_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang")
IF("${CMAKE_CXX_COMPILER_VERSION}" VERSION_LESS 3)
MESSAGE(FATAL_ERROR "Clang version must be 3.0.0 or greater! Aborting...")
ENDIF()
ELSEIF(NOT CMAKE_COMPILER_IS_GNUCXX)
MESSAGE(FATAL_ERROR "Compiler is not GNU gcc! Aborting...")
ENDIF() # CHECK VALID COMPILER
SET(CMAKE_CXX_FLAGS_COVERAGE
"-g -O0 --coverage -fprofile-arcs -ftest-coverage"
CACHE STRING "Flags used by the C++ compiler during coverage builds."
FORCE )
SET(CMAKE_C_FLAGS_COVERAGE
"-g -O0 --coverage -fprofile-arcs -ftest-coverage"
CACHE STRING "Flags used by the C compiler during coverage builds."
FORCE )
SET(CMAKE_EXE_LINKER_FLAGS_COVERAGE
""
CACHE STRING "Flags used for linking binaries during coverage builds."
FORCE )
SET(CMAKE_SHARED_LINKER_FLAGS_COVERAGE
""
CACHE STRING "Flags used by the shared libraries linker during coverage builds."
FORCE )
MARK_AS_ADVANCED(
CMAKE_CXX_FLAGS_COVERAGE
CMAKE_C_FLAGS_COVERAGE
CMAKE_EXE_LINKER_FLAGS_COVERAGE
CMAKE_SHARED_LINKER_FLAGS_COVERAGE )
IF ( NOT (CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "Coverage"))
MESSAGE( WARNING "Code coverage results with an optimized (non-Debug) build may be misleading" )
ENDIF() # NOT CMAKE_BUILD_TYPE STREQUAL "Debug"
# Param _targetname The name of new the custom make target
# Param _testrunner The name of the target which runs the tests.
# MUST return ZERO always, even on errors.
# If not, no coverage report will be created!
# Param _outputname lcov output is generated as _outputname.info
# HTML report is generated in _outputname/index.html
# Optional fourth parameter is passed as arguments to _testrunner
# Pass them in list form, e.g.: "-j;2" for -j 2
FUNCTION(SETUP_TARGET_FOR_COVERAGE _targetname _testrunner _outputname)
IF(NOT LCOV_PATH)
MESSAGE(FATAL_ERROR "lcov not found! Aborting...")
ENDIF() # NOT LCOV_PATH
IF(NOT GENHTML_PATH)
MESSAGE(FATAL_ERROR "genhtml not found! Aborting...")
ENDIF() # NOT GENHTML_PATH
SET(coverage_info "${CMAKE_BINARY_DIR}/${_outputname}.info")
SET(coverage_cleaned "${coverage_info}.cleaned")
SEPARATE_ARGUMENTS(test_command UNIX_COMMAND "${_testrunner}")
# Setup target
ADD_CUSTOM_TARGET(${_targetname}
# Cleanup lcov
${LCOV_PATH} --directory . --zerocounters
# Run tests
COMMAND ${test_command} ${ARGV3}
# Capturing lcov counters and generating report
COMMAND ${LCOV_PATH} --directory . --capture --output-file ${coverage_info}
#extract only /include/bitsery directory
COMMAND ${LCOV_PATH} --extract ${coverage_info} '*include/bitsery*' --output-file ${coverage_cleaned}
COMMAND ${GENHTML_PATH} -o ${_outputname} ${coverage_cleaned}
COMMAND ${CMAKE_COMMAND} -E remove ${coverage_info} ${coverage_cleaned}
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
COMMENT "Resetting code coverage counters to zero.\nProcessing code coverage counters and generating report."
)
# Show info where to find the report
ADD_CUSTOM_COMMAND(TARGET ${_targetname} POST_BUILD
COMMAND ;
COMMENT "Open ./${_outputname}/index.html in your browser to view the coverage report."
)
ENDFUNCTION() # SETUP_TARGET_FOR_COVERAGE
# Param _targetname The name of new the custom make target
# Param _testrunner The name of the target which runs the tests
# Param _outputname cobertura output is generated as _outputname.xml
# Optional fourth parameter is passed as arguments to _testrunner
# Pass them in list form, e.g.: "-j;2" for -j 2
FUNCTION(SETUP_TARGET_FOR_COVERAGE_COBERTURA _targetname _testrunner _outputname)
IF(NOT PYTHON_EXECUTABLE)
MESSAGE(FATAL_ERROR "Python not found! Aborting...")
ENDIF() # NOT PYTHON_EXECUTABLE
IF(NOT GCOVR_PATH)
MESSAGE(FATAL_ERROR "gcovr not found! Aborting...")
ENDIF() # NOT GCOVR_PATH
ADD_CUSTOM_TARGET(${_targetname}
# Run tests
${_testrunner} ${ARGV3}
# Running gcovr
COMMAND ${GCOVR_PATH} -x -r ${CMAKE_SOURCE_DIR} -e '${CMAKE_SOURCE_DIR}/tests/' -o ${_outputname}.xml
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
COMMENT "Running gcovr to produce Cobertura code coverage report."
)
# Show info where to find the report
ADD_CUSTOM_COMMAND(TARGET ${_targetname} POST_BUILD
COMMAND ;
COMMENT "Cobertura code coverage report saved in ${_outputname}.xml."
)
ENDFUNCTION() # SETUP_TARGET_FOR_COVERAGE_COBERTURA

View File

@@ -1,17 +0,0 @@
function(LinkTestLib TargetName)
add_dependencies(${TargetName} googletest)
if(NOT WIN32 OR MINGW)
FOREACH(LibName ${GTestLinkLibNames})
target_link_libraries(${TargetName} ${GTestLibsDir}/lib${LibName}.a )
ENDFOREACH()
else()
FOREACH(LibName ${GTestLinkLibNames})
target_link_libraries(${TargetName}
debug ${GTestLibsDir}/DebugLibs/${CMAKE_FIND_LIBRARY_PREFIXES}${LibName}d${CMAKE_FIND_LIBRARY_SUFFIXES}
optimized ${GTestLibsDir}/ReleaseLibs/${CMAKE_FIND_LIBRARY_PREFIXES}${LibName}${CMAKE_FIND_LIBRARY_SUFFIXES})
ENDFOREACH()
endif()
target_link_libraries(${TargetName} ${CMAKE_THREAD_LIBS_INIT})
endfunction(LinkTestLib)

View File

@@ -1,79 +0,0 @@
#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.
cmake_minimum_required(VERSION 3.2)
project(gtest_builder C CXX)
include(ExternalProject)
set(ForceSharedCrt ON)
set(DisablePThreads OFF)
if(MINGW)
set(DisablePThreads ON)
endif()
if (${UseGMock})
message("use gmock")
set(BuildArgs -DBUILD_GTEST=OFF -DBUILD_GMOCK=ON)
else ()
message("use gtest only")
set(BuildArgs -DBUILD_GTEST=ON -DBUILD_GMOCK=OFF)
endif()
if (WIN32 AND NOT MINGW)
set(BuildArgs ${BuildArgs}
-DCMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG:PATH=DebugLibs
-DCMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE:PATH=ReleaseLibs)
endif()
ExternalProject_Add(googletest
GIT_REPOSITORY https://github.com/google/googletest.git
CMAKE_ARGS ${BuildArgs}
-Dgtest_force_shared_crt=${ForceSharedCrt}
-Dgtest_disable_pthreads=${DisablePThreads}
PREFIX "${CMAKE_CURRENT_BINARY_DIR}"
# disable update command
UPDATE_COMMAND ""
# disable install step
INSTALL_COMMAND ""
)
#export variables
ExternalProject_Get_Property(googletest source_dir)
ExternalProject_Get_Property(googletest binary_dir)
if (${UseGMock})
# need to include both googletest and googlemock
set(GTestIncludeDirs ${source_dir}/googlemock/include ${source_dir}/googletest/include PARENT_SCOPE)
set(GTestLibsDir ${binary_dir}/googlemock PARENT_SCOPE)
set(GTestLibName gmock PARENT_SCOPE)
set(GTestMainLibName gmock_main PARENT_SCOPE)
set(GTestLinkLibNames gmock_main PARENT_SCOPE)
else()
set(GTestIncludeDirs ${source_dir}/googletest/include PARENT_SCOPE)
set(GTestLibsDir ${binary_dir}/googletest PARENT_SCOPE)
set(GTestLibName gtest PARENT_SCOPE)
set(GTestMainLibName gtest_main PARENT_SCOPE)
# need to include both libs gtest and gtest_main
set(GTestLinkLibNames gtest gtest_main PARENT_SCOPE)
endif()

View File

@@ -21,8 +21,8 @@
//SOFTWARE.
#ifndef BITSERY_ADAPTERS_INPUT_BUFFER_ADAPTER_H
#define BITSERY_ADAPTERS_INPUT_BUFFER_ADAPTER_H
#ifndef BITSERY_ADAPTER_BUFFER_H
#define BITSERY_ADAPTER_BUFFER_H
#include "../details/adapter_common.h"
#include "../traits/core/traits.h"
@@ -204,4 +204,4 @@ namespace bitsery {
}
#endif //BITSERY_ADAPTERS_INPUT_BUFFER_ADAPTER_H
#endif //BITSERY_ADAPTER_BUFFER_H

View File

@@ -21,8 +21,8 @@
//SOFTWARE.
#ifndef BITSERY_ADAPTERS_DYNAMIC_STREAM_H
#define BITSERY_ADAPTERS_DYNAMIC_STREAM_H
#ifndef BITSERY_ADAPTER_STREAM_H
#define BITSERY_ADAPTER_STREAM_H
#include "../details/adapter_common.h"
#include "../traits/array.h"
@@ -224,4 +224,4 @@ namespace bitsery {
using OutputBufferedStreamAdapter = BasicBufferedOutputStreamAdapter<char, std::char_traits<char>>;
}
#endif //BITSERY_ADAPTERS_DYNAMIC_STREAM_H
#endif //BITSERY_ADAPTER_STREAM_H

View File

@@ -22,8 +22,8 @@
#ifndef BITSERY_BASIC_READER_H
#define BITSERY_BASIC_READER_H
#ifndef BITSERY_ADAPTER_READER_H
#define BITSERY_ADAPTER_READER_H
#include "details/sessions.h"
#include <algorithm>
@@ -39,7 +39,7 @@ namespace bitsery {
struct AdapterReader {
//this is required by deserializer
static constexpr bool BitPackingEnabled = false;
using TConfig = Config;
using TValue = typename InputAdapter::TValue;
static_assert(details::IsDefined<TValue>::value, "Please define adapter traits or include from <bitsery/traits/...>");
@@ -99,7 +99,8 @@ namespace bitsery {
}
void setError(ReaderError error) {
return _inputAdapter.setError(error);
if (this->error() == ReaderError::NoError)
_inputAdapter.setError(error);
}
void beginSession() {
@@ -153,6 +154,7 @@ namespace bitsery {
public:
//this is required by deserializer
static constexpr bool BitPackingEnabled = true;
using TConfig = typename TReader::TConfig;
//make TValue unsigned for bitpacking
using UnsignedValue = typename std::make_unsigned<typename TReader::TValue>::type;
using ScratchType = typename details::ScratchType<UnsignedValue>::type;
@@ -267,4 +269,4 @@ namespace bitsery {
};
}
#endif //BITSERY_BUFFER_READER_H
#endif //BITSERY_ADAPTER_READER_H

View File

@@ -22,8 +22,8 @@
#ifndef BITSERY_BASIC_WRITER_H
#define BITSERY_BASIC_WRITER_H
#ifndef BITSERY_ADAPTER_WRITER_H
#define BITSERY_ADAPTER_WRITER_H
#include "details/sessions.h"
@@ -33,10 +33,12 @@
namespace bitsery {
struct MeasureSize {
template <typename Config>
struct BasicMeasureSize {
//measure class is bit-packing enabled, no need to create wrapper for it
static constexpr bool BitPackingEnabled = true;
using TConfig = Config;
template<size_t SIZE, typename T>
void writeBytes(const T &) {
static_assert(std::is_integral<T>(), "");
@@ -95,6 +97,9 @@ namespace bitsery {
size_t _sessionsBytesCount{};
};
//helper type for default config
using MeasureSize = BasicMeasureSize<DefaultConfig>;
template <typename TWriter>
class AdapterWriterBitPackingWrapper;
@@ -103,7 +108,7 @@ namespace bitsery {
struct AdapterWriter {
//this is required by serializer
static constexpr bool BitPackingEnabled = false;
using TConfig = Config;
using TValue = typename OutputAdapter::TValue;
static_assert(details::IsDefined<TValue>::value, "Please define adapter traits or include from <bitsery/traits/...>");
@@ -207,6 +212,7 @@ namespace bitsery {
public:
//this is required by serializer
static constexpr bool BitPackingEnabled = true;
using TConfig = typename TWriter::TConfig;
//make TValue unsigned for bit packing
using UnsignedType = typename std::make_unsigned<typename TWriter::TValue>::type;
@@ -334,4 +340,4 @@ namespace bitsery {
};
}
#endif //BITSERY_BASIC_WRITER_H
#endif //BITSERY_ADAPTER_WRITER_H

View File

@@ -25,8 +25,8 @@
#define BITSERY_BITSERY_H
#define BITSERY_MAJOR_VERSION 4
#define BITSERY_MINOR_VERSION 1
#define BITSERY_PATCH_VERSION 0
#define BITSERY_MINOR_VERSION 2
#define BITSERY_PATCH_VERSION 1
#define BITSERY_QUOTE_MACRO(name) #name
#define BITSERY_BUILD_VERSION_STR(major,minor, patch) \

View File

@@ -24,6 +24,8 @@
#ifndef BITSERY_COMMON_H
#define BITSERY_COMMON_H
#include <tuple>
namespace bitsery {
/*
@@ -41,7 +43,10 @@ namespace bitsery {
//this functionality allows to support backward/forward compatibility
//however reading from streams is not supported, because this functionality requires random access to buffer.
static constexpr bool BufferSessionsEnabled = false;
//list of contexts that will be instanciated internally within serializer/deserializer.
//contexts must be default constructable.
//internal context has priority, if external context with the same type exists.
using InternalContext = std::tuple<>;
};
}

View File

@@ -40,11 +40,14 @@ namespace bitsery {
using BPEnabledType = BasicDeserializer<typename std::conditional<TAdapterReader::BitPackingEnabled,
TAdapterReader, AdapterReaderBitPackingWrapper<TAdapterReader>>::type, TContext>;
static_assert(details::IsSpecializationOf<typename TReader::TConfig::InternalContext, std::tuple>::value,
"Config::InternalContext must be std::tuple");
template <typename ReaderParam>
explicit BasicDeserializer(ReaderParam&& r, TContext* context = nullptr)
: _reader{std::forward<ReaderParam>(r)},
_context{context}
_context{context},
_internalContext{}
{
}
@@ -66,7 +69,12 @@ namespace bitsery {
template <typename T>
T* context(){
return details::getContext<T>(_context);
return details::getContext<T>(_context, _internalContext);
}
template <typename T>
T* contextOrNull(){
return details::getContextIfTypeExists<T>(_context, _internalContext);
}
/*
@@ -326,6 +334,8 @@ namespace bitsery {
TAdapterReader _reader;
TContext* _context;
typename TReader::TConfig::InternalContext _internalContext;
//process value types
//false_type means that we must process all elements individually

View File

@@ -20,8 +20,8 @@
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
//SOFTWARE.
#ifndef BITSERY_DETAILS_BUFFER_COMMON_H
#define BITSERY_DETAILS_BUFFER_COMMON_H
#ifndef BITSERY_DETAILS_ADAPTER_COMMON_H
#define BITSERY_DETAILS_ADAPTER_COMMON_H
#include <algorithm>
#include <utility>
@@ -124,4 +124,4 @@ namespace bitsery {
}
#endif //BITSERY_DETAILS_BUFFER_COMMON_H
#endif //BITSERY_DETAILS_ADAPTER_COMMON_H

View File

@@ -20,8 +20,8 @@
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
//SOFTWARE.
#ifndef BITSERY_DETAILS_BOTH_COMMON_H
#define BITSERY_DETAILS_BOTH_COMMON_H
#ifndef BITSERY_DETAILS_ADAPTER_UTILS_H
#define BITSERY_DETAILS_ADAPTER_UTILS_H
#include <cassert>
#include <cstdint>
@@ -84,4 +84,4 @@ namespace bitsery {
}
}
#endif //BITSERY_DETAILS_BOTH_COMMON_H
#endif //BITSERY_DETAILS_ADAPTER_UTILS_H

View File

@@ -21,8 +21,8 @@
//SOFTWARE.
#ifndef BITSERY_TRAITS_CORE_NOT_DEFINED_TYPE_H
#define BITSERY_TRAITS_CORE_NOT_DEFINED_TYPE_H
#ifndef BITSERY_DETAILS_NOT_DEFINED_TYPE_H
#define BITSERY_DETAILS_NOT_DEFINED_TYPE_H
#include <iterator>
@@ -76,4 +76,4 @@ namespace std {
using iterator_category = std::random_access_iterator_tag;
};
}
#endif //BITSERY_TRAITS_CORE_NOT_DEFINED_TYPE_H
#endif //BITSERY_DETAILS_NOT_DEFINED_TYPE_H

View File

@@ -41,18 +41,33 @@ namespace bitsery {
}
};
//when call to serialize function is ambiguous (member and non-member serialize function exists for a type)
//specialize this class, by inheriting from either UseNonMemberFnc or UseMemberFnc
//e.g.
//template <> struct SelectSerializeFnc<MyDerivedClass>:UseMemberFnc {};
template<typename T>
struct SelectSerializeFnc : std::integral_constant<int, 0> {
};
//types you need to inherit from when specializing SelectSerializeFnc class
struct UseNonMemberFnc : std::integral_constant<int, 1> {
};
struct UseMemberFnc : std::integral_constant<int, 2> {
};
//serializer/deserializer, does not public interface to get underlying writer/reader
//to prevent users from using writer/reader directly, because they have different interface
//and they cannot be used describing serialization flows.: use extensions for this reason.
//this class allows to get underlying adapter writer/reader, and only should be used outside serialization functions.
struct AdapterAccess {
template <typename Serializer>
static typename Serializer::TWriter& getWriter(Serializer& s) {
template<typename Serializer>
static typename Serializer::TWriter &getWriter(Serializer &s) {
return s._writer;
}
template <typename Deserializer>
static typename Deserializer::TReader& getReader(Deserializer& s) {
template<typename Deserializer>
static typename Deserializer::TReader &getReader(Deserializer &s) {
return s._reader;
}
};
@@ -60,101 +75,104 @@ namespace bitsery {
namespace details {
//helper types for error handling
template <typename T>
struct IsContainerTraitsDefined:public IsDefined<typename traits::ContainerTraits<T>::TValue> {
template<typename T>
struct IsContainerTraitsDefined : public IsDefined<typename traits::ContainerTraits<T>::TValue> {
};
template <typename T>
struct IsTextTraitsDefined:public IsDefined<typename traits::TextTraits<T>::TValue> {
template<typename T>
struct IsTextTraitsDefined : public IsDefined<typename traits::TextTraits<T>::TValue> {
};
template <typename Ext, typename T>
struct IsExtensionTraitsDefined:public IsDefined<typename traits::ExtensionTraits<Ext, T>::TValue> {
template<typename Ext, typename T>
struct IsExtensionTraitsDefined : public IsDefined<typename traits::ExtensionTraits<Ext, T>::TValue> {
};
#ifdef _MSC_VER
//helper types for HasSerializeFunction
template <typename S, typename T>
using TrySerializeFunction = decltype(serialize(std::declval<S &>(), std::declval<T &>()));
//helper types for HasSerializeFunction
template <typename S, typename T>
using TrySerializeFunction = decltype(serialize(std::declval<S &>(), std::declval<T &>()));
template <typename S, typename T>
struct HasSerializeFunctionHelper {
template <typename Q, typename R, typename = TrySerializeFunction<Q, R>>
static std::true_type tester(Q&&, R&&);
static std::false_type tester(...);
using type = decltype(tester(std::declval<S>(), std::declval<T>()));
};
template <typename S, typename T>
struct HasSerializeFunction :HasSerializeFunctionHelper<S, T>::type {};
template <typename S, typename T>
struct HasSerializeFunctionHelper {
template <typename Q, typename R, typename = TrySerializeFunction<Q, R>>
static std::true_type tester(Q&&, R&&);
static std::false_type tester(...);
using type = decltype(tester(std::declval<S>(), std::declval<T>()));
};
template <typename S, typename T>
struct HasSerializeFunction :HasSerializeFunctionHelper<S, T>::type {};
//helper types for HasSerializeMethod
template <typename S, typename T>
using TrySerializeMethod = decltype(Access::serialize(std::declval<S &>(), std::declval<T &>()));
//helper types for HasSerializeMethod
template <typename S, typename T>
using TrySerializeMethod = decltype(Access::serialize(std::declval<S &>(), std::declval<T &>()));
template <typename S, typename T>
struct HasSerializeMethodHelper {
template <typename Q, typename R, typename = TrySerializeMethod<Q, R>>
static std::true_type tester(Q&&, R&&);
static std::false_type tester(...);
using type = decltype(tester(std::declval<S>(), std::declval<T>()));
};
template <typename S, typename T>
struct HasSerializeMethod :HasSerializeMethodHelper<S, T>::type {};
template <typename S, typename T>
struct HasSerializeMethodHelper {
template <typename Q, typename R, typename = TrySerializeMethod<Q, R>>
static std::true_type tester(Q&&, R&&);
static std::false_type tester(...);
using type = decltype(tester(std::declval<S>(), std::declval<T>()));
};
template <typename S, typename T>
struct HasSerializeMethod :HasSerializeMethodHelper<S, T>::type {};
//helper types for IsFlexibleIncluded
template <typename S, typename T>
using TryArchiveProcess = decltype(archiveProcess(std::declval<S &>(), std::declval<T &&>()));
//helper types for IsFlexibleIncluded
template <typename S, typename T>
using TryArchiveProcess = decltype(archiveProcess(std::declval<S &>(), std::declval<T &&>()));
template <typename S, typename T>
struct IsFlexibleIncludedHelper {
template <typename Q, typename R, typename = TryArchiveProcess<Q, R>>
static std::true_type tester(Q&&, R&&);
static std::false_type tester(...);
using type = decltype(tester(std::declval<S>(), std::declval<T>()));
};
template <typename S, typename T>
struct IsFlexibleIncludedHelper {
template <typename Q, typename R, typename = TryArchiveProcess<Q, R>>
static std::true_type tester(Q&&, R&&);
static std::false_type tester(...);
using type = decltype(tester(std::declval<S>(), std::declval<T>()));
};
template <typename S, typename T>
struct IsFlexibleIncluded :IsFlexibleIncludedHelper<S, T>::type {};
template <typename S, typename T>
struct IsFlexibleIncluded :IsFlexibleIncludedHelper<S, T>::type {};
#else
//helper metafunction, that is added to c++17
template<typename... Ts>
struct make_void {
typedef void type;
};
template<typename... Ts>
using void_t = typename make_void<Ts...>::type;
//helper metafunction, that is added to c++17
template<typename... Ts>
struct make_void {
typedef void type;
};
template<typename... Ts>
using void_t = typename make_void<Ts...>::type;
template <typename, typename, typename = void>
struct HasSerializeFunction :std::false_type {};
template<typename, typename, typename = void>
struct HasSerializeFunction : std::false_type {
};
template <typename S, typename T>
struct HasSerializeFunction<S, T,
void_t<decltype(serialize(std::declval<S &>(), std::declval<T &>()))>
> : std::true_type {};
template<typename S, typename T>
struct HasSerializeFunction<S, T,
void_t<decltype(serialize(std::declval<S &>(), std::declval<T &>()))>
> : std::true_type {
};
template <typename, typename, typename = void>
struct HasSerializeMethod :std::false_type {};
template<typename, typename, typename = void>
struct HasSerializeMethod : std::false_type {
};
template <typename S, typename T>
struct HasSerializeMethod<S, T,
void_t<decltype(Access::serialize(std::declval<S &>(), std::declval<T &>()))>
> : std::true_type {};
template<typename S, typename T>
struct HasSerializeMethod<S, T,
void_t<decltype(Access::serialize(std::declval<S &>(), std::declval<T &>()))>
> : std::true_type {
};
//this solution doesn't work with visual studio, but is more elegant
template <typename, typename, typename = void>
struct IsFlexibleIncluded :std::false_type {};
//this solution doesn't work with visual studio, but is more elegant
template<typename, typename, typename = void>
struct IsFlexibleIncluded : std::false_type {
};
template <typename S, typename T>
struct IsFlexibleIncluded<S, T,
void_t<decltype(archiveProcess(std::declval<S &>(), std::declval<T &&>()))>
> : std::true_type {};
template<typename S, typename T>
struct IsFlexibleIncluded<S, T,
void_t<decltype(archiveProcess(std::declval<S &>(), std::declval<T &&>()))>
> : std::true_type {
};
#endif
//used for extensions, when extension TValue = void
struct DummyType {
};
@@ -201,23 +219,34 @@ namespace bitsery {
struct SerializeFunction {
static void invoke(S &s, T &v) {
static_assert(HasSerializeFunction<S,T>::value || HasSerializeMethod<S,T>::value,
static_assert(HasSerializeFunction<S, T>::value || HasSerializeMethod<S, T>::value,
"\nPlease define 'serialize' function for your type (inside or outside of class):\n"
" template<typename S>\n"
" void serialize(S& s)\n"
" {\n"
" ...\n"
" }\n");
static_assert(!(HasSerializeFunction<S,T>::value && HasSerializeMethod<S,T>::value),
"\nPlease define only one 'serialize' function (member OR free), not both.");
internalInvoke(s,v, HasSerializeMethod<S,T>{});
using TDecayed = typename std::decay<T>::type;
selectSerializeFnc(s, v, SelectSerializeFnc<TDecayed>{});
}
private:
static void internalInvoke(S& s, T& v,std::true_type) {
Access::serialize(s,v);
static void selectSerializeFnc(S &s, T &v, std::integral_constant<int, 0>) {
static_assert(!(HasSerializeFunction<S, T>::value && HasSerializeMethod<S, T>::value),
"\nPlease define only one 'serialize' function (member OR free).\n"
"If serialization function is inherited from base class, then explicitly select correct function for your type e.g.:\n"
" template <>\n"
" struct SelectSerializeFnc<DerivedClass>:UseMemberFnc {};\n");
selectSerializeFnc(s, v, std::integral_constant<int,
HasSerializeFunction<S, T>::value ? 1 : 2>{});
}
static void internalInvoke(S& s, T& v,std::false_type) {
serialize(s,v);
static void selectSerializeFnc(S &s, T &v, std::integral_constant<int, 1>) {
serialize(s, v);
}
static void selectSerializeFnc(S &s, T &v, std::integral_constant<int, 2>) {
Access::serialize(s, v);
}
};
@@ -228,7 +257,7 @@ namespace bitsery {
template<typename S, typename T, typename Enabled = void>
struct ArchiveFunction {
static void invoke(S &s, T&& obj) {
static void invoke(S &s, T &&obj) {
static_assert(IsFlexibleIncluded<S, T>::value,
"\nPlease include '<bitsery/flexible.h>' to use 'archive' function:\n");
@@ -237,71 +266,135 @@ namespace bitsery {
};
/*
* function for getting context from serializer/deserializer
* has different behaviour when context is tuple
* helper function for getting context from serializer/deserializer
*/
template <typename T, template <typename...> class Template>
struct IsSpecializationOf : std::false_type
{};
template<typename T, template<typename...> class Template>
struct IsSpecializationOf : std::false_type {
};
template <template <typename...> class Template, typename... Args>
struct IsSpecializationOf<Template<Args...>, Template> : std::true_type
{};
template<template<typename...> class Template, typename... Args>
struct IsSpecializationOf<Template<Args...>, Template> : std::true_type {
};
//helper types for better error messages
template <typename Find, typename ... TList>
struct GetTypeIndex:std::integral_constant<size_t, 0>
{};
template<typename Find, typename ... TList>
struct GetTypeIndex : std::integral_constant<size_t, 0> {
};
//found it
template <typename Find, typename ... Tail>
struct GetTypeIndex<Find, Find, Tail...> :std::integral_constant<size_t, 0>
{};
template<typename Find, typename ... Tail>
struct GetTypeIndex<Find, Find, Tail...> : std::integral_constant<size_t, 0> {
};
//iteratates over types
template <typename Find, typename Head, typename ... Tail>
struct GetTypeIndex<Find, Head, Tail...> :std::integral_constant<size_t, 1 + GetTypeIndex<Find, Tail...>::value>
{};
template<typename Find, typename Head, typename ... Tail>
struct GetTypeIndex<Find, Head, Tail...> : std::integral_constant<size_t,
1 + GetTypeIndex<Find, Tail...>::value> {
};
template <typename Find, typename ... TList>
struct HasType: std::integral_constant<bool, (GetTypeIndex<Find, TList...>::value < (sizeof ... (TList)))>
{};
template<typename Find, typename ... TList>
struct HasType : std::integral_constant<bool, (GetTypeIndex<Find, TList...>::value<(sizeof ... (TList)))> {
};
template<typename TCast, typename Tuple>
struct HasContext : std::is_same<TCast, Tuple> {
};
//when context is tuple, then get object from tuple
#if __cplusplus >= 201402L
template <typename TCast, typename ... Args>
TCast* getContextImpl(std::tuple<Args...>* ctx, std::true_type) {
static_assert(HasType<TCast, Args...>::value, "Invalid context cast from tuple. Type doesn't exists.");
return std::addressof(std::get<TCast>(*ctx));
}
#else
//c++11 doesn't have std::get<type> overload for getting tuple element, so we need to write our selves
template<typename TCast, typename ... Args>
struct HasContext<TCast, std::tuple<Args...>> : HasType<TCast, Args...> {
};
template <typename TCast, typename ... Args>
TCast* getContextImpl(std::tuple<Args...>* ctx, std::true_type) {
/*
* get context, and static assert if type doesn't exists
*/
template<typename TCast, typename ... Args>
TCast *getContextImpl(std::tuple<Args...> *ctx, std::true_type) {
using TCastIndex = GetTypeIndex<TCast, Args...>;
static_assert(HasType<TCast, Args...>::value, "Invalid context cast from tuple. Type doesn't exists.");
static_assert(HasType<TCast, Args...>::value, "Invalid context cast. Type doesn't exists.");
return std::addressof(std::get<TCastIndex::value>(*ctx));
}
#endif
template <typename TCast, typename TContext>
TCast* getContextImpl(TContext* ctx, std::false_type) {
static_assert(std::is_convertible<TContext*, TCast*>::value, "Invalid context cast");
return static_cast<TCast*>(ctx);
template<typename TCast, typename TContext>
TCast *getContextImpl(TContext *ctx, std::false_type) {
static_assert(std::is_convertible<TContext *, TCast *>::value, "Invalid context cast. Type doesn't exists.");
return static_cast<TCast *>(ctx);
}
template <typename TCast, typename TContext>
TCast* getContext(TContext* ctx) {
//get local ctx
template<typename TCast, typename TContext, typename TInternalContext>
TCast *chooseInternalOrExternalContext(TContext *, TInternalContext &internalCtx, std::true_type) {
return getContextImpl<TCast>(&internalCtx, std::true_type{});
}
//get external ctx
template<typename TCast, typename TContext, typename TInternalContext>
TCast *chooseInternalOrExternalContext(TContext *ctx, TInternalContext &, std::false_type) {
return ctx
? getContextImpl<TCast>(ctx, IsSpecializationOf<TContext, std::tuple>{})
: nullptr;
}
}
template<typename TCast, typename TContext, typename TInternalContext>
TCast *getContext(TContext *ctx, TInternalContext &internalCtx) {
return chooseInternalOrExternalContext<TCast>(ctx, internalCtx, HasContext<TCast, TInternalContext>{});
}
/*
* get context, if type doesn't exists then do not static_assert but return null instead
*/
template<typename TCast, typename TContext>
TCast *getContextFromTypeIfExists(TContext *ctx, std::true_type) {
return static_cast<TCast *>(ctx);
}
template<typename TCast, typename TContext>
TCast *getContextFromTypeIfExists(TContext *, std::false_type) {
return nullptr;
}
template<typename TCast, typename TContext>
TCast *getContextImplIfExists(TContext *ctx, std::false_type) {
return getContextFromTypeIfExists<TCast>(ctx, std::is_convertible<TContext *, TCast *>{});
}
template<typename TCast, typename TContext>
TCast *getContextFromTupleIfExists(TContext *ctx, std::true_type tmp) {
return getContextImpl<TCast>(ctx, tmp);
}
template<typename TCast, typename TContext>
TCast *getContextFromTupleIfExists(TContext *, std::false_type) {
return nullptr;
}
template<typename TCast, typename TContext>
TCast *getContextImplIfExists(TContext *ctx, std::true_type) {
return getContextFromTupleIfExists<TCast>(ctx, HasContext<TCast, TContext>{});
}
//get local ctx
template<typename TCast, typename TContext, typename TInternalContext>
TCast *chooseInternalOrExternalContextIfExists(TContext *, TInternalContext &internalCtx, std::true_type) {
return getContextImplIfExists<TCast>(&internalCtx, std::true_type{});
}
//get external ctx
template<typename TCast, typename TContext, typename TInternalContext>
TCast *chooseInternalOrExternalContextIfExists(TContext *ctx, TInternalContext &, std::false_type) {
return ctx
? getContextImplIfExists<TCast>(ctx, IsSpecializationOf<TContext, std::tuple>{})
: nullptr;
}
template<typename TCast, typename TContext, typename TInternalContext>
TCast *getContextIfTypeExists(TContext *ctx, TInternalContext &internalCtx) {
return chooseInternalOrExternalContextIfExists<TCast>(ctx, internalCtx, HasContext<TCast, TInternalContext>{});
}
}
}
#endif //BITSERY_DETAILS_SERIALIZATION_COMMON_H

View File

@@ -2,8 +2,8 @@
// Created by fraillt on 17.10.5.
//
#ifndef BITSERY_BUFFER_SESSIONS_H
#define BITSERY_BUFFER_SESSIONS_H
#ifndef BITSERY_DETAILS_SESSIONS_H
#define BITSERY_DETAILS_SESSIONS_H
#include <vector>
#include <stack>
@@ -163,9 +163,12 @@ namespace bitsery {
break;
}
}
_posItRef = _endItRef;
//restore end position
_endItRef = _sessionsStack.top();
//modify pointers only if no error or buffer overflow
if (_reader.error() == ReaderError::NoError || _reader.error() == ReaderError::DataOverflow) {
_posItRef = _endItRef;
//restore end position
_endItRef = _sessionsStack.top();
}
_sessionsStack.pop();
}
}
@@ -222,4 +225,4 @@ namespace bitsery {
}
}
#endif //BITSERY_BUFFER_SESSIONS_H
#endif //BITSERY_DETAILS_SESSIONS_H

View File

@@ -23,6 +23,8 @@
#ifndef BITSERY_EXT_GROWABLE_H
#define BITSERY_EXT_GROWABLE_H
#include "../traits/core/traits.h"
namespace bitsery {
namespace ext {

View File

@@ -0,0 +1,156 @@
//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.
#ifndef BITSERY_EXT_INHERITANCE_H
#define BITSERY_EXT_INHERITANCE_H
#include <unordered_set>
#include "../traits/core/traits.h"
namespace bitsery {
namespace ext {
//required when virtual inheritance (ext::VirtualBaseClass) exists in serialization flow.
//for standard inheritance (ext::BaseClass) it is optional.
class InheritanceContext {
public:
InheritanceContext() = default;
InheritanceContext(const InheritanceContext&) = delete;
InheritanceContext&operator = (const InheritanceContext&) = delete;
InheritanceContext(InheritanceContext&&) = default;
InheritanceContext& operator = (InheritanceContext&&) = default;
template <typename TDerived, typename TBase>
void beginBase(const TDerived &derived, const TBase &) {
if (_depth == 0) {
const void* ptr = std::addressof(derived);
if ( _parentPtr != ptr)
_virtualBases.clear();
_parentPtr = ptr;
}
++_depth;
}
template <typename TDerived, typename TBase>
bool beginVirtualBase(const TDerived &derived, const TBase &base) {
beginBase(derived, base);
return _virtualBases.emplace(std::addressof(base)).second;
}
void end() {
--_depth;
}
private:
//these members are required to know when we can clear _virtualBases
size_t _depth{};
const void* _parentPtr{};
//add virtual bases to the list, as long as we're on the same parent
std::unordered_set<const void*> _virtualBases{};
};
template <typename TBase>
class BaseClass {
public:
template<typename Ser, typename Writer, typename T, typename Fnc>
void serialize(Ser &ser, Writer &, const T &obj, Fnc &&fnc) const {
auto& resObj = static_cast<const TBase&>(obj);
if (auto ctx = ser.template contextOrNull<InheritanceContext>()) {
ctx->beginBase(obj, resObj);
fnc(const_cast<TBase&>(resObj));
ctx->end();
} else {
fnc(const_cast<TBase&>(resObj));
}
}
template<typename Des, typename Reader, typename T, typename Fnc>
void deserialize(Des &des, Reader &, T &obj, Fnc &&fnc) const {
auto& resObj = static_cast<TBase&>(obj);
if (auto ctx = des.template contextOrNull<InheritanceContext>()) {
ctx->beginBase(obj, resObj);
fnc(resObj);
ctx->end();
} else {
fnc(resObj);
}
}
};
//requires InheritanceContext
template <typename TBase>
class VirtualBaseClass {
public:
template<typename Ser, typename Writer, typename T, typename Fnc>
void serialize(Ser &ser, Writer &, const T &obj, Fnc &&fnc) const {
auto ctx = ser.template context<InheritanceContext>();
auto& resObj = static_cast<const TBase&>(obj);
if (ctx->beginVirtualBase(obj, resObj))
fnc(const_cast<TBase&>(resObj));
ctx->end();
}
template<typename Des, typename Reader, typename T, typename Fnc>
void deserialize(Des &des, Reader &, T &obj, Fnc &&fnc) const {
auto ctx = des.template context<InheritanceContext>();
auto& resObj = static_cast<TBase&>(obj);
if (ctx->beginVirtualBase(obj, resObj))
fnc(resObj);
ctx->end();
}
};
}
namespace traits {
template<typename TBase, typename T>
struct ExtensionTraits<ext::BaseClass<TBase>, T> {
static_assert(std::is_base_of<TBase, T>::value, "Invalid base class");
using TValue = TBase;
static constexpr bool SupportValueOverload = false;
static constexpr bool SupportObjectOverload = true;
static constexpr bool SupportLambdaOverload = true;
};
template<typename TBase, typename T>
struct ExtensionTraits<ext::VirtualBaseClass<TBase>, T> {
static_assert(std::is_base_of<TBase, T>::value, "Invalid base class");
using TValue = TBase;
static constexpr bool SupportValueOverload = false;
static constexpr bool SupportObjectOverload = true;
//disable lambda overload, when serializing virtually inherited base class.
//Only one instance of virtual base will be serialized, when using multiple inheritance
//and it will be undefined behaviour if derived classes would have different virtual base class serialization flow.
static constexpr bool SupportLambdaOverload = false;
};
}
}
#endif //BITSERY_EXT_INHERITANCE_H

View File

@@ -23,277 +23,85 @@
#ifndef BITSERY_EXT_POINTER_H
#define BITSERY_EXT_POINTER_H
#include <unordered_map>
#include <vector>
#include <memory>
#include "../traits/core/traits.h"
#include "utils/pointer_utils.h"
#include "utils/rtti_utils.h"
namespace bitsery {
namespace ext {
//forward declare
class PointerLinkingContext;
namespace details_pointer {
enum class PointerOwnershipType:uint8_t {
//is not responsible for pointer lifetime management.
Observer,
//only ONE owner is responsible for this pointers creation/destruction
Owner,
//MANY shared owners is responsible for pointer creation/destruction
//requires additional context to manage shared owners themselves.
Shared
};
class PointerLinkingContextSerialization {
public:
explicit PointerLinkingContextSerialization()
:_currId{0},
_ptrMap{}
{}
PointerLinkingContextSerialization(const PointerLinkingContextSerialization&) = delete;
PointerLinkingContextSerialization& operator = (const PointerLinkingContextSerialization&) = delete;
PointerLinkingContextSerialization(PointerLinkingContextSerialization&&) = default;
PointerLinkingContextSerialization& operator = (PointerLinkingContextSerialization&&) = default;
~PointerLinkingContextSerialization() = default;
size_t createId(const void *ptr, PointerOwnershipType ptrType) {
if (ptr == nullptr)
return 0u;
auto res = _ptrMap.emplace(ptr, PointerInfo{_currId + 1u, ptrType});
auto& ptrInfo = res.first->second;
if (res.second) {
++_currId;
return ptrInfo.id;
}
//ptr already exists
//for observer return success
if (ptrType == PointerOwnershipType::Observer)
return ptrInfo.id;
//set owner and return success
if (ptrInfo.ownershipType == PointerOwnershipType::Observer) {
ptrInfo.ownershipType = ptrType;
return ptrInfo.id;
}
//only shared ownership can get here multiple times
assert(ptrType == PointerOwnershipType::Shared);
return ptrInfo.id;
}
template <typename S, typename TPtr>
void serialize(S& s, const TPtr& obj) {
using TNonPtr = typename std::remove_pointer<TPtr>::type;
static_assert(!std::is_polymorphic<TNonPtr>::value, "Polymorphic types are not supported");
serializeImpl(s, *obj, details::IsFundamentalType<TNonPtr>{});
}
//valid, when all pointers have owners.
//we cannot serialize pointers, if we haven't serialized objects themselves
bool isPointerSerializationValid() const {
return std::all_of(_ptrMap.begin(), _ptrMap.end(), [](const std::pair<const void*, PointerInfo>& p) {
return p.second.ownershipType != PointerOwnershipType::Observer;
});
}
private:
template <typename S, typename T>
void serializeImpl(S& s, const T& obj, std::true_type) {
s.template value<sizeof(T)>(obj);
}
template <typename S, typename T>
void serializeImpl(S& s, const T& obj, std::false_type) {
s.object(obj);
}
struct PointerInfo {
PointerInfo(size_t id_, PointerOwnershipType ownershipType_)
:id{id_},
ownershipType{ownershipType_}
{};
size_t id;
PointerOwnershipType ownershipType;
};
size_t _currId;
std::unordered_map<const void*, PointerInfo> _ptrMap;
};
//this class is used to store context for shared ptr owners
struct SharedContextBase {
virtual ~SharedContextBase() = default;
};
//helper functions that creates or destroys pointers
//useful, because can be specialized
template <typename T>
void destroyPointer(T& ptr) {
if (ptr) {
delete ptr;
}
ptr = nullptr;
}
template <typename T>
void createPointer(T& ptr) {
using TNonPtr = typename std::remove_pointer<T>::type;
if (ptr == nullptr)
ptr = new TNonPtr{};
}
class PointerLinkingContextDeserialization {
public:
explicit PointerLinkingContextDeserialization()
: _idMap{}
{}
PointerLinkingContextDeserialization(const PointerLinkingContextDeserialization&) = delete;
PointerLinkingContextDeserialization& operator = (const PointerLinkingContextDeserialization&) = delete;
PointerLinkingContextDeserialization(PointerLinkingContextDeserialization&&) = default;
PointerLinkingContextDeserialization& operator = (PointerLinkingContextDeserialization&&) = default;
~PointerLinkingContextDeserialization() = default;
void processOwnerPtr(size_t id, void* ptr, PointerOwnershipType ptrType) {
assert(id != 0 && ptr != nullptr && ptrType != PointerOwnershipType::Observer);
auto res = _idMap.emplace(id, PointerInfo{id, ptr, ptrType});
auto& ptrInfo = res.first->second;
if (!res.second) {
assert(ptrInfo.ownershipType != PointerOwnershipType::Owner);
//if already exists, then process observer list
if (ptrInfo.ownershipType == PointerOwnershipType::Observer) {
ptrInfo.ptrAddress = ptr;
ptrInfo.ownershipType = ptrType;
processObserverList(ptrInfo.observersList, ptr);
}
}
}
void processObserverPtr(size_t id, void* (&ptr)) {
auto res = _idMap.emplace(id, PointerInfo{id, nullptr, PointerOwnershipType::Observer});
auto& ptrInfo = res.first->second;
if (ptrInfo.ptrAddress)
ptr = ptrInfo.ptrAddress;
else
ptrInfo.observersList.push_back(ptr);
}
template <typename S, typename TPtr>
void deserialize(S& s, TPtr& obj) {
using TNonPtr = typename std::remove_pointer<TPtr>::type;
static_assert(!std::is_polymorphic<TNonPtr>::value, "Polymorphic types are not supported");
createPointer(obj);
deserializeImpl(s, *obj, details::IsFundamentalType<TNonPtr>{});
}
//valid, when all pointers has owners
bool isPointerDeserializationValid() const {
return std::all_of(_idMap.begin(), _idMap.end(), [](const std::pair<const size_t, PointerInfo>& p) {
return p.second.ownershipType != PointerOwnershipType::Observer;
});
}
private:
struct PointerInfo {
PointerInfo(size_t id_, void* ptr, PointerOwnershipType ownershipType_)
:id{id_},
ownershipType{ownershipType_},
ptrAddress{ptr},
observersList{},
sharedContext{}
{};
PointerInfo(const PointerInfo&) = delete;
PointerInfo& operator = (const PointerInfo&) = delete;
PointerInfo(PointerInfo&&) = default;
PointerInfo&operator = (PointerInfo&&) = default;
~PointerInfo() = default;
size_t id;
PointerOwnershipType ownershipType;
void* ptrAddress;
std::vector<std::reference_wrapper<void*>> observersList;
std::unique_ptr<SharedContextBase> sharedContext;
};
void processObserverList(std::vector<std::reference_wrapper<void*>>& observers, void* ptr) {
for (auto& o:observers)
o.get() = ptr;
observers.clear();
observers.shrink_to_fit();
}
template <typename S, typename T>
void deserializeImpl(S& s, T& obj, std::true_type) {
s.template value<sizeof(T)>(obj);
}
template <typename S, typename T>
void deserializeImpl(S& s, T& obj, std::false_type) {
s.object(obj);
}
std::unordered_map<size_t, PointerInfo> _idMap;
};
template <typename S>
PointerLinkingContext& getLinkingContext(S& s) {
template<typename S>
PointerLinkingContext &getLinkingContext(S &s) {
auto res = s.template context<PointerLinkingContext>();
assert(res != nullptr);
return *res;
}
template<typename TObject>
struct RawPointerObjectHandler {
using TPointer = TObject;
template<typename T>
void create(TObject &obj) const {
obj = new T{};
}
void destroy(TObject &obj) const {
delete obj;
obj = nullptr;
}
const TPointer getPtr(const TObject &obj) const {
return obj;
}
TPointer getPtr(TObject &obj) const {
return obj;
}
};
template <typename TObject>
struct RawPointerManagerConfig {
using RTTI = bitsery::ext::utils::StandardRTTI;
static constexpr PointerOwnershipType OwnershipType = PointerOwnershipType::Owner;
using Handler = RawPointerObjectHandler<TObject>;
static std::unique_ptr<utils::PointerSharedContextBase> createSharedContext(TObject &) {
return {};
}
static void restoreFromSharedContext(TObject &, utils::PointerSharedContextBase *) {
}
};
}
//this class is for convenience
class PointerLinkingContext:
public details_pointer::PointerLinkingContextSerialization,
public details_pointer::PointerLinkingContextDeserialization {
class PointerOwner : public utils::PointerOwnerManager<details_pointer::RawPointerManagerConfig> {
public:
bool isValid() {
return isPointerSerializationValid() && isPointerDeserializationValid();
}
};
class PointerOwner {
public:
template<typename Ser, typename Writer, typename T, typename Fnc>
void serialize(Ser &ser, Writer &w, const T &obj, Fnc &&) const {
auto& ctx = details_pointer::getLinkingContext(ser);
auto id = ctx.createId(obj, details_pointer::PointerOwnershipType::Owner);
details::writeSize(w, id);
if (id)
ctx.serialize(ser, obj);
}
template<typename Des, typename Reader, typename T, typename Fnc>
void deserialize(Des &des, Reader &r, T &obj, Fnc &&) const {
details_pointer::getLinkingContext(des);
size_t id{};
details::readSize(r, id, std::numeric_limits<size_t>::max());
if (id) {
auto& ctx = details_pointer::getLinkingContext(des);
ctx.deserialize(des, obj);
ctx.processOwnerPtr(id, obj, details_pointer::PointerOwnershipType::Owner);
} else {
details_pointer::destroyPointer(obj);
}
}
explicit PointerOwner(PointerType ptrType = PointerType::Nullable) : PointerOwnerManager(ptrType) {}
};
class PointerObserver {
public:
explicit PointerObserver(PointerType ptrType = PointerType::Nullable) : _ptrType{ptrType} {}
template<typename Ser, typename Writer, typename T, typename Fnc>
void serialize(Ser &ser, Writer &w, const T &obj, Fnc &&) const {
auto& ctx = details_pointer::getLinkingContext(ser);
details::writeSize(w, ctx.createId(obj, details_pointer::PointerOwnershipType::Observer));
auto &ctx = details_pointer::getLinkingContext(ser);
if (obj) {
details::writeSize(w, ctx.getInfoByPtr(obj, PointerOwnershipType::Observer).id);
} else {
assert(_ptrType == PointerType::Nullable);
details::writeSize(w, 0);
}
}
template<typename Des, typename Reader, typename T, typename Fnc>
@@ -301,32 +109,37 @@ namespace bitsery {
size_t id{};
details::readSize(r, id, std::numeric_limits<size_t>::max());
if (id) {
auto& ctx = details_pointer::getLinkingContext(des);
ctx.processObserverPtr(id, reinterpret_cast<void*&>(obj));
auto &ctx = details_pointer::getLinkingContext(des);
ctx.getInfoById(id, PointerOwnershipType::Observer).processObserver(reinterpret_cast<void *&>(obj));
} else {
obj = nullptr;
if (_ptrType == PointerType::Nullable)
obj = nullptr;
else
r.setError(ReaderError::InvalidPointer);
}
}
private:
PointerType _ptrType;
};
class ReferencedByPointer {
public:
template<typename Ser, typename Writer, typename T, typename Fnc>
void serialize(Ser &ser, Writer &w, const T &obj, Fnc && fnc) const {
auto& ctx = details_pointer::getLinkingContext(ser);
details::writeSize(w, ctx.createId(&obj, details_pointer::PointerOwnershipType::Owner));
fnc(const_cast<T&>(obj));
void serialize(Ser &ser, Writer &w, const T &obj, Fnc &&fnc) const {
auto &ctx = details_pointer::getLinkingContext(ser);
details::writeSize(w, ctx.getInfoByPtr(&obj, PointerOwnershipType::Owner).id);
fnc(const_cast<T &>(obj));
}
template<typename Des, typename Reader, typename T, typename Fnc>
void deserialize(Des &des, Reader &r, T &obj, Fnc && fnc) const {
void deserialize(Des &des, Reader &r, T &obj, Fnc &&fnc) const {
size_t id{};
details::readSize(r, id, std::numeric_limits<size_t>::max());
if (id) {
auto& ctx = details_pointer::getLinkingContext(des);
auto &ctx = details_pointer::getLinkingContext(des);
fnc(obj);
ctx.processOwnerPtr(id, &obj, details_pointer::PointerOwnershipType::Owner);
ctx.getInfoById(id, PointerOwnershipType::Owner).processOwner(&obj);
} else {
//cannot be null for references
r.setError(ReaderError::InvalidPointer);
@@ -339,7 +152,7 @@ namespace bitsery {
namespace traits {
template<typename T>
struct ExtensionTraits<ext::PointerOwner, T*> {
struct ExtensionTraits<ext::PointerOwner, T *> {
using TValue = T;
static constexpr bool SupportValueOverload = true;
static constexpr bool SupportObjectOverload = true;
@@ -348,7 +161,7 @@ namespace bitsery {
};
template<typename T>
struct ExtensionTraits<ext::PointerObserver, T*> {
struct ExtensionTraits<ext::PointerObserver, T *> {
//although pointer observer doesn't serialize anything, but we still add value overload support to be consistent with pointer owners
using TValue = T;
static constexpr bool SupportValueOverload = true;

View File

@@ -23,6 +23,7 @@
#ifndef BITSERY_EXT_STD_MAP_H
#define BITSERY_EXT_STD_MAP_H
#include "../traits/core/traits.h"
#include "../details/adapter_utils.h"
namespace bitsery {

View File

@@ -33,6 +33,7 @@
// using optional = experimental::optional<T>;
//}
#include <type_traits>
#include "../traits/core/traits.h"
namespace bitsery {
namespace ext {

View File

@@ -27,6 +27,7 @@
#include "../details/adapter_utils.h"
//we need this, so we could
#include <unordered_set>
#include "../traits/core/traits.h"
namespace bitsery {
namespace ext {

View File

@@ -0,0 +1,319 @@
//
// Created by fraillt on 17.11.30.
//
#ifndef BITSERY_POINTER_UTILS_H
#define BITSERY_POINTER_UTILS_H
#include <unordered_map>
#include <vector>
#include <memory>
#include "polymorphism_utils.h"
namespace bitsery {
namespace ext {
enum class PointerType {
Nullable,
NotNull
};
enum class PointerOwnershipType : uint8_t {
//is not responsible for pointer lifetime management.
Observer,
//only ONE owner is responsible for this pointers creation/destruction
Owner,
//MANY shared owners is responsible for pointer creation/destruction
//requires additional context to manage shared owners themselves.
Shared
};
//forward declaration
class PointerLinkingContext;
namespace utils {
class PointerLinkingContextSerialization {
public:
explicit PointerLinkingContextSerialization()
: _currId{0},
_ptrMap{} {}
PointerLinkingContextSerialization(const PointerLinkingContextSerialization &) = delete;
PointerLinkingContextSerialization &operator=(const PointerLinkingContextSerialization &) = delete;
PointerLinkingContextSerialization(PointerLinkingContextSerialization &&) = default;
PointerLinkingContextSerialization &operator=(PointerLinkingContextSerialization &&) = default;
~PointerLinkingContextSerialization() = default;
struct PointerInfo {
PointerInfo(size_t id_, PointerOwnershipType ownershipType_)
: id{id_},
ownershipType{ownershipType_},
sharedCount{0} {};
size_t id;
PointerOwnershipType ownershipType;
size_t sharedCount;
};
const PointerInfo &getInfoByPtr(const void *ptr, PointerOwnershipType ptrType) {
auto res = _ptrMap.emplace(ptr, PointerInfo{_currId + 1u, ptrType});
auto &ptrInfo = res.first->second;
if (res.second) {
++_currId;
return ptrInfo;
}
//ptr already exists
//for observer return success
if (ptrType == PointerOwnershipType::Observer)
return ptrInfo;
//set owner and return success
if (ptrInfo.ownershipType == PointerOwnershipType::Observer) {
ptrInfo.ownershipType = ptrType;
return ptrInfo;
}
//only shared ownership can get here multiple times
assert(ptrType == PointerOwnershipType::Shared);
ptrInfo.sharedCount++;
return ptrInfo;
}
//valid, when all pointers have owners.
//we cannot serialize pointers, if we haven't serialized objects themselves
bool isPointerSerializationValid() const {
return std::all_of(_ptrMap.begin(), _ptrMap.end(),
[](const std::pair<const void *, PointerInfo> &p) {
return p.second.ownershipType != PointerOwnershipType::Observer;
});
}
private:
size_t _currId;
std::unordered_map<const void *, PointerInfo> _ptrMap;
};
//this class is used to store context for shared ptr owners
struct PointerSharedContextBase {
virtual ~PointerSharedContextBase() = default;
};
class PointerLinkingContextDeserialization {
public:
explicit PointerLinkingContextDeserialization()
: _idMap{} {}
PointerLinkingContextDeserialization(const PointerLinkingContextDeserialization &) = delete;
PointerLinkingContextDeserialization &operator=(const PointerLinkingContextDeserialization &) = delete;
PointerLinkingContextDeserialization(PointerLinkingContextDeserialization &&) = default;
PointerLinkingContextDeserialization &operator=(PointerLinkingContextDeserialization &&) = default;
~PointerLinkingContextDeserialization() = default;
struct PointerInfo {
PointerInfo(size_t id_, void *ptr, PointerOwnershipType ownershipType_)
: id{id_},
ownershipType{ownershipType_},
ownerPtr{ptr},
observersList{},
sharedContext{} {};
PointerInfo(const PointerInfo &) = delete;
PointerInfo &operator=(const PointerInfo &) = delete;
PointerInfo(PointerInfo &&) = default;
PointerInfo &operator=(PointerInfo &&) = default;
~PointerInfo() = default;
void processOwner(void *ptr) {
ownerPtr = ptr;
assert(ownershipType != PointerOwnershipType::Observer);
for (auto &o:observersList)
o.get() = ptr;
observersList.clear();
observersList.shrink_to_fit();
}
void processObserver(void *(&ptr)) {
if (ownerPtr) {
ptr = ownerPtr;
} else {
observersList.push_back(ptr);
}
}
size_t id;
PointerOwnershipType ownershipType;
void *ownerPtr;
std::vector<std::reference_wrapper<void *>> observersList;
std::unique_ptr<PointerSharedContextBase> sharedContext;
};
PointerInfo &getInfoById(size_t id, PointerOwnershipType ptrType) {
auto res = _idMap.emplace(id, PointerInfo{id, nullptr, ptrType});
auto &ptrInfo = res.first->second;
if (!res.second) {
assert(ptrType != PointerOwnershipType::Owner ||
ptrInfo.ownershipType == PointerOwnershipType::Observer);
if (ptrInfo.ownershipType == PointerOwnershipType::Observer)
ptrInfo.ownershipType = ptrType;
}
return ptrInfo;
}
//valid, when all pointers has owners
bool isPointerDeserializationValid() const {
return std::all_of(_idMap.begin(), _idMap.end(), [](const std::pair<const size_t, PointerInfo> &p) {
return p.second.ownershipType != PointerOwnershipType::Observer;
});
}
private:
std::unordered_map<size_t, PointerInfo> _idMap;
};
template<template<typename> class Config>
class PointerOwnerManager {
template <typename TObject>
using Handler = typename Config<TObject>::Handler;
template<typename TObject>
struct HelperTypes {
using RTTI = typename Config<TObject>::RTTI;
using THandler = Handler<TObject>;
using TValue = typename std::remove_pointer<typename THandler::TPointer>::type;
};
template<typename Ser, typename T, typename Fnc>
void serializeImpl(PointerLinkingContextSerialization &, Ser &ser, const T &obj, Fnc &&,
std::true_type) const {
InheritanceTreeSerialize<Ser, T, HelperTypes> tree{};
auto handler = tree.getHandler(obj);
assert(handler.second);
ser.object(handler.first);
handler.second->process(ser, const_cast<T &>(obj));
}
template<typename Ser, typename T, typename Fnc>
void serializeImpl(PointerLinkingContextSerialization &, Ser &, const T &obj, Fnc &&fnc,
std::false_type) const {
auto handler = Handler<T>{};
fnc(*handler.getPtr(const_cast<T &>(obj)));
}
template<typename Des, typename T, typename Fnc, typename Reader>
void deserializeImpl(PointerLinkingContextDeserialization &, Des &des, T &obj, Fnc &&,
Reader &r, std::true_type) const {
InheritanceTreeTypeId id{};
des.object(id);
InheritanceTreeDeserialize<Des, T, HelperTypes> tree{};
auto handler = tree.getHandler(id);
if (handler) {
handler->create(obj);
handler->process(des, obj);
} else {
r.setError(ReaderError::InvalidPointer);
}
}
template<typename Des, typename T, typename Fnc, typename Reader>
void deserializeImpl(PointerLinkingContextDeserialization &, Des &, T &obj, Fnc &&fnc,
Reader &, std::false_type) const {
using TValue = typename HelperTypes<T>::TValue;
auto handler = Handler<T>{};
if (auto ptr = handler.getPtr(obj)) {
fnc(*ptr);
} else {
handler.template create<TValue>(obj);
fnc(*handler.getPtr(obj));
}
}
PointerType _ptrType;
public:
explicit PointerOwnerManager(PointerType ptrType = PointerType::Nullable) : _ptrType{ptrType} {}
template<typename Ser, typename Writer, typename T, typename Fnc>
void serialize(Ser &ser, Writer &w, const T &obj, Fnc &&fnc) const {
auto handler = Handler<T>{};
auto ptr = handler.getPtr(obj);
if (ptr) {
auto ctx = ser.template context<PointerLinkingContext>();
assert(ctx != nullptr);
auto &ptrInfo = ctx->getInfoByPtr(ptr, Config<T>::OwnershipType);
details::writeSize(w, ptrInfo.id);
if (ptrInfo.sharedCount == 0) {
serializeImpl(*ctx, ser, obj, std::forward<Fnc>(fnc),
std::is_polymorphic<typename HelperTypes<T>::TValue>{});
}
} else {
assert(_ptrType == PointerType::Nullable);
details::writeSize(w, 0);
}
}
template<typename Des, typename Reader, typename T, typename Fnc>
void deserialize(Des &des, Reader &r, T &obj, Fnc &&fnc) const {
size_t id{};
details::readSize(r, id, std::numeric_limits<size_t>::max());
auto handler = Handler<T>{};
if (id) {
auto ctx = des.template context<PointerLinkingContext>();
assert(ctx != nullptr);
auto &ptrInfo = ctx->getInfoById(id, Config<T>::OwnershipType);
//todo add deserialization checking
if (ptrInfo.ownershipType == PointerOwnershipType::Owner) {
deserializeImpl(*ctx, des, obj, std::forward<Fnc>(fnc), r,
std::is_polymorphic<typename HelperTypes<T>::TValue>{});
ptrInfo.processOwner(handler.getPtr(obj));
} else {
if (!ptrInfo.sharedContext) {
deserializeImpl(*ctx, des, obj, std::forward<Fnc>(fnc), r,
std::is_polymorphic<typename HelperTypes<T>::TValue>{});
ptrInfo.processOwner(handler.getPtr(obj));
ptrInfo.sharedContext = Config<T>::createSharedContext(obj);
} else {
Config<T>::restoreFromSharedContext(obj, ptrInfo.sharedContext.get());
}
}
} else {
if (_ptrType == PointerType::Nullable && handler.getPtr(obj)) {
handler.destroy(obj);
} else
r.setError(ReaderError::InvalidPointer);
}
}
};
}
//this class is for convenience
class PointerLinkingContext :
public utils::PointerLinkingContextSerialization,
public utils::PointerLinkingContextDeserialization {
public:
bool isValid() {
return isPointerSerializationValid() && isPointerDeserializationValid();
}
};
}
}
#endif //BITSERY_POINTER_UTILS_H

View File

@@ -0,0 +1,225 @@
//
// Created by fraillt on 17.11.3.
//
#ifndef BITSERY_EXT_POLYMORPHISM_H
#define BITSERY_EXT_POLYMORPHISM_H
#include "../inheritance.h"
#include <unordered_map>
#include <functional>
#include <memory>
namespace bitsery {
namespace ext {
namespace details_polymorphism {
//stores template types
template<typename ...>
struct List {
};
}
//specialize for your base class by deriving from DerivedClasses with list of derivatives that DIRECTLY inherits from your base class.
//e.g.
// template <> PolymorphicBase<Animal>: DerivedClasses<Dog, Cat>{};
// template <> PolymorphicBase<Dog>: DerivedClasses<Bulldog, GoldenRetriever> {};
// IMPORTANT !!!
// although you can add all derivates to same base like this:
// SuperClass<Animal>:DerivedClasses<Dog, Cat, Bulldog, GoldenRetriever>{};
// it will not work when you try to serialize Dog*, because it will not find Bulldog and GoldenRetriever
template<typename TBase>
struct PolymorphicBaseClass {
using childs = details_polymorphism::List<>;
};
//derive from this class when specifying childs for your base class, atleast one child must exists, hence T1
//e.g.
// template <> SuperClass<Animal>: DerivedClasses<Dog, Cat>{};
template<typename T1, typename ... Tn>
struct DerivedClasses {
using childs = details_polymorphism::List<T1, Tn...>;
};
namespace utils {
//this object will be used to serialize/deserialize polymorphic type id within inheritance tree from base class
struct InheritanceTreeTypeId {
uint16_t depth{};
uint16_t index{};
template <typename S>
void serialize(S& s) {
s.value2b(depth);
s.value2b(index);
}
};
template <typename S, typename TObject>
struct PolymorphicObjectHandlerInterface {
virtual void process(S& s, TObject& obj) const = 0;
virtual void create(TObject& obj) const = 0;
virtual ~PolymorphicObjectHandlerInterface() = default;
};
}
namespace details_polymorphism {
template<typename S, typename TObject, typename TDerived, typename RTTI, typename ObjectHandler>
struct PolymorphicObjectHandlerBase : public utils::PolymorphicObjectHandlerInterface<S, TObject> {
void process(S &s, TObject &obj) const final {
s.object(dynamic_cast<TDerived &>(*_handler.getPtr(obj)));
};
void create(TObject &obj) const final {
auto ptr = _handler.getPtr(obj);
if (ptr && RTTI::get(*ptr) != RTTI::template get<TDerived>()) {
_handler.destroy(obj);
_handler.template create<TDerived>(obj);
} else {
if (ptr == nullptr)
_handler.template create<TDerived>(obj);
}
}
ObjectHandler _handler{};
};
template<typename S, typename TObject, template<typename> class TObjectManager>
class PolymorphicHandlersGenerator {
public:
template<typename Fnc>
static void generate(Fnc &&addHandlerFnc) {
PolymorphicHandlersGenerator tmp{std::forward<Fnc>(addHandlerFnc)};
}
private:
using RTTI = typename TObjectManager<TObject>::RTTI;
template <typename TDerived>
using TPolymorphicObjectHandler = PolymorphicObjectHandlerBase<S, TObject, TDerived, RTTI, typename TObjectManager<TObject>::THandler>;
template<typename Fnc>
explicit PolymorphicHandlersGenerator(Fnc &&addHandlerFnc):_addHandler(std::forward<Fnc>(addHandlerFnc)) {
//fill inheritance tree
using TBase = typename TObjectManager<TObject>::TValue;
add<TBase>();
}
template<typename TClass>
void add() {
addClass<TClass>();
//save current index and increase depth
auto saveIndex = index;
index = 0;
depth++;
addChilds<TClass>(typename PolymorphicBaseClass<TClass>::childs{});
//restore index and depth
depth--;
index = saveIndex;
}
template<typename TClassBase, typename T1, typename ... Tn>
void addChilds(details_polymorphism::List<T1, Tn...>) {
static_assert(std::is_base_of<TClassBase, T1>::value,
"PolymorphicBaseClass<TBase> must derive a list of derived classes from TBase.");
add<T1>();
addChilds<TClassBase>(details_polymorphism::List<Tn...>{});
}
template<typename>
void addChilds(details_polymorphism::List<>) {
}
template<typename TClass>
void addClass() {
if (!std::is_abstract<TClass>::value) {
utils::InheritanceTreeTypeId tid{};
tid.index = index;
tid.depth = depth;
#if __cplusplus > 201103L
auto handler = std::make_unique<TPolymorphicObjectHandler<TClass>>();
#else
auto handler = std::unique_ptr<TPolymorphicObjectHandler<TClass>>(
new TPolymorphicObjectHandler<TClass>{});
#endif
if (_addHandler(RTTI::template get<TClass>(), tid, std::move(handler)))
++index;
}
}
std::function<bool(size_t, utils::InheritanceTreeTypeId,
std::unique_ptr<utils::PolymorphicObjectHandlerInterface<S, TObject>> &&)> _addHandler;
uint16_t depth{};
uint16_t index{};
};
}
namespace utils {
template<typename Ser, typename TObject, template<typename> class TObjectManager>
class InheritanceTreeSerialize {
public:
InheritanceTreeSerialize() {
details_polymorphism::PolymorphicHandlersGenerator<Ser, TObject, TObjectManager>::generate(
[this](size_t typeHash, InheritanceTreeTypeId id,
std::unique_ptr<PolymorphicObjectHandlerInterface<Ser, TObject>> &&handler) {
return _map.emplace(std::make_pair(typeHash, std::make_pair(id, std::move(handler)))).second;
}
);
}
const std::pair<InheritanceTreeTypeId, PolymorphicObjectHandlerInterface<Ser, TObject>*> getHandler(const TObject &obj) {
using RTTI = typename TObjectManager<TObject>::RTTI;
auto handler = typename TObjectManager<TObject>::THandler{};
auto it = _map.find(RTTI::get(*handler.getPtr(obj)));
if (it != _map.end()) {
return std::make_pair(it->second.first, it->second.second.get());
}
return {};
}
private:
std::unordered_map<size_t, std::pair<InheritanceTreeTypeId, std::unique_ptr<PolymorphicObjectHandlerInterface<Ser, TObject>>>> _map{};
};
template<typename Des, typename TObject, template<typename> class TObjectManager>
class InheritanceTreeDeserialize {
public:
InheritanceTreeDeserialize() {
details_polymorphism::PolymorphicHandlersGenerator<Des, TObject, TObjectManager>::generate(
[this](size_t, InheritanceTreeTypeId id, std::unique_ptr<PolymorphicObjectHandlerInterface<Des, TObject>> &&handler) {
return _map.emplace(std::make_pair(getHashFromId(id), std::move(handler))).second;
}
);
}
const PolymorphicObjectHandlerInterface<Des, TObject> *getHandler(const InheritanceTreeTypeId &id) {
auto it = _map.find(getHashFromId(id));
if (it != _map.end())
return it->second.get();
return nullptr;
}
private:
size_t getHashFromId(const InheritanceTreeTypeId &id) const {
size_t res = id.depth << 16;
return res + id.index;
}
std::unordered_map<size_t, std::unique_ptr<PolymorphicObjectHandlerInterface<Des, TObject>>> _map{};
};
}
}
}
#endif //BITSERY_EXT_POLYMORPHISM_H

View File

@@ -0,0 +1,28 @@
//
// Created by fraillt on 17.11.30.
//
#ifndef BITSERY_RTTI_UTILS_H
#define BITSERY_RTTI_UTILS_H
#include <typeinfo>
namespace bitsery {
namespace ext {
namespace utils {
struct StandardRTTI {
template<typename T>
static size_t get() {
return typeid(T).hash_code();
}
template<typename T>
static size_t get(T &&obj) {
return typeid(obj).hash_code();
}
};
}
}
}
#endif //BITSERY_RTTI_UTILS_H

View File

@@ -39,10 +39,14 @@ namespace bitsery {
using BPEnabledType = BasicSerializer<typename std::conditional<TAdapterWriter::BitPackingEnabled,
TAdapterWriter, AdapterWriterBitPackingWrapper<TAdapterWriter>>::type, TContext>;
static_assert(details::IsSpecializationOf<typename TWriter::TConfig::InternalContext, std::tuple>::value,
"Config::InternalContext must be std::tuple");
template <typename WriterParam>
explicit BasicSerializer(WriterParam&& w, TContext* context = nullptr)
: _writer{std::forward<WriterParam>(w)},
_context{context}
_context{context},
_internalContext{}
{
}
@@ -64,7 +68,12 @@ namespace bitsery {
template <typename T>
T* context() {
return details::getContext<T>(_context);
return details::getContext<T>(_context, _internalContext);
}
template <typename T>
T* contextOrNull() {
return details::getContextIfTypeExists<T>(_context, _internalContext);
}
/*
@@ -322,6 +331,7 @@ namespace bitsery {
TAdapterWriter _writer;
TContext* _context;
typename TWriter::TConfig::InternalContext _internalContext;
//process value types
//false_type means that we must process all elements individually

View File

@@ -20,8 +20,8 @@
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
//SOFTWARE.
#ifndef BITSERY_TRAITS_HELPER_STD_DEFAULTS_H
#define BITSERY_TRAITS_HELPER_STD_DEFAULTS_H
#ifndef BITSERY_TRAITS_CORE_STD_DEFAULTS_H
#define BITSERY_TRAITS_CORE_STD_DEFAULTS_H
#include "traits.h"
#include <iostream>
@@ -82,4 +82,4 @@ namespace bitsery {
}
}
#endif //BITSERY_TRAITS_HELPER_STD_DEFAULTS_H
#endif //BITSERY_TRAITS_CORE_STD_DEFAULTS_H

View File

@@ -0,0 +1,7 @@
set(CTEST_PROJECT_NAME "bitsery")
set(CTEST_DROP_METHOD "http")
set(CTEST_DROP_SITE "my.cdash.org")
set(CTEST_DROP_LOCATION "/submit.php?project=bitsery")
set(CTEST_DROP_SITE_CDASH TRUE)

View File

@@ -0,0 +1,20 @@
# run from linux shell:
#$ ctest -S build.bitsery.cmake
set(CTEST_SOURCE_DIRECTORY "..") #path to bitsery root (top-level) directory
set(CTEST_BINARY_DIRECTORY "build")
set(ENV{CXXFLAGS} "--coverage")
#when using Ninja generator, ctest_coverage cannot find files...
set(CTEST_CMAKE_GENERATOR "CodeBlocks - Unix Makefiles")
set(CTEST_USE_LAUNCHERS 1)
set(CTEST_COVERAGE_COMMAND "gcov")
configure_file(CTestConfig.cmake ${CTEST_SOURCE_DIRECTORY}/CTestConfig.cmake)
ctest_start("Continuous")
ctest_configure(OPTIONS "-DBITSERY_BUILD_EXAMPLES=OFF;-DBITSERY_BUILD_TESTS=ON")
ctest_build()
ctest_test(BUILD ${CTEST_BINARY_DIRECTORY}/tests)
ctest_coverage()
#ctest_submit()

8
scripts/show_coverage.sh Normal file
View File

@@ -0,0 +1,8 @@
#!/bin/sh
BUILD_DIR=$1
TESTS_BUILD_DIR=$BUILD_DIR/tests/CMakeFiles/
COV_INFO=$TESTS_BUILD_DIR/bitsery_coverage.info
lcov --directory $TESTS_BUILD_DIR --capture --output-file $COV_INFO
lcov --extract $COV_INFO '*include/bitsery*' --output-file $COV_INFO.clean
genhtml --output-directory $TESTS_BUILD_DIR/coverage_web $COV_INFO.clean
xdg-open $TESTS_BUILD_DIR/coverage_web/index.html

View File

@@ -20,25 +20,14 @@
#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
#SOFTWARE.
cmake_minimum_required(VERSION 3.2)
set(TestProjectName bitsery_tests)
project(${TestProjectName} C CXX)
cmake_minimum_required(VERSION 3.5)
project(bitsery_tests CXX)
find_package(GTest 1.8 REQUIRED)
find_package(Threads REQUIRED)
#add googletest external project
#USE_GMOCK enable gmock
#exports variables GTEST_INCLUDE_DIRS, GTEST_LIBS_DIR, GTEST_LIBNAME, GTEST_MAIN_LIBNAME
set(ExtCMakeFilesDir ${CMAKE_SOURCE_DIR}/ext)
set(UseGMock ON)
add_subdirectory(${ExtCMakeFilesDir}/gtest ${CMAKE_BINARY_DIR}/gtest)
#this helps idea to know which files are actually used
file(GLOB_RECURSE IncludeHeaders ${CMAKE_SOURCE_DIR}/include/bitsery/*.h)
# set common include folder for module
include_directories(SYSTEM ${GTestIncludeDirs})
include_directories(${CMAKE_SOURCE_DIR}/include)
if (NOT TARGET Bitsery::bitsery)
message(FATAL_ERROR "Bitsery::bitsery alias not set. Please generate CMake from bitsery root directory.")
endif()
file(GLOB TestSourceFiles ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
@@ -47,28 +36,27 @@ if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
list(REMOVE_ITEM TestSourceFiles ${CMAKE_CURRENT_SOURCE_DIR}/serialization_ext_std_optional.cpp)
endif()
include(${ExtCMakeFilesDir}/LinkTestLib.cmake)
enable_testing()
FOREACH(TestFile ${TestSourceFiles})
foreach(TestFile ${TestSourceFiles})
get_filename_component(TestName ${TestFile} NAME_WE)
set(TestName TEST_${TestName})
add_executable(${TestName} ${TestFile} ${IncludeHeaders} serialization_test_utils.h)
LinkTestLib(${TestName})
set(TestName bitsery.test.${TestName})
add_executable(${TestName} ${TestFile})
target_link_libraries(${TestName} PRIVATE GTest::Main Bitsery::bitsery)
add_test(NAME ${TestName} COMMAND $<TARGET_FILE:${TestName}>)
endforeach()
ENDFOREACH()
#======================= setup development environment ====================
#all in one tests for code coverage
add_executable(${TestProjectName} ${TestSourceFiles})
if(CMAKE_COMPILER_IS_GNUCXX)
include(${ExtCMakeFilesDir}/CodeCoverage.cmake)
target_compile_options(${TestProjectName} PUBLIC -O0 -fprofile-arcs -ftest-coverage)
target_link_libraries(${TestProjectName} -O0 -fprofile-arcs -ftest-coverage)
setup_target_for_coverage(tests_coverage ${TestProjectName} coverage)
# get all header files for IDE (in my case Clion) and create dummy project that consumes theses files
get_directory_property(ParentDir PARENT_DIRECTORY)
if (ParentDir)
# only include when working from root project (Bitsery)
file(GLOB_RECURSE HeadersForIDE ${ParentDir}/include/bitsery/*.h)
# create dummy target IDE
file(WRITE ${CMAKE_BINARY_DIR}/dummy_for_ide.cpp "//generated by CMake to create dummy target with all includes for IDE.")
add_library(bitsery.dummy_for_ide ${CMAKE_BINARY_DIR}/dummy_for_ide.cpp )
# add headers so IDE correctly show them
target_sources(bitsery.dummy_for_ide PRIVATE ${HeadersForIDE} serialization_test_utils.h)
target_link_libraries(bitsery.dummy_for_ide PRIVATE GTest::Main Bitsery::bitsery)
endif()
LinkTestLib(${TestProjectName})

View File

@@ -44,7 +44,7 @@ TEST(FlexibleSyntax, FundamentalTypesAndBool) {
int ti = 8745;
MyEnumClass te = MyEnumClass::E4;
float tf = 485.042f;
double_t td = -454184.48445;
double td = -454184.48445;
bool tb=true;
SerializationContext ctx{};
ctx.createSerializer().archive(ti,te,tf,td,tb);
@@ -53,7 +53,7 @@ TEST(FlexibleSyntax, FundamentalTypesAndBool) {
int ri{};
MyEnumClass re{};
float rf{};
double_t rd{};
double rd{};
bool rb{};
ctx.createDeserializer().archive(ri,re,rf,rd,rb);
@@ -69,7 +69,7 @@ TEST(FlexibleSyntax, UseObjectFncInsteadOfValueN) {
int ti = 8745;
MyEnumClass te = MyEnumClass::E4;
float tf = 485.042f;
double_t td = -454184.48445;
double td = -454184.48445;
bool tb=true;
SerializationContext ctx;
auto& ser = ctx.createSerializer();
@@ -83,7 +83,7 @@ TEST(FlexibleSyntax, UseObjectFncInsteadOfValueN) {
int ri{};
MyEnumClass re{};
float rf{};
double_t rd{};
double rd{};
bool rb{};
auto& des = ctx.createDeserializer();
des.object(ri);
@@ -104,7 +104,7 @@ TEST(FlexibleSyntax, MixDifferentSyntax) {
int ti = 8745;
MyEnumClass te = MyEnumClass::E4;
float tf = 485.042f;
double_t td = -454184.48445;
double td = -454184.48445;
bool tb=true;
SerializationContext ctx;
auto& ser = ctx.createSerializer();
@@ -116,7 +116,7 @@ TEST(FlexibleSyntax, MixDifferentSyntax) {
int ri{};
MyEnumClass re{};
float rf{};
double_t rd{};
double rd{};
bool rb{};
auto& des = ctx.createDeserializer();
des.archive(ri, re, rf);

View File

@@ -250,4 +250,20 @@ TYPED_TEST(SerializeContainerFixedSizeCompositeTypes, CustomFunctionThatSerializ
EXPECT_THAT(res, ContainerEq(src));
}
class SerializeContainer : public ::testing::TestWithParam<size_t> {
};
TEST_P(SerializeContainer, SizeHasVariableLength) {
SerializationContext ctx{};
auto emptyFnc = [](uint8_t &) {};
std::vector<uint8_t > src(GetParam());
std::vector<uint8_t > res{};
ctx.createSerializer().container(src, std::numeric_limits<size_t>::max(), emptyFnc);
ctx.createDeserializer().container(res, std::numeric_limits<size_t>::max(), emptyFnc);
EXPECT_THAT(res.size(), Eq(src.size()));
EXPECT_THAT(ctx.getBufferSize(), Eq(ctx.containerSizeSerializedBytesCount(src.size())));
}
INSTANTIATE_TEST_CASE_P(LargeContainerSize, SerializeContainer, ::testing::Values(0x01, 0x80, 0x4000));

View File

@@ -26,6 +26,19 @@
using testing::Eq;
template <typename ... Args>
struct ConfigWithContext: bitsery::DefaultConfig {
using InternalContext = std::tuple<Args...>;
};
template <typename Context, typename ... Args>
using SerializerConfigWithContext = bitsery::BasicSerializer<
bitsery::AdapterWriter<bitsery::OutputBufferAdapter<Buffer>, ConfigWithContext<Args...>>, Context>;
template <typename Context, typename ... Args>
using DeserializerConfigWithContext = bitsery::BasicDeserializer<
bitsery::AdapterReader<bitsery::InputBufferAdapter<Buffer>, ConfigWithContext<Args...>>, Context>;
template <typename Context>
using MySerializer = bitsery::BasicSerializer<Writer, Context>;
@@ -66,4 +79,55 @@ TEST(SerializationContext, WhenContextIsNotTupleThenContextCastOverloadReturnSam
SingleTypeContext ctx{};
MySerializer<SingleTypeContext> ser1{buf, &ctx};
EXPECT_THAT(ser1.context<SingleTypeContext>(), Eq(&ctx));
}
TEST(SerializationContext, SerializerDeserializerCanHaveInternalContextViaConfig) {
Buffer buf{};
SerializerConfigWithContext<void, float, int> ser{buf};
EXPECT_THAT(ser.context<int>(), ::testing::NotNull());
EXPECT_THAT(*ser.context<int>(), Eq(0));
*ser.context<int>() = 10;
EXPECT_THAT(*ser.context<int>(), Eq(10));
DeserializerConfigWithContext<void, char> des{InputAdapter{buf.begin(), 1}};
EXPECT_THAT(des.context<char>(), ::testing::NotNull());
EXPECT_THAT(*des.context<char>(), Eq(0));
*des.context<char>() = 10;
EXPECT_THAT(*des.context<char>(), Eq(10));
//new instance has new context
SerializerConfigWithContext<void, float, int> ser2{buf};
EXPECT_THAT(ser2.context<int>(), ::testing::NotNull());
EXPECT_THAT(*ser2.context<int>(), Eq(0));
}
TEST(SerializationContext, WhenInternalAndExternalContextIsTheSamePriorityGoesToInternalContext) {
Buffer buf{};
int externalCtx = 5;
SerializerConfigWithContext<int, float, int> ser{buf, &externalCtx};
EXPECT_THAT(ser.context<int>(), ::testing::NotNull());
EXPECT_THAT(*ser.context<int>(), Eq(0));
*ser.context<int>() = 2;
DeserializerConfigWithContext<int, int, char> des{InputAdapter{buf.begin(), 1}, &externalCtx};
EXPECT_THAT(des.context<char>(), ::testing::NotNull());
EXPECT_THAT(*des.context<char>(), Eq(0));
*des.context<int>() = 3;
EXPECT_THAT(externalCtx, Eq(5));
}
TEST(SerializationContext, ContextIfExistsReturnsNullWhenTypeDoesntExists) {
Buffer buf{};
std::tuple<double, short> extCtx1{};
SerializerConfigWithContext<std::tuple<double, short>, float, int> ser{buf, &extCtx1};
EXPECT_THAT(ser.contextOrNull<int>(), ::testing::NotNull());
EXPECT_THAT(ser.contextOrNull<char>(), ::testing::IsNull());
double extCtx2{};
DeserializerConfigWithContext<double, int, char> des{InputAdapter{buf.begin(), 1}, &extCtx2};
EXPECT_THAT(des.contextOrNull<double>(), ::testing::NotNull());
EXPECT_THAT(des.contextOrNull<float>(), ::testing::IsNull());
}

View File

@@ -0,0 +1,355 @@
//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 <gmock/gmock.h>
#include "serialization_test_utils.h"
#include <bitsery/ext/inheritance.h>
using bitsery::ext::BaseClass;
using bitsery::ext::VirtualBaseClass;
struct ConfigWithInheritanceCtx:bitsery::DefaultConfig {
using InternalContext = std::tuple<bitsery::ext::InheritanceContext>;
};
using SerContext = BasicSerializationContext<ConfigWithInheritanceCtx, void>;
using testing::Eq;
/*
* base class
*/
struct Base {
uint8_t x{};
virtual ~Base() = default;
};
template <typename S>
void serialize(S& s, Base& o) {
s.value1b(o.x);
}
/*
* non virtual inheritance from base
*/
struct Derive1NonVirtually:Base {
uint8_t y1{};
};
template <typename S>
void serialize(S& s, Derive1NonVirtually& o) {
s.ext(o, BaseClass<Base>{});
s.value1b(o.y1);
}
struct Derive2NonVirtually:Base {
uint8_t y2{};
};
template <typename S>
void serialize(S& s, Derive2NonVirtually& o) {
//use lambda to serialize base
s.ext(o, BaseClass<Base>{}, [&s](Base& b) {
s.object(b);
});
s.value1b(o.y2);
}
struct MultipleInheritanceNonVirtualBase: Derive1NonVirtually, Derive2NonVirtually {
uint8_t z{};
};
template <typename S>
void serialize(S& s, MultipleInheritanceNonVirtualBase& o) {
s.ext(o, BaseClass<Derive1NonVirtually>{});
s.ext(o, BaseClass<Derive2NonVirtually>{});
s.value1b(o.z);
}
/*
* virtual inheritance from base
*/
struct Derive1Virtually:virtual Base {
uint8_t y1{};
};
template <typename S>
void serialize(S& s, Derive1Virtually& o) {
s.ext(o, VirtualBaseClass<Base>{});
s.value1b(o.y1);
}
struct Derive2Virtually:virtual Base {
uint8_t y2{};
};
template <typename S>
void serialize(S& s, Derive2Virtually& o) {
s.ext(o, VirtualBaseClass<Base>{});
s.value1b(o.y2);
}
struct MultipleInheritanceVirtualBase: Derive1Virtually, Derive2Virtually {
uint8_t z{};
MultipleInheritanceVirtualBase() = default;
MultipleInheritanceVirtualBase(uint8_t x_, uint8_t y1_, uint8_t y2_, uint8_t z_) {
x = x_;
y1 = y1_;
y2 = y2_;
z = z_;
}
template <typename S>
void serialize(S& s) {
s.ext(*this, BaseClass<Derive1Virtually>{});
s.ext(*this, BaseClass<Derive2Virtually>{});
s.value1b(z);
}
};
bool operator == (const MultipleInheritanceVirtualBase& lhs, const MultipleInheritanceVirtualBase& rhs) {
return std::tie(lhs.x, lhs.y1, lhs.y2, lhs.z) == std::tie(rhs.x, rhs.y1, rhs.y2, rhs.z);
}
TEST(SerializeExtensionInheritance, BaseClass) {
Derive1NonVirtually d1{};
d1.x = 187;
d1.y1 = 74;
Derive1NonVirtually rd1{};
SerContext ctx{};
ctx.createSerializer().object(d1);
ctx.createDeserializer().object(rd1);
EXPECT_THAT(rd1.x, Eq(d1.x));
EXPECT_THAT(rd1.y1, Eq(d1.y1));
EXPECT_THAT(ctx.getBufferSize(), Eq(2));
}
TEST(SerializeExtensionInheritance, VirtualBaseClass) {
Derive1Virtually d1{};
d1.x = 15;
d1.y1 = 87;
Derive1Virtually rd1{};
SerContext ctx{};
ctx.createSerializer().object(d1);
ctx.createDeserializer().object(rd1);
EXPECT_THAT(rd1.x, Eq(d1.x));
EXPECT_THAT(rd1.y1, Eq(d1.y1));
EXPECT_THAT(ctx.getBufferSize(), Eq(2));
}
TEST(SerializeExtensionInheritance, MultipleBasesWithoutVirtualInheritance) {
MultipleInheritanceNonVirtualBase md{};
//x is ambiguous because we don't derive virtually
static_cast<Derive1NonVirtually&>(md).x = 1;
static_cast<Derive2NonVirtually&>(md).x = 2;
md.y1 = 4;
md.z = 5;
md.y2 = 6;
MultipleInheritanceNonVirtualBase res{};
SerContext ctx{};
ctx.createSerializer().object(md);
ctx.createDeserializer().object(res);
EXPECT_THAT(static_cast<Derive1NonVirtually&>(res).x, Eq(static_cast<Derive1NonVirtually&>(md).x));
EXPECT_THAT(static_cast<Derive2NonVirtually&>(res).x, Eq(static_cast<Derive2NonVirtually&>(md).x));
EXPECT_THAT(res.y1, Eq(md.y1));
EXPECT_THAT(res.y2, Eq(md.y2));
EXPECT_THAT(res.z, Eq(md.z));
EXPECT_THAT(ctx.getBufferSize(), Eq(5)); //5 because two bases
}
TEST(SerializeExtensionInheritance, WhenNoVirtualInheritanceExistsThenInheritanceContextIsNotRequired) {
MultipleInheritanceNonVirtualBase md{};
//x is ambiguous because we don't derive virtually
static_cast<Derive1NonVirtually&>(md).x = 1;
static_cast<Derive2NonVirtually&>(md).x = 2;
md.y1 = 4;
md.z = 5;
md.y2 = 6;
MultipleInheritanceNonVirtualBase res{};
//without InheritanceContext
SerializationContext ctx{};
ctx.createSerializer().object(md);
ctx.createDeserializer().object(res);
EXPECT_THAT(static_cast<Derive1NonVirtually&>(res).x, Eq(static_cast<Derive1NonVirtually&>(md).x));
EXPECT_THAT(static_cast<Derive2NonVirtually&>(res).x, Eq(static_cast<Derive2NonVirtually&>(md).x));
EXPECT_THAT(res.y1, Eq(md.y1));
EXPECT_THAT(res.y2, Eq(md.y2));
EXPECT_THAT(res.z, Eq(md.z));
EXPECT_THAT(ctx.getBufferSize(), Eq(5)); //5 because two bases
}
TEST(SerializeExtensionInheritance, MultipleBasesWithVirtualInheritance) {
MultipleInheritanceVirtualBase md{3,7,5,15};
MultipleInheritanceVirtualBase res{};
SerContext ctx{};
ctx.createSerializer().object(md);
ctx.createDeserializer().object(res);
EXPECT_THAT(res, Eq(md));
EXPECT_THAT(ctx.getBufferSize(), Eq(4)); //4 because virtual base
}
TEST(SerializeExtensionInheritance, MultipleBasesWithVirtualInheritanceMultipleObjects) {
std::vector<MultipleInheritanceVirtualBase> data;
data.emplace_back(4,8,7,9);
data.emplace_back(1,2,3,4);
data.emplace_back(8,7,15,97);
data.emplace_back(54,132,45,84);
data.emplace_back(27,85,41,2);
std::vector<MultipleInheritanceVirtualBase> res{};
SerContext ctx{};
ctx.createSerializer().container(data, 10);
ctx.createDeserializer().container(res, 10);
EXPECT_THAT(res, ::testing::ContainerEq(data));
EXPECT_THAT(ctx.getBufferSize(), Eq(1 + 4 * data.size())); //1 container size + 4 because virtual base * elements
}
//
class BasePrivateSerialize {
public:
explicit BasePrivateSerialize(uint8_t v):_v{v} {}
uint8_t getX() const { return _v; }
private:
uint8_t _v;
friend bitsery::Access;
template <typename S>
void serialize(S& s) {
s.value1b(_v);
}
};
class DerivedPrivateBase: public BasePrivateSerialize {
public:
explicit DerivedPrivateBase(uint8_t v) : BasePrivateSerialize(v) {}
uint8_t z{};
};
template <typename S>
void serialize(S& s, DerivedPrivateBase& o) {
//use lambda for base serialization
s.ext(o, BaseClass<BasePrivateSerialize>{}, [&s](BasePrivateSerialize& b) {
s.object(b);
});
s.value1b(o.z);
}
struct BaseNonMemberSerialize {
uint8_t x{};
};
template <typename S>
void serialize(S& s, BaseNonMemberSerialize& o) {
s.value1b(o.x);
}
struct DerivedMemberSerialize: public BaseNonMemberSerialize {
uint8_t z{};
template <typename S>
void serialize(S& s) {
s.ext(*this, BaseClass<BaseNonMemberSerialize>{});
s.value1b(z);
}
};
//explicitly select serialize functions, for types that has ambiguous serialize functions
namespace bitsery {
template <>
struct SelectSerializeFnc<DerivedPrivateBase>:UseNonMemberFnc {};
template <>
struct SelectSerializeFnc<DerivedMemberSerialize>:UseMemberFnc {};
}
TEST(SerializeExtensionInheritance, WhenDerivedClassHasAmbiguousSerializeFunctionThenExplicitlySelectSpecialization) {
DerivedPrivateBase data1{43};
data1.z = 87;
DerivedMemberSerialize data2{};
data2.x = 71;
data2.z = 22;
DerivedPrivateBase res1{0};
DerivedMemberSerialize res2{};
SerContext ctx{};
ctx.createSerializer().object(data1);
ctx.createSerializer().object(data2);
ctx.createDeserializer().object(res1);
ctx.createDeserializer().object(res2);
EXPECT_THAT(res1.getX(), Eq(data1.getX()));
EXPECT_THAT(res1.z, Eq(data1.z));
EXPECT_THAT(res2.x, Eq(data2.x));
EXPECT_THAT(res2.z, Eq(data2.z));
}
struct AbstractBase {
uint8_t x{};
virtual void exec() = 0;
virtual ~AbstractBase() = default;
template <typename S>
void serialize(S& s) {
s.value1b(x);
}
};
struct ImplementedBase:AbstractBase {
uint8_t y{};
void exec() override {}
template <typename S>
void serialize(S& s) {
s.ext(*this, BaseClass<AbstractBase>{});
s.value1b(y);
}
};
TEST(SerializeExtensionInheritance, CanSerializeAbstractClass) {
ImplementedBase data{};
data.x = 4;
data.y = 2;
data.exec();
ImplementedBase res{};
SerContext ctx{};
ctx.createSerializer().object(data);
ctx.createDeserializer().object(res);
EXPECT_THAT(res.x, Eq(data.x));
EXPECT_THAT(res.y, Eq(data.y));
}

View File

@@ -28,6 +28,7 @@ using bitsery::ext::PointerOwner;
using bitsery::ext::PointerObserver;
using bitsery::ext::ReferencedByPointer;
using bitsery::ext::PointerLinkingContext;
using bitsery::ext::PointerType;
using testing::Eq;
@@ -91,9 +92,9 @@ TEST(SerializeExtensionPointer, PointerLinkingContextAcceptsMultipleSharedOwners
MyStruct1* sharedPtr = &data;
//linking context
PointerLinkingContext plctx1{};
EXPECT_THAT(plctx1.createId(sharedPtr, bitsery::ext::details_pointer::PointerOwnershipType::Shared), Eq(1));
EXPECT_THAT(plctx1.createId(sharedPtr, bitsery::ext::details_pointer::PointerOwnershipType::Shared), Eq(1));
EXPECT_THAT(plctx1.createId(sharedPtr, bitsery::ext::details_pointer::PointerOwnershipType::Shared), Eq(1));
EXPECT_THAT(plctx1.getInfoByPtr(sharedPtr, bitsery::ext::PointerOwnershipType::Shared).id, Eq(1));
EXPECT_THAT(plctx1.getInfoByPtr(sharedPtr, bitsery::ext::PointerOwnershipType::Shared).id, Eq(1));
EXPECT_THAT(plctx1.getInfoByPtr(sharedPtr, bitsery::ext::PointerOwnershipType::Shared).id, Eq(1));
}
TEST_F(SerializeExtensionPointerSerialization, WhenPointersAreNullThenIsValid) {
@@ -110,13 +111,13 @@ TEST_F(SerializeExtensionPointerSerialization, WhenPointersAreNullThenIsValid) {
}
#ifndef NDEBUG
TEST(SerializeExtensionPointer, WhenPointerLinkingContextIsNullThenAssert) {
MyStruct1* data = nullptr;
TEST(SerializeExtensionPointer, WhenPointerLinkingContextIsNullAndPointerIsNotNullThenAssert) {
MyStruct1 tmp;
MyStruct1* data = &tmp;
//linking context
PointerLinkingContext plctx1{};
SerContext sctx1;
EXPECT_DEATH(sctx1.createSerializer(nullptr).ext(data, PointerOwner{}), "");
EXPECT_DEATH(sctx1.createDeserializer(nullptr).ext(data, PointerObserver{}), "");
}
TEST_F(SerializeExtensionPointerSerialization, WhenPointerOwnerIsNotUniqueThenAssert) {
@@ -145,6 +146,12 @@ TEST_F(SerializeExtensionPointerSerialization, WhenRererencedByPointerIsSameAsPo
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) {
@@ -277,7 +284,7 @@ TEST_F(SerializeExtensionPointerDeserialization, ReferencedByPointer) {
EXPECT_THAT(r3, Eq(d3));
}
TEST_F(SerializeExtensionPointerDeserialization, WhenReferencedByPointerReadsZeroPointerIdThenInvalidPointerError) {
TEST_F(SerializeExtensionPointerDeserialization, WhenReferencedByPointerReadsNullPointerThenInvalidPointerError) {
auto& ser = createSerializer();
bitsery::details::writeSize(*sctx1.bw, 0u);
ser.ext2b(d1, ReferencedByPointer{});
@@ -286,6 +293,18 @@ TEST_F(SerializeExtensionPointerDeserialization, WhenReferencedByPointerReadsZer
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{});

View File

@@ -0,0 +1,249 @@
//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 <gmock/gmock.h>
#include "serialization_test_utils.h"
#include <bitsery/ext/pointer.h>
using bitsery::ext::BaseClass;
using bitsery::ext::VirtualBaseClass;
using bitsery::ext::PointerOwner;
using bitsery::ext::PointerObserver;
using bitsery::ext::ReferencedByPointer;
using bitsery::ext::PointerLinkingContext;
using bitsery::ext::PointerType;
using testing::Eq;
using TContext = std::tuple<PointerLinkingContext, bitsery::ext::InheritanceContext>;
using SerContext = BasicSerializationContext<bitsery::DefaultConfig, TContext>;
/*
* base class
*/
struct Base {
uint8_t x{};
virtual ~Base() = default;
};
template <typename S>
void serialize(S& s, Base& o) {
s.value1b(o.x);
}
struct Derived1:virtual Base {
uint8_t y1{};
};
template <typename S>
void serialize(S& s, Derived1& o) {
s.ext(o, VirtualBaseClass<Base>{});
s.value1b(o.y1);
}
struct Derived2:virtual Base {
uint8_t y2{};
};
template <typename S>
void serialize(S& s, Derived2& o) {
s.ext(o, VirtualBaseClass<Base>{});
s.value1b(o.y2);
}
struct MultipleVirtualInheritance: Derived1, Derived2 {
int8_t z{};
MultipleVirtualInheritance() = default;
MultipleVirtualInheritance(uint8_t x_, uint8_t y1_, uint8_t y2_, uint8_t z_) {
x = x_;
y1 = y1_;
y2 = y2_;
z = z_;
}
template <typename S>
void serialize(S& s) {
s.ext(*this, BaseClass<Derived1>{});
s.ext(*this, BaseClass<Derived2>{});
s.value1b(z);
}
};
//define PolymorphicBase relationships for runtime polymorphism
namespace bitsery {
namespace ext {
template <>
struct PolymorphicBaseClass<Base>: DerivedClasses<Derived1, Derived2> {};
template <>
struct PolymorphicBaseClass<Derived1>: DerivedClasses<MultipleVirtualInheritance> {};
template <>
struct PolymorphicBaseClass<Derived2>: DerivedClasses<MultipleVirtualInheritance> {};
}
}
class SerializeExtensionPointerPolymorphicTypes: public testing::Test {
public:
TContext plctx1{};
SerContext sctx1{};
typename SerContext::TSerializer& createSerializer() {
return sctx1.createSerializer(&plctx1);
}
typename SerContext::TDeserializer& createDeserializer() {
return sctx1.createDeserializer(&plctx1);
}
bool isPointerContextValid() {
return std::get<0>(plctx1).isValid();
}
virtual void TearDown() override {
EXPECT_TRUE(isPointerContextValid());
}
};
TEST_F(SerializeExtensionPointerPolymorphicTypes, Data0Result0) {
Base* baseData = nullptr;
createSerializer().ext(baseData, PointerOwner{});
Base* baseRes = nullptr;
createDeserializer().ext(baseRes, PointerOwner{});
EXPECT_THAT(baseRes, ::testing::IsNull());
EXPECT_THAT(baseData, ::testing::IsNull());
}
TEST_F(SerializeExtensionPointerPolymorphicTypes, Data0Result1) {
Base* baseData = nullptr;
createSerializer().ext(baseData, PointerOwner{});
Base* baseRes = new Derived1{};
createDeserializer().ext(baseRes, PointerOwner{});
EXPECT_THAT(baseRes, ::testing::IsNull());
EXPECT_THAT(baseData, ::testing::IsNull());
}
TEST_F(SerializeExtensionPointerPolymorphicTypes, Data1Result0) {
Derived1 d1{};
d1.x = 3;
d1.y1 = 78;
Base* baseData = &d1;
createSerializer().ext(baseData, PointerOwner{});
Base* baseRes = nullptr;
createDeserializer().ext(baseRes, PointerOwner{});
auto* data = dynamic_cast<Derived1*>(baseData);
auto* res = dynamic_cast<Derived1*>(baseRes);
EXPECT_THAT(baseRes, ::testing::NotNull());
EXPECT_THAT(data, ::testing::NotNull());
EXPECT_THAT(res, ::testing::NotNull());
EXPECT_THAT(res->x, Eq(data->x));
EXPECT_THAT(res->y1, Eq(data->y1));
delete baseRes;
}
TEST_F(SerializeExtensionPointerPolymorphicTypes, Data1Result1) {
Derived1 d1{};
d1.x = 3;
d1.y1 = 78;
Base* baseData = &d1;
createSerializer().ext(baseData, PointerOwner{});
Base* baseRes = &d1;
createDeserializer().ext(baseRes, PointerOwner{});
auto* data = dynamic_cast<Derived1*>(baseData);
auto* res = dynamic_cast<Derived1*>(baseRes);
EXPECT_THAT(baseRes, ::testing::NotNull());
EXPECT_THAT(data, ::testing::NotNull());
EXPECT_THAT(res, ::testing::NotNull());
EXPECT_THAT(res->x, Eq(data->x));
EXPECT_THAT(res->y1, Eq(data->y1));
}
TEST_F(SerializeExtensionPointerPolymorphicTypes, ComplexTypeData1Result0) {
MultipleVirtualInheritance md1{};
md1.x = 3;
md1.y1 = 78;
md1.y2 = 14;
md1.z = -33;
Base* baseData = &md1;
createSerializer().ext(baseData, PointerOwner{});
Base* baseRes = nullptr;
createDeserializer().ext(baseRes, PointerOwner{});
auto* data = dynamic_cast<MultipleVirtualInheritance*>(baseData);
auto* res = dynamic_cast<MultipleVirtualInheritance*>(baseRes);
EXPECT_THAT(baseRes, ::testing::NotNull());
EXPECT_THAT(data, ::testing::NotNull());
EXPECT_THAT(res, ::testing::NotNull());
EXPECT_THAT(res->x, Eq(data->x));
EXPECT_THAT(res->y1, Eq(data->y1));
EXPECT_THAT(res->y2, Eq(data->y2));
EXPECT_THAT(res->z, Eq(data->z));
delete baseRes;
}
TEST_F(SerializeExtensionPointerPolymorphicTypes, WhenResultIsDifferentTypeThenRecreate) {
MultipleVirtualInheritance md1{};
md1.x = 3;
md1.y1 = 78;
md1.y2 = 14;
md1.z = -33;
Base* baseData = &md1;
createSerializer().ext(baseData, PointerOwner{});
Base* baseRes = new Derived1{};
EXPECT_THAT(dynamic_cast<MultipleVirtualInheritance*>(baseRes), ::testing::IsNull());
createDeserializer().ext(baseRes, PointerOwner{});
EXPECT_THAT(dynamic_cast<MultipleVirtualInheritance*>(baseRes), ::testing::NotNull());
delete baseRes;
}
//struct UnknownType:Base {
//};
//
//template <typename S>
//void serialize(S& s, UnknownType& o) {
// s.ext(o, VirtualBaseClass<Base>{});
//}
// todo reimplement whole polymorphism thing, because with current solution this test fails
//TEST(SerializeExtensionPointerPolymorphicTypesErrors, WhenSerializingUnknownTypeThenAssert) {
// TContext plctx1{};
// SerContext sctx1{};
// UnknownType obj{};
// UnknownType* unknownPtr = &obj;
// EXPECT_DEATH(sctx1.createSerializer(&plctx1).ext(unknownPtr, PointerOwner{}), "");
//}

View File

@@ -29,7 +29,7 @@ using bitsery::details::RangeSpec;
using bitsery::ext::BitsConstraint;
using bitsery::ext::ValueRange;
#if __cplusplus > 201402L
#if __cplusplus > 201103L
TEST(SerializeExtensionValueRange, RequiredBitsIsConstexpr) {
constexpr RangeSpec<int> r1{0, 31};