Skip to content

Commit

Permalink
Load once again with exceptions enabled to validate the archive's con…
Browse files Browse the repository at this point in the history
…sistency
  • Loading branch information
alex-sparus committed Apr 23, 2024
1 parent 2254d22 commit 4e7f47b
Show file tree
Hide file tree
Showing 3 changed files with 179 additions and 21 deletions.
36 changes: 21 additions & 15 deletions immer/extra/archive/json/json_with_archive.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -545,8 +545,13 @@ auto load_archives(std::istream& is,
// Reloading of the archive might trigger validation of some containers
// (hash-based, for example) because the elements actually come from
// other archives that are not yet loaded.
archives = reload_archive(is, std::move(archives));
constexpr bool ignore_archive_exceptions = true;
archives =
reload_archive(is, std::move(archives), ignore_archive_exceptions);
if (prev == archives) {
// Looks like we're done, reload one more time but do not ignore the
// exceptions, for the final validation.
archives = reload_archive(is, std::move(archives), false);
break;
}
prev = archives;
Expand All @@ -555,20 +560,21 @@ auto load_archives(std::istream& is,
return archives;
}

constexpr auto reload_archive = [](std::istream& is, auto archives) {
using Archives = std::decay_t<decltype(archives)>;
auto restore = util::istream_snapshot{is};
archives.ignore_archive_exceptions = true;
auto ar = json_immer_input_archive<Archives>{std::move(archives), is};
/**
* NOTE: Critical to clear the archives before loading into it
* again. I hit a bug when archives contained a vector and every
* load would append to it, instead of replacing the contents.
*/
archives = {};
ar(CEREAL_NVP(archives));
return archives;
};
constexpr auto reload_archive =
[](std::istream& is, auto archives, bool ignore_archive_exceptions) {
using Archives = std::decay_t<decltype(archives)>;
auto restore = util::istream_snapshot{is};
archives.ignore_archive_exceptions = ignore_archive_exceptions;
auto ar = json_immer_input_archive<Archives>{std::move(archives), is};
/**
* NOTE: Critical to clear the archives before loading into it
* again. I hit a bug when archives contained a vector and every
* load would append to it, instead of replacing the contents.
*/
archives = {};
ar(CEREAL_NVP(archives));
return archives;
};

template <typename T>
T from_json_with_archive(std::istream& is)
Expand Down
18 changes: 12 additions & 6 deletions immer/extra/archive/json/json_with_archive_auto.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,9 @@ auto to_json_with_auto_archive(const T& serializable,
auto previous = json_immer_output_archive<Archives>{os};
auto ar = json_immer_auto_output_archive<decltype(previous), WrapF>{
previous, wrap};
ar(serializable);
// value0 because that's now cereal saves the unnamed object by default,
// maybe change later.
ar(cereal::make_nvp("value0", serializable));
if constexpr (!is_archive_empty<Archives>()) {
save_archives_impl(previous, save_archive);
ar.finalize();
Expand All @@ -228,11 +230,13 @@ auto load_initial_auto_archives(std::istream& is, WrapF wrap)
}

constexpr auto reload_archive_auto = [](auto wrap) {
return [wrap](std::istream& is, auto archives) {
return [wrap](std::istream& is,
auto archives,
bool ignore_archive_exceptions) {
using Archives = std::decay_t<decltype(archives)>;
using WrapF = std::decay_t<decltype(wrap)>;
auto restore = util::istream_snapshot{is};
archives.ignore_archive_exceptions = true;
archives.ignore_archive_exceptions = ignore_archive_exceptions;
auto previous =
json_immer_input_archive<Archives>{std::move(archives), is};
auto ar = json_immer_auto_input_archive<decltype(previous), WrapF>{
Expand Down Expand Up @@ -267,9 +271,11 @@ T from_json_with_auto_archive(std::istream& is,
auto previous = json_immer_input_archive<Archives>{std::move(archives), is};
auto ar = json_immer_auto_input_archive<decltype(previous), WrapF>{previous,
wrap};
auto r = T{};
ar(r);
return r;
// value0 because that's now cereal saves the unnamed object by default,
// maybe change later.
auto value0 = T{};
ar(CEREAL_NVP(value0));
return value0;
}

template <typename T, class ArchivesTypes>
Expand Down
146 changes: 146 additions & 0 deletions test/extra/archive/test_special_archive_auto.cpp
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
#include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers_exception.hpp>
#include <catch2/matchers/catch_matchers_string.hpp>

#include "utils.hpp"

#include <immer/extra/archive/json/json_with_archive_auto.hpp>
#include <nlohmann/json.hpp>

#define DEFINE_OPERATIONS(name) \
bool operator==(const name& left, const name& right) \
Expand Down Expand Up @@ -521,6 +523,150 @@ TEST_CASE("Test table with a funny value")
REQUIRE(loaded == value);
}

TEST_CASE("Test loading broken table")
{
const auto two1 = champ_test::two_boxed{champ_test::value_two{
.key = champ_test::key{"456"},
}};
const auto t1 =
immer::table<champ_test::two_boxed,
champ_test::table_key_fn,
immer::archive::xx_hash<champ_test::key>>{two1};
const auto two2 = champ_test::two_boxed{champ_test::value_two{
.ones =
{
champ_test::value_one{
.twos_table = t1,
},
},
.key = champ_test::key{"123"},
}};

const auto value = champ_test::value_one{
.twos = {champ_test::two_boxed{champ_test::value_two{
.key = champ_test::key{"90"},
}},
champ_test::two_boxed{champ_test::value_two{
.key = champ_test::key{"91"},
}}},
.twos_table = t1.insert(two2),
};

const auto names = immer::archive::get_archives_for_types(
hana::tuple_t<champ_test::value_one,
champ_test::value_two,
champ_test::two_boxed>,
hana::make_map());

const auto [json_str, ar] =
immer::archive::to_json_with_auto_archive(value, names);
// REQUIRE(json_str == "");

constexpr auto expected_json_str = R"(
{
"value0": {"twos": 0, "twos_table": 0},
"archives": {
"two": [
{"ones": 0, "key": {"str": "90"}},
{"ones": 0, "key": {"str": "91"}},
{"ones": 0, "key": {"str": "456"}},
{"ones": 1, "key": {"str": "123"}}
],
"ones": {
"leaves": [
{"key": 1, "value": []},
{"key": 2, "value": [{"twos": 1, "twos_table": 1}]}
],
"inners": [{"key": 0, "value": {"children": [], "relaxed": false}}],
"vectors": [{"root": 0, "tail": 1}, {"root": 0, "tail": 2}]
},
"twos": {
"leaves": [
{"key": 1, "value": [{"two": 0}, {"two": 1}]},
{"key": 2, "value": []}
],
"inners": [{"key": 0, "value": {"children": [], "relaxed": false}}],
"vectors": [{"root": 0, "tail": 1}, {"root": 0, "tail": 2}]
},
"twos_table": [
{
"values": [{"two": 2}, {"two": 3}],
"children": [],
"nodemap": 0,
"datamap": 4100,
"collisions": false
},
{
"values": [{"two": 2}],
"children": [],
"nodemap": 0,
"datamap": 4096,
"collisions": false
}
]
}
})";
using json_t = nlohmann::json;
auto json = json_t::parse(expected_json_str);

// Serialized json should look like this
REQUIRE(json == json_t::parse(json_str));

SECTION("Loads correctly 1")
{
INFO(json_str);
const auto loaded =
immer::archive::from_json_with_auto_archive<champ_test::value_one>(
json_str, names);
REQUIRE(loaded == value);
}

SECTION("Loads correctly 2")
{
INFO(json.dump());
const auto loaded =
immer::archive::from_json_with_auto_archive<champ_test::value_one>(
json.dump(), names);
REQUIRE(loaded == value);
}

SECTION("Break the table that's part of the archive itself, not the loaded "
"value")
{
SECTION("box exists")
{
json["archives"]["twos_table"][1]["values"][0]["two"] = 0;
REQUIRE_THROWS_MATCHES(
immer::archive::from_json_with_auto_archive<
champ_test::value_one>(json.dump(), names),
::cereal::Exception,
MessageMatches(Catch::Matchers::ContainsSubstring(
"Couldn't find an element")));
}
SECTION("box doesn't exist")
{
json["archives"]["twos_table"][1]["values"][0]["two"] = 99;
REQUIRE_THROWS_MATCHES(
immer::archive::from_json_with_auto_archive<
champ_test::value_one>(json.dump(), names),
::cereal::Exception,
MessageMatches(Catch::Matchers::ContainsSubstring(
"Couldn't find an element")));
}
}

SECTION("Box that doesn't exist is referenced in the table for the value")
{
json["archives"]["twos_table"][0]["values"][0]["two"] = 99;
REQUIRE_THROWS_MATCHES(
immer::archive::from_json_with_auto_archive<champ_test::value_one>(
json.dump(), names),
::cereal::Exception,
MessageMatches(Catch::Matchers::ContainsSubstring(
"Container ID 99 is not found")));
}
}

namespace test_no_auto {

using immer::archive::archivable;
Expand Down

0 comments on commit 4e7f47b

Please sign in to comment.