From 43f26a02365db9138393b89bb2c1de8873896eed Mon Sep 17 00:00:00 2001 From: Alex Shabalin Date: Fri, 13 Sep 2024 16:58:03 +0200 Subject: [PATCH] cereal loading into containers must not append Fix and test the loading and saving behavior for all immer containers --- immer/extra/cereal/immer_array.hpp | 1 + .../extra}/cereal/immer_box.hpp | 0 immer/extra/cereal/immer_map.hpp | 2 + .../extra}/cereal/immer_set.hpp | 1 + .../extra}/cereal/immer_table.hpp | 1 + immer/extra/cereal/immer_vector.hpp | 2 + .../persist/detail/cereal/compact_map.hpp | 1 + test/extra/persist/CMakeLists.txt | 1 + .../test_circular_dependency_conversion.cpp | 6 +- test/extra/persist/test_containers_cereal.cpp | 217 ++++++++++++++++++ 10 files changed, 229 insertions(+), 3 deletions(-) rename {test/extra/persist => immer/extra}/cereal/immer_box.hpp (100%) rename {test/extra/persist => immer/extra}/cereal/immer_set.hpp (98%) rename {test/extra/persist => immer/extra}/cereal/immer_table.hpp (98%) create mode 100644 test/extra/persist/test_containers_cereal.cpp diff --git a/immer/extra/cereal/immer_array.hpp b/immer/extra/cereal/immer_array.hpp index 786da0c3..9d705ff5 100644 --- a/immer/extra/cereal/immer_array.hpp +++ b/immer/extra/cereal/immer_array.hpp @@ -10,6 +10,7 @@ void CEREAL_LOAD_FUNCTION_NAME(Archive& ar, immer::array& m) { size_type size; ar(make_size_tag(size)); + m = {}; for (auto i = size_type{}; i < size; ++i) { T x; diff --git a/test/extra/persist/cereal/immer_box.hpp b/immer/extra/cereal/immer_box.hpp similarity index 100% rename from test/extra/persist/cereal/immer_box.hpp rename to immer/extra/cereal/immer_box.hpp diff --git a/immer/extra/cereal/immer_map.hpp b/immer/extra/cereal/immer_map.hpp index 88cbc3fa..3acf1c8a 100644 --- a/immer/extra/cereal/immer_map.hpp +++ b/immer/extra/cereal/immer_map.hpp @@ -33,6 +33,7 @@ CEREAL_LOAD_FUNCTION_NAME(Archive& ar, immer::map& m) { size_type size; ar(make_size_tag(size)); + m = {}; for (auto i = size_type{}; i < size; ++i) { T x; @@ -71,6 +72,7 @@ CEREAL_LOAD_FUNCTION_NAME(Archive& ar, immer::map& m) { size_type size; ar(make_size_tag(size)); + m = {}; for (auto i = size_type{}; i < size; ++i) { K k; diff --git a/test/extra/persist/cereal/immer_set.hpp b/immer/extra/cereal/immer_set.hpp similarity index 98% rename from test/extra/persist/cereal/immer_set.hpp rename to immer/extra/cereal/immer_set.hpp index 75e7f5f3..d953cd69 100644 --- a/test/extra/persist/cereal/immer_set.hpp +++ b/immer/extra/cereal/immer_set.hpp @@ -16,6 +16,7 @@ void CEREAL_LOAD_FUNCTION_NAME(Archive& ar, immer::set& m) { size_type size; ar(make_size_tag(size)); + m = {}; for (auto i = size_type{}; i < size; ++i) { T x; diff --git a/test/extra/persist/cereal/immer_table.hpp b/immer/extra/cereal/immer_table.hpp similarity index 98% rename from test/extra/persist/cereal/immer_table.hpp rename to immer/extra/cereal/immer_table.hpp index 64320850..93fd3e3d 100644 --- a/test/extra/persist/cereal/immer_table.hpp +++ b/immer/extra/cereal/immer_table.hpp @@ -17,6 +17,7 @@ void CEREAL_LOAD_FUNCTION_NAME(Archive& ar, immer::table& m) { size_type size; ar(make_size_tag(size)); + m = {}; for (auto i = size_type{}; i < size; ++i) { T x; diff --git a/immer/extra/cereal/immer_vector.hpp b/immer/extra/cereal/immer_vector.hpp index 93bef240..9d902feb 100644 --- a/immer/extra/cereal/immer_vector.hpp +++ b/immer/extra/cereal/immer_vector.hpp @@ -15,6 +15,7 @@ void CEREAL_LOAD_FUNCTION_NAME(Archive& ar, { size_type size; ar(make_size_tag(size)); + m = {}; for (auto i = size_type{}; i < size; ++i) { T x; @@ -46,6 +47,7 @@ void CEREAL_LOAD_FUNCTION_NAME(Archive& ar, { size_type size; ar(make_size_tag(size)); + m = {}; for (auto i = size_type{}; i < size; ++i) { T x; diff --git a/immer/extra/persist/detail/cereal/compact_map.hpp b/immer/extra/persist/detail/cereal/compact_map.hpp index 6a63d4a6..b118a604 100644 --- a/immer/extra/persist/detail/cereal/compact_map.hpp +++ b/immer/extra/persist/detail/cereal/compact_map.hpp @@ -58,6 +58,7 @@ void CEREAL_LOAD_FUNCTION_NAME(Archive& ar, { size_type size; ar(make_size_tag(size)); + m.map = {}; for (auto i = size_type{}; i < size; ++i) { auto pair = immer::persist::detail::compact_pair{}; diff --git a/test/extra/persist/CMakeLists.txt b/test/extra/persist/CMakeLists.txt index 2e186ec5..d2986db3 100644 --- a/test/extra/persist/CMakeLists.txt +++ b/test/extra/persist/CMakeLists.txt @@ -21,6 +21,7 @@ add_executable( test_circular_dependency_conversion.cpp test_table_box_recursive.cpp test_for_docs.cpp + test_containers_cereal.cpp ${PROJECT_SOURCE_DIR}/immer/extra/persist/xxhash/xxhash_64.cpp) target_precompile_headers( persist-tests PRIVATE diff --git a/test/extra/persist/test_circular_dependency_conversion.cpp b/test/extra/persist/test_circular_dependency_conversion.cpp index 869f1550..09f085e1 100644 --- a/test/extra/persist/test_circular_dependency_conversion.cpp +++ b/test/extra/persist/test_circular_dependency_conversion.cpp @@ -5,9 +5,9 @@ #include "utils.hpp" -#include -#include -#include +#include +#include +#include #include #include diff --git a/test/extra/persist/test_containers_cereal.cpp b/test/extra/persist/test_containers_cereal.cpp new file mode 100644 index 00000000..66962fef --- /dev/null +++ b/test/extra/persist/test_containers_cereal.cpp @@ -0,0 +1,217 @@ +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +namespace { + +using json_t = nlohmann::json; + +struct with_id +{ + int id = 99; + std::string data = "_data_"; + + auto tie() const { return std::tie(id, data); } + + template + void serialize(Archive& ar) + { + ar(CEREAL_NVP(id), CEREAL_NVP(data)); + } + + friend bool operator==(const with_id& left, const with_id& right) + { + return left.tie() == right.tie(); + } + + friend std::ostream& operator<<(std::ostream& s, const with_id& value) + { + auto ar = cereal::JSONOutputArchive{s}; + ar(value); + return s; + } +}; + +// A struct with non-empty default constructed containers +struct non_empty_default +{ + immer::vector vec = {1, 2, 3}; + immer::flex_vector flex_vec = {4, 5, 6}; + immer::array arr = {7, 8, 9}; + immer::map map = { + {1, "one"}, + {2, "two"}, + }; + immer::map with_auto_id = { + {1, + with_id{ + .id = 1, + .data = "oner", + }}, + {2, + with_id{ + .id = 2, + .data = "twoer", + }}, + {3, + with_id{ + .id = 3, + .data = "threer", + }}, + }; + immer::map compact_map = { + {"one", 1}, + {"two", 2}, + }; + immer::box box{"default!"}; + immer::set set = {6, 7, 8, 9}; + immer::table table = { + with_id{ + .id = 1, + .data = "oner", + }, + with_id{ + .id = 2, + .data = "twoer", + }, + with_id{ + .id = 3, + .data = "threer", + }, + }; + + auto tie() const + { + return std::tie(vec, + flex_vec, + arr, + map, + with_auto_id, + compact_map, + box, + set, + table); + } + + template + void serialize(Archive& ar) + { + ar(CEREAL_NVP(vec), + CEREAL_NVP(flex_vec), + CEREAL_NVP(arr), + CEREAL_NVP(map), + CEREAL_NVP(with_auto_id), + cereal::make_nvp( + "compact_map", + immer::persist::detail::make_compact_map(compact_map)), + CEREAL_NVP(box), + CEREAL_NVP(set), + CEREAL_NVP(table)); + } + + friend bool operator==(const non_empty_default& left, + const non_empty_default& right) + { + return left.tie() == right.tie(); + } + + friend std::ostream& operator<<(std::ostream& s, + const non_empty_default& value) + { + auto ar = cereal::JSONOutputArchive{s}; + ar(value); + return s; + } +}; + +} // namespace + +TEST_CASE("Test loading struct with non-empty containers") +{ + // Create some non-default data + const auto value = non_empty_default{ + .vec = {4, 5}, + .flex_vec = {6, 7}, + .arr = {1, 2}, + .map = + { + {3, "three"}, + {4, "four"}, + }, + .with_auto_id = + { + {5, + with_id{ + .id = 5, + .data = "fiver", + }}, + {6, + with_id{ + .id = 6, + .data = "sixer", + }}, + }, + .compact_map = + { + {"three", 3}, + {"four", 4}, + }, + .box = "non-default-box", + .set = {1, 3, 5}, + .table = + { + with_id{ + .id = 5, + .data = "fiver", + }, + with_id{ + .id = 6, + .data = "sixer", + }, + }, + }; + const auto str = [&] { + auto os = std::ostringstream{}; + { + auto ar = cereal::JSONOutputArchive{os}; + ar(value); + } + return os.str(); + }(); + + const auto expected_json = json_t::parse(R"( +{ + "value0": { + "arr": [1, 2], + "box": {"value0": "non-default-box"}, + "compact_map": [["three", 3], ["four", 4]], + "flex_vec": [6, 7], + "map": [{"key": 3, "value": "three"}, {"key": 4, "value": "four"}], + "set": [1, 3, 5], + "table": [{"data": "fiver", "id": 5}, {"data": "sixer", "id": 6}], + "vec": [4, 5], + "with_auto_id": [{"data": "fiver", "id": 5}, {"data": "sixer", "id": 6}] + } +} + )"); + REQUIRE(json_t::parse(str) == expected_json); + + const auto loaded_value = [&] { + auto is = std::istringstream{str}; + auto ar = cereal::JSONInputArchive{is}; + auto r = non_empty_default{}; + ar(r); + return r; + }(); + REQUIRE(value == loaded_value); +}