// This file Copyright (C) 2013-2022 Mnemosyne LLC. // It may be used under GPLv2 (SPDX: GPL-2.0-only), GPLv3 (SPDX: GPL-3.0-only), // or any future license endorsed by Mnemosyne LLC. // License text can be found in the licenses/ folder. #include #include #include // size_t #include // int64_t #include #include #include #include #include #include #define LIBTRANSMISSION_VARIANT_MODULE #include #include // tr_rand_buffer(), tr_rand_int() #include #include #include #include "test-fixtures.h" using VariantTest = ::libtransmission::test::TransmissionTest; using namespace std::literals; namespace { template struct Overloaded : Ts... { using Ts::operator()...; }; template Overloaded(Ts...) -> Overloaded; } // namespace #ifndef _WIN32 #define STACK_SMASH_DEPTH (1 * 1000 * 1000) #else #define STACK_SMASH_DEPTH (100 * 1000) #endif TEST_F(VariantTest, getType) { auto v = tr_variant{}; v = 30; auto i = v.value_if(); ASSERT_TRUE(i); EXPECT_EQ(30, *i); auto d = v.value_if(); ASSERT_TRUE(d); EXPECT_EQ(30, static_cast(*d)); EXPECT_FALSE(v.holds_alternative()); EXPECT_FALSE(v.holds_alternative()); auto strkey = "foo"sv; v = tr_variant{ strkey }; EXPECT_FALSE(v.holds_alternative()); auto sv = v.value_if(); ASSERT_TRUE(sv); EXPECT_EQ(strkey, *sv); EXPECT_NE(std::data(strkey), std::data(*sv)); EXPECT_EQ(std::size(strkey), std::size(*sv)); strkey = "anything"sv; v = tr_variant::unmanaged_string(strkey); sv = v.value_if(); ASSERT_TRUE(sv); EXPECT_EQ(strkey, *sv); EXPECT_EQ(std::data(strkey), std::data(*sv)); // literally the same memory EXPECT_EQ(std::size(strkey), std::size(*sv)); strkey = "true"sv; v = tr_variant{ strkey }; auto b = v.value_if(); ASSERT_TRUE(b); EXPECT_TRUE(*b); sv = v.value_if(); ASSERT_TRUE(sv); EXPECT_EQ(strkey, *sv); strkey = "false"sv; v = tr_variant{ strkey }; b = v.value_if(); ASSERT_TRUE(b); EXPECT_FALSE(*b); sv = v.value_if(); ASSERT_TRUE(sv); EXPECT_EQ(strkey, *sv); } TEST_F(VariantTest, mergeStringsTakesOwnership) { auto const is_equal_string = [](std::string_view const a, std::string_view const b) { return a == b; }; auto const is_same_address = [](std::string_view const a, std::string_view const b) { return std::data(a) == std::data(b); }; // set up `src` to hold an unmanaged string auto constexpr Original = "this is the string"sv; auto const src = tr_variant::unmanaged_string(Original); auto src_sv = src.value_if().value_or(""sv); // set up `tgt` to hold another unmanaged string auto constexpr WillBeReplaced = "some other string"sv; static_assert(Original != WillBeReplaced); auto tgt = tr_variant::unmanaged_string(WillBeReplaced); auto tgt_sv = tgt.value_if().value_or(""sv); // test that `src` and `tgt` hold unmanaged strings EXPECT_TRUE(is_equal_string(Original, src_sv)); EXPECT_TRUE(is_equal_string(WillBeReplaced, tgt_sv)); EXPECT_TRUE(is_same_address(Original, src_sv)); EXPECT_TRUE(is_same_address(WillBeReplaced, tgt_sv)); tgt.merge(src); // test that `tgt` now holds its own copy of `Original`. auto const actual = tgt.value_if().value_or(""sv); EXPECT_TRUE(is_equal_string(Original, actual)); EXPECT_FALSE(is_same_address(Original, actual)); } TEST_F(VariantTest, parseInt) { static auto constexpr Benc = "i64e"sv; static auto constexpr ExpectVal = int64_t{ 64 }; auto benc = Benc; auto const value = transmission::benc::impl::ParseInt(&benc); ASSERT_TRUE(value); EXPECT_EQ(ExpectVal, *value); EXPECT_EQ(std::data(Benc) + std::size(Benc), std::data(benc)); } TEST_F(VariantTest, parseIntWithMissingEnd) { static auto constexpr Benc = "i64"sv; auto benc = Benc; EXPECT_FALSE(transmission::benc::impl::ParseInt(&benc)); EXPECT_EQ(std::data(Benc), std::data(benc)); } TEST_F(VariantTest, parseIntEmptyBuffer) { static auto constexpr Benc = ""sv; auto benc = Benc; EXPECT_FALSE(transmission::benc::impl::ParseInt(&benc)); EXPECT_EQ(std::data(Benc), std::data(benc)); } TEST_F(VariantTest, parseIntWithBadDigits) { static auto constexpr Benc = "i6z4e"sv; auto benc = Benc; EXPECT_FALSE(transmission::benc::impl::ParseInt(&benc)); EXPECT_EQ(std::data(Benc), std::data(benc)); } TEST_F(VariantTest, parseNegativeInt) { static auto constexpr Benc = "i-3e"sv; static auto constexpr Expected = int64_t{ -3 }; auto benc = Benc; auto const value = transmission::benc::impl::ParseInt(&benc); ASSERT_TRUE(value); EXPECT_EQ(Expected, *value); EXPECT_EQ(std::data(Benc) + std::size(Benc), std::data(benc)); } TEST_F(VariantTest, parseNegativeWithLeadingZero) { static auto constexpr Benc = "i-03e"sv; auto benc = Benc; EXPECT_FALSE(transmission::benc::impl::ParseInt(&benc)); EXPECT_EQ(std::data(Benc), std::data(benc)); } TEST_F(VariantTest, parseIntZero) { static auto constexpr Benc = "i0e"sv; static auto constexpr Expected = int64_t{ 0 }; auto benc = Benc; auto const value = transmission::benc::impl::ParseInt(&benc); ASSERT_TRUE(value); EXPECT_EQ(Expected, *value); EXPECT_EQ(std::data(Benc) + std::size(Benc), std::data(benc)); } TEST_F(VariantTest, parseIntWithLeadingZero) { static auto constexpr Benc = "i04e"sv; auto benc = Benc; EXPECT_FALSE(transmission::benc::impl::ParseInt(&benc)); EXPECT_EQ(std::data(Benc), std::data(benc)); } TEST_F(VariantTest, str) { using namespace transmission::benc::impl; // string len is designed to overflow auto benc = "99999999999999999999:boat"sv; auto inout = benc; auto value = ParseString(&inout); EXPECT_FALSE(value); EXPECT_EQ(benc, inout); // good string inout = benc = "4:boat"; value = ParseString(&inout); ASSERT_TRUE(value); EXPECT_EQ("boat"sv, *value); EXPECT_EQ(std::data(benc) + std::size(benc), std::data(inout)); // string goes past end of buffer inout = benc = "4:boa"sv; value = ParseString(&inout); EXPECT_FALSE(value); EXPECT_EQ(benc, inout); // empty string inout = benc = "0:"sv; value = ParseString(&inout); ASSERT_TRUE(value); EXPECT_EQ(""sv, *value); EXPECT_EQ(std::data(benc) + std::size(benc), std::data(inout)); // short string inout = benc = "3:boat"; value = ParseString(&inout); ASSERT_TRUE(value); EXPECT_EQ("boa"sv, *value); EXPECT_EQ(std::data(benc) + benc.find('t'), std::data(inout)); } TEST_F(VariantTest, parse) { auto serde = tr_variant_serde::benc(); serde.inplace(); auto benc = "i64e"sv; auto var = serde.parse(benc).value_or(tr_variant{}); auto i = var.value_if(); ASSERT_TRUE(i); EXPECT_EQ(64, *i); EXPECT_EQ(std::data(benc) + std::size(benc), serde.end()); var.clear(); benc = "li64ei32ei16ee"sv; var = serde.parse(benc).value_or(tr_variant{}); auto* l = var.get_if(); ASSERT_NE(l, nullptr); EXPECT_EQ(std::data(benc) + std::size(benc), serde.end()); ASSERT_EQ(3, std::size(*l)); i = (*l)[0].value_if(); ASSERT_TRUE(i); EXPECT_EQ(64, *i); i = (*l)[1].value_if(); ASSERT_TRUE(i); EXPECT_EQ(32, *i); i = (*l)[2].value_if(); ASSERT_TRUE(i); EXPECT_EQ(16, *i); EXPECT_EQ(benc, serde.to_string(var)); var.clear(); benc = "lllee"sv; var = serde.parse(benc).value_or(tr_variant{}); EXPECT_FALSE(var.has_value()); EXPECT_EQ(std::data(benc) + std::size(benc), serde.end()); var.clear(); benc = "le"sv; var = serde.parse(benc).value_or(tr_variant{}); EXPECT_TRUE(var.holds_alternative()); EXPECT_EQ(std::data(benc) + std::size(benc), serde.end()); EXPECT_EQ(benc, serde.to_string(var)); var.clear(); benc = "d20:"sv; var = serde.parse(benc).value_or(tr_variant{}); EXPECT_FALSE(var.has_value()); EXPECT_EQ(std::data(benc) + 1U, serde.end()); } TEST_F(VariantTest, bencParseAndReencode) { static auto constexpr Tests = std::array, 9>{ { { "llleee"sv, true }, { "d3:cow3:moo4:spam4:eggse"sv, true }, { "d4:spaml1:a1:bee"sv, true }, { "d5:greenli1ei2ei3ee4:spamd1:ai123e3:keyi214eee"sv, true }, { "d9:publisher3:bob17:publisher-webpage15:www.example.com18:publisher.location4:homee"sv, true }, { "d8:completei1e8:intervali1800e12:min intervali1800e5:peers0:e"sv, true }, { "d1:ai0e1:be"sv, false }, // odd number of children { ""sv, false }, { " "sv, false }, } }; auto serde = tr_variant_serde::benc(); serde.inplace(); for (auto const& [benc, is_good] : Tests) { auto var = serde.parse(benc); EXPECT_EQ(is_good, var.has_value()); if (var) { EXPECT_EQ(benc.data() + benc.size(), serde.end()); EXPECT_EQ(benc, serde.to_string(*var)); } } } TEST_F(VariantTest, bencSortWhenSerializing) { static auto constexpr In = "lld1:bi32e1:ai64eeee"sv; static auto constexpr ExpectedOut = "lld1:ai64e1:bi32eeee"sv; auto serde = tr_variant_serde::benc(); auto var = serde.inplace().parse(In); EXPECT_TRUE(var.has_value()); EXPECT_EQ(std::data(In) + std::size(In), serde.end()); EXPECT_EQ(ExpectedOut, serde.to_string(*var)); } TEST_F(VariantTest, bencMalformedTooManyEndings) { static auto constexpr In = "leee"sv; static auto constexpr ExpectedOut = "le"sv; auto serde = tr_variant_serde::benc(); auto var = serde.inplace().parse(In); EXPECT_TRUE(var.has_value()); EXPECT_EQ(std::data(In) + std::size(ExpectedOut), serde.end()); EXPECT_EQ(ExpectedOut, serde.to_string(*var)); } TEST_F(VariantTest, bencMalformedNoEnding) { static auto constexpr In = "l1:a1:b1:c"sv; auto serde = tr_variant_serde::benc(); auto const var = serde.inplace().parse(In); EXPECT_FALSE(var.has_value()); } TEST_F(VariantTest, bencMalformedIncompleteString) { static auto constexpr In = "1:"sv; auto serde = tr_variant_serde::benc(); auto const var = serde.inplace().parse(In); EXPECT_FALSE(var.has_value()); } TEST_F(VariantTest, bencToJson) { static auto constexpr Tests = std::array, 5>{ { { "i6e"sv, "6"sv }, { "d5:helloi1e5:worldi2ee"sv, R"({"hello":1,"world":2})"sv }, { "d5:helloi1e5:worldi2e3:fooli1ei2ei3eee"sv, R"({"foo":[1,2,3],"hello":1,"world":2})"sv }, { "d5:helloi1e5:worldi2e3:fooli1ei2ei3ed1:ai0eeee"sv, R"({"foo":[1,2,3,{"a":0}],"hello":1,"world":2})"sv }, { "d4:argsd6:statusle7:status2lee6:result7:successe"sv, R"({"args":{"status":[],"status2":[]},"result":"success"})"sv } } }; auto benc_serde = tr_variant_serde::benc(); auto json_serde = tr_variant_serde::json(); benc_serde.inplace(); json_serde.compact(); for (auto const& [benc, expected] : Tests) { auto top = benc_serde.parse(benc).value_or(tr_variant{}); EXPECT_EQ(expected, json_serde.to_string(top)); } } TEST_F(VariantTest, mergeMapsCreatesCombinedMap) { auto serde = tr_variant_serde::json(); serde.compact(); serde.inplace(); auto src = serde.parse(R"({"src_key":123})"sv).value_or(tr_variant{}); auto tgt = serde.parse(R"({"tgt_key":456})"sv).value_or(tr_variant{}); tgt.merge(src); EXPECT_EQ(R"({"src_key":123,"tgt_key":456})"sv, serde.to_string(tgt)); } TEST_F(VariantTest, mergeMapsOverwritesSrcMapEntries) { auto serde = tr_variant_serde::json(); serde.compact(); serde.inplace(); auto src = serde.parse(R"({"src_key": 123, "dup_key":789})"sv).value_or(tr_variant{}); auto tgt = serde.parse(R"({"tgt_key": 456, "dup_key":456})"sv).value_or(tr_variant{}); tgt.merge(src); EXPECT_EQ(R"({"dup_key":789,"src_key":123,"tgt_key":456})"sv, serde.to_string(tgt)); } TEST_F(VariantTest, variantConstructor) { auto const var_none = tr_variant{}; auto const var_null = tr_variant{ nullptr }; auto const var_true = tr_variant{ true }; auto const var_false = tr_variant{ false }; auto const var_int = tr_variant{ 123 }; auto const var_real = tr_variant{ 4.5 }; auto const var_sv = tr_variant{ "foo"sv }; auto const var_s = tr_variant{ "foo"s }; auto const var_char_ptr = tr_variant{ "foo" }; auto const var_char_nullptr = tr_variant{ static_cast(nullptr) }; EXPECT_EQ(var_none.index(), tr_variant::NoneIndex); EXPECT_EQ(var_null.index(), tr_variant::NullIndex); EXPECT_EQ(var_true.index(), tr_variant::BoolIndex); EXPECT_EQ(var_false.index(), tr_variant::BoolIndex); EXPECT_EQ(var_int.index(), tr_variant::IntIndex); EXPECT_EQ(var_real.index(), tr_variant::DoubleIndex); EXPECT_EQ(var_sv.index(), tr_variant::StringIndex); EXPECT_EQ(var_s.index(), tr_variant::StringIndex); EXPECT_EQ(var_char_ptr.index(), tr_variant::StringIndex); EXPECT_EQ(var_char_nullptr.index(), tr_variant::StringIndex); } TEST_F(VariantTest, variantAssingmentOperator) { auto var = tr_variant{}; var = nullptr; EXPECT_EQ(var.index(), tr_variant::NullIndex); var = true; EXPECT_EQ(var.index(), tr_variant::BoolIndex); var = false; EXPECT_EQ(var.index(), tr_variant::BoolIndex); var = 123; EXPECT_EQ(var.index(), tr_variant::IntIndex); var = 4.5; EXPECT_EQ(var.index(), tr_variant::DoubleIndex); var = "foo"sv; EXPECT_EQ(var.index(), tr_variant::StringIndex); var = "foo"s; EXPECT_EQ(var.index(), tr_variant::StringIndex); var = "foo"; EXPECT_EQ(var.index(), tr_variant::StringIndex); var = static_cast(nullptr); EXPECT_EQ(var.index(), tr_variant::StringIndex); } TEST_F(VariantTest, mergeOverwritesDifferingTypes) { auto const variants = std::array, 7U>{ { { tr_variant{ true }, "true" }, { tr_variant{ int64_t{ 123 } }, "123" }, { tr_variant{ 4.5 }, "4.5" }, { tr_variant{ "foo"sv }, R"("foo")"sv }, { tr_variant{ nullptr }, "null"sv }, { tr_variant::make_map(0U), "{}"sv }, { tr_variant::make_vector(), "[]"sv }, } }; auto serde = tr_variant_serde::json(); serde.compact(); serde.inplace(); for (auto const& [src, src_expected] : variants) { for (auto const& [tgt, tgt_expected] : variants) { if (&src != &tgt) { // set up `var` to be a copy of `src` auto var = src.clone(); EXPECT_EQ(src_expected, serde.to_string(var)); var.merge(tgt); // test that `var` is now a copy of `tgt` EXPECT_EQ(tgt_expected, serde.to_string(var)); } } } } TEST_F(VariantTest, stackSmashBenc) { // set up a nested list of list of lists. static int constexpr Depth = STACK_SMASH_DEPTH; std::string const in = std::string(Depth, 'l') + std::string(Depth, 'e'); // test that parsing fails without crashing auto serde = tr_variant_serde::benc(); auto var = serde.inplace().parse(in); EXPECT_FALSE(var.has_value()); EXPECT_TRUE(serde.error_); EXPECT_EQ(E2BIG, serde.error_.code()); } TEST_F(VariantTest, stackSmashJson) { auto serde = tr_variant_serde::json(); serde.inplace(); // set up a nested array of arrays of arrays. static auto constexpr Depth = STACK_SMASH_DEPTH; auto const in = std::string(Depth, '[') + std::string(Depth, ']'); // test that parsing fails without crashing auto var = serde.inplace().parse(in); EXPECT_FALSE(var.has_value()); EXPECT_TRUE(serde.error_); EXPECT_EQ(E2BIG, serde.error_.code()); } TEST_F(VariantTest, valueIfCanReadBoolsAndIntsInterchangeably) { auto const key1 = tr_quark_new("key1"sv); auto const key2 = tr_quark_new("key2"sv); auto const key3 = tr_quark_new("key3"sv); auto const key4 = tr_quark_new("key4"sv); auto top = tr_variant::make_map(4U); auto* map = top.get_if(); map->try_emplace(key1, false); map->try_emplace(key2, 0); map->try_emplace(key3, true); map->try_emplace(key4, 1); // confirm we can read both bools and ints as bools auto b = map->value_if(key1); ASSERT_TRUE(b); EXPECT_FALSE(*b); b = map->value_if(key2); ASSERT_TRUE(b); EXPECT_FALSE(*b); b = map->value_if(key3); ASSERT_TRUE(b); EXPECT_TRUE(*b); b = map->value_if(key4); ASSERT_TRUE(b); EXPECT_TRUE(*b); // confirm we can read both bools and ints as ints auto i = map->value_if(key1); ASSERT_TRUE(i); EXPECT_EQ(0, *i); i = map->value_if(key2); ASSERT_TRUE(i); EXPECT_EQ(0, *i); i = map->value_if(key3); ASSERT_TRUE(i); EXPECT_NE(0, *i); i = map->value_if(key4); ASSERT_TRUE(i); EXPECT_NE(0, *i); } TEST_F(VariantTest, dictFindType) { static auto constexpr ExpectedStr = "this-is-a-string"sv; static auto constexpr ExpectedBool = true; static auto constexpr ExpectedInt = 1234; static auto constexpr ExpectedReal = 0.3; auto const key_bool = tr_quark_new("this-is-a-bool"sv); auto const key_real = tr_quark_new("this-is-a-real"sv); auto const key_int = tr_quark_new("this-is-an-int"sv); auto const key_str = tr_quark_new("this-is-a-string"sv); auto const key_unknown = tr_quark_new("this-is-a-missing-entry"sv); // populate a dict auto top = tr_variant::make_map(4U); auto* map = top.get_if(); map->try_emplace(key_bool, ExpectedBool); map->try_emplace(key_int, ExpectedInt); map->try_emplace(key_real, ExpectedReal); map->try_emplace(key_str, ExpectedStr); // look up the keys as strings EXPECT_FALSE(map->value_if(key_bool)); EXPECT_FALSE(map->value_if(key_real)); EXPECT_FALSE(map->value_if(key_int)); auto sv = map->value_if(key_str); ASSERT_TRUE(sv); EXPECT_EQ(ExpectedStr, *sv); EXPECT_FALSE(map->value_if(key_unknown)); // look up the keys as bools EXPECT_FALSE(map->value_if(key_int)); EXPECT_FALSE(map->value_if(key_real)); EXPECT_FALSE(map->value_if(key_str)); auto b = map->value_if(key_bool); ASSERT_TRUE(b); EXPECT_EQ(ExpectedBool, b); EXPECT_FALSE(map->value_if(key_unknown)); // look up the keys as doubles EXPECT_FALSE(map->value_if(key_bool)); auto d = map->value_if(key_int); ASSERT_TRUE(d); EXPECT_EQ(static_cast(ExpectedInt), *d); EXPECT_FALSE(map->value_if(key_str)); d = map->value_if(key_real); ASSERT_TRUE(d); EXPECT_EQ(ExpectedReal, *d); // look up the keys as ints auto i = map->value_if(key_bool); ASSERT_TRUE(i); EXPECT_EQ(ExpectedBool ? 1 : 0, *i); EXPECT_FALSE(map->value_if(key_real)); EXPECT_FALSE(map->value_if(key_str)); i = map->value_if(key_int); ASSERT_TRUE(i); EXPECT_EQ(ExpectedInt, *i); } TEST_F(VariantTest, mapContains) { auto serde = tr_variant_serde::json(); serde.inplace(); serde.compact(); // set up a map with some sample entries static auto constexpr Input = R"({ "id": 42, "is_finished": true, "labels": ["a", "b"], "units": { "speed_units": ["KB/s", "MB/s", "GB/s", "TB/s"] }, "upload_ratio": 4.2, "version": "5.0" })"sv; auto top = serde.parse(Input).value_or(tr_variant{}); auto* const map = top.get_if(); ASSERT_NE(nullptr, map); // test that contains() returns true for entries that exist EXPECT_TRUE(map->contains(TR_KEY_id)); EXPECT_TRUE(map->contains(TR_KEY_is_finished)); EXPECT_TRUE(map->contains(TR_KEY_labels)); EXPECT_TRUE(map->contains(TR_KEY_units)); EXPECT_TRUE(map->contains(TR_KEY_upload_ratio)); EXPECT_TRUE(map->contains(TR_KEY_version)); // test that contains() returns false for entries that never existed EXPECT_FALSE(map->contains(TR_KEY_umask)); // test that contains() returns false for entries that were removed auto const key = TR_KEY_labels; EXPECT_TRUE(map->contains(key)); EXPECT_EQ(1U, map->erase(key)); EXPECT_FALSE(map->contains(key)); } TEST_F(VariantTest, visitConstVariant) { auto var = tr_variant::make_vector(1U); auto* vec = var.get_if(); ASSERT_NE(vec, nullptr); vec->emplace_back(int64_t{ 99 }); auto const result = std::as_const(var).visit( Overloaded{ [](tr_variant::Vector const& values) -> int64_t { EXPECT_EQ(1U, std::size(values)); return values[0].value_if().value_or(-1); }, [](auto&&) -> int64_t { ADD_FAILURE() << "unexpected alternative"; return -1; } }); EXPECT_EQ(99, result); } TEST_F(VariantTest, visitsNodesDepthFirst) { auto serde = tr_variant_serde::json(); serde.compact(); serde.inplace(); // set up a test variant to be visited static auto constexpr Input = R"({ "files": [ { "name": "file1", "size": 5, "pieces": [1, 2] }, { "name": "file2", "size": 7, "pieces": [] } ], "meta": { "active": true } })"sv; auto const var = serde.parse(Input).value_or(tr_variant{}); // set up some containers that we'll populate during `var.visit()` auto visited_counts = std::map{}; auto flattened = tr_variant::Vector{}; flattened.reserve(64U); // set up the visitor auto flatten = [&](tr_variant const& node, auto const& self) -> void { ++visited_counts[node.index()]; node.visit( [&](auto const& val) { // TODO(c++20): use std::remove_cvref_t (P0550R2) when GCC >= 9.1 using ValueType = std::decay_t; if constexpr ( std::is_same_v || // std::is_same_v || // std::is_same_v || // std::is_same_v || // std::is_same_v || // std::is_same_v || // std::is_same_v) { flattened.emplace_back(val); } else if constexpr (std::is_same_v) { for (auto const& child : val) { self(child, self); } } else if constexpr (std::is_same_v) { for (auto const& [key, child] : val) { flattened.emplace_back(tr_variant::unmanaged_string(key)); self(child, self); } } }); }; flatten(var, flatten); // test that the nodes were visited depth-first auto const actual = serde.to_string({ std::move(flattened) }); auto constexpr Expected = R"(["files","name","file1","size",5,"pieces",1,2,"name","file2","size",7,"pieces","meta","active",true])"sv; EXPECT_EQ(Expected, actual); // test that we visited the expected number of nodes. // // FIXME(ckerr): `serde.inplace()` doesn't work on JSON right now. // RapidJSON always copies strings unless given mutable JSON input. // That's why StringViewIndex is missing two counts here. auto const expected_visited_count = std::map{ { tr_variant::BoolIndex, 1U }, // { tr_variant::IntIndex, 4U }, // { tr_variant::MapIndex, 4U }, // { tr_variant::StringIndex, 2U }, // { tr_variant::VectorIndex, 3U }, // }; EXPECT_EQ(expected_visited_count, visited_counts); } TEST_F(VariantTest, variantFromBufFuzz) { auto benc_serde = tr_variant_serde::json(); auto json_serde = tr_variant_serde::json(); auto buf = std::vector{}; for (size_t i = 0; i < 100000; ++i) { buf.resize(tr_rand_int(4096U)); tr_rand_buffer(std::data(buf), std::size(buf)); (void)benc_serde.inplace().parse(buf); (void)json_serde.inplace().parse(buf); } } TEST_F(VariantTest, serdeEnd) { static auto constexpr TestsJson = std::array{ std::tuple{ R"({ "json1": 1 }{ "json2": 2 })"sv, '{', 14U }, std::tuple{ R"({ "json1": 1 })"sv, '\0', 14U }, }; static auto constexpr TestsBenc = std::array{ std::tuple{ "d5:benc1i1eed5:benc2i2ee"sv, 'd', 12U }, std::tuple{ "d5:benc1i1ee"sv, '\0', 12U }, }; for (auto [in, c, pos] : TestsJson) { auto json_serde = tr_variant_serde::json().inplace(); auto json_var = json_serde.parse(in).value_or(tr_variant{}); EXPECT_TRUE(json_var.holds_alternative()) << json_serde.error_; EXPECT_EQ(*json_serde.end(), c); EXPECT_EQ(json_serde.end() - std::data(in), pos); } for (auto [in, c, pos] : TestsBenc) { auto benc_serde = tr_variant_serde::benc().inplace(); auto benc_var = benc_serde.parse(in).value_or(tr_variant{}); EXPECT_TRUE(benc_var.holds_alternative()) << benc_serde.error_; EXPECT_EQ(*benc_serde.end(), c); EXPECT_EQ(benc_serde.end() - std::data(in), pos); } }