#include "oxenmq/bt_serialize.h" #include "oxenmq/bt_producer.h" #include "common.h" #include #include #include TEST_CASE("bt basic value serialization", "[bt][serialization]") { int x = 42; std::string x_ = bt_serialize(x); REQUIRE( bt_serialize(x) == "i42e" ); int64_t ibig = -8'000'000'000'000'000'000LL; uint64_t ubig = 10'000'000'000'000'000'000ULL; REQUIRE( bt_serialize(ibig) == "i-8000000000000000000e" ); REQUIRE( bt_serialize(std::numeric_limits::min()) == "i-9223372036854775808e" ); REQUIRE( bt_serialize(ubig) == "i10000000000000000000e" ); REQUIRE( bt_serialize(std::numeric_limits::max()) == "i18446744073709551615e" ); std::unordered_map m; m["hi"] = 123; m["omg"] = -7890; m["bye"] = 456; m["zap"] = 0; // bt values are always sorted: REQUIRE( bt_serialize(m) == "d3:byei456e2:hii123e3:omgi-7890e3:zapi0ee" ); // Dict-like list serializes as a dict (and get sorted, as above) std::list> d{{ {"c", "x"}, {"a", "z"}, {"b", "y"}, }}; REQUIRE( bt_serialize(d) == "d1:a1:z1:b1:y1:c1:xe" ); std::vector v{{"a", "", "\x00"s, "\x00\x00\x00goo"s}}; REQUIRE( bt_serialize(v) == "l1:a0:1:\0006:\x00\x00\x00gooe"sv ); std::array v2 = {"a"sv, ""sv, "\x00"sv, "\x00\x00\x00goo"sv}; REQUIRE( bt_serialize(v2) == "l1:a0:1:\0006:\x00\x00\x00gooe"sv ); } TEST_CASE("bt nested value serialization", "[bt][serialization]") { std::unordered_map>>> x{{ {"foo", {{{"a", {1,2,3}}, {"b", {}}}, {{"c", {4,-5}}}}}, {"bar", {}} }}; REQUIRE( bt_serialize(x) == "d3:barle3:foold1:ali1ei2ei3ee1:bleed1:cli-5ei4eeeee" ); } TEST_CASE("bt basic value deserialization", "[bt][deserialization]") { REQUIRE( bt_deserialize("i42e") == 42 ); int64_t ibig = -8'000'000'000'000'000'000LL; uint64_t ubig = 10'000'000'000'000'000'000ULL; REQUIRE( bt_deserialize("i-8000000000000000000e") == ibig ); REQUIRE( bt_deserialize("i10000000000000000000e") == ubig ); REQUIRE( bt_deserialize("i-9223372036854775808e") == std::numeric_limits::min() ); REQUIRE( bt_deserialize("i18446744073709551615e") == std::numeric_limits::max() ); REQUIRE( bt_deserialize("i4294967295e") == std::numeric_limits::max() ); REQUIRE_THROWS( bt_deserialize("i-9223372036854775809e") ); REQUIRE_THROWS( bt_deserialize("i-1e") ); REQUIRE_THROWS( bt_deserialize("i4294967296e") ); std::unordered_map m; m["hi"] = 123; m["omg"] = -7890; m["bye"] = 456; m["zap"] = 0; // bt values are always sorted: REQUIRE( bt_deserialize>("d3:byei456e2:hii123e3:omgi-7890e3:zapi0ee") == m ); // Dict-like list can be used for deserialization std::list> d{{ {"a", "z"}, {"b", "y"}, {"c", "x"}, }}; REQUIRE( bt_deserialize>>("d1:a1:z1:b1:y1:c1:xe") == d ); std::vector v{{"a", "", "\x00"s, "\x00\x00\x00goo"s}}; REQUIRE( bt_deserialize>("l1:a0:1:\0006:\x00\x00\x00gooe"sv) == v ); std::vector v2 = {"a"sv, ""sv, "\x00"sv, "\x00\x00\x00goo"sv}; REQUIRE( bt_deserialize("l1:a0:1:\0006:\x00\x00\x00gooe"sv) == v2 ); } TEST_CASE("bt_value serialization", "[bt][serialization][bt_value]") { bt_value dna{42}; std::string x_ = bt_serialize(dna); REQUIRE( bt_serialize(dna) == "i42e" ); bt_value foo{"foo"}; REQUIRE( bt_serialize(foo) == "3:foo" ); bt_value ibig{-8'000'000'000'000'000'000LL}; bt_value ubig{10'000'000'000'000'000'000ULL}; int16_t ismall = -123; uint16_t usmall = 123; bt_dict nums{ {"a", 0}, {"b", -8'000'000'000'000'000'000LL}, {"c", 10'000'000'000'000'000'000ULL}, {"d", ismall}, {"e", usmall}, }; REQUIRE( bt_serialize(ibig) == "i-8000000000000000000e" ); REQUIRE( bt_serialize(ubig) == "i10000000000000000000e" ); REQUIRE( bt_serialize(nums) == "d1:ai0e1:bi-8000000000000000000e1:ci10000000000000000000e1:di-123e1:ei123ee" ); // Same as nested test, above, but with bt_* types bt_dict x{{ {"foo", bt_list{{bt_dict{{ {"a", bt_list{{1,2,3}}}, {"b", bt_list{}}}}, bt_dict{{{"c", bt_list{{-5, 4}}}}}}}}, {"bar", bt_list{}} }}; REQUIRE( bt_serialize(x) == "d3:barle3:foold1:ali1ei2ei3ee1:bleed1:cli-5ei4eeeee" ); std::vector v{{"a", "", "\x00"s, "\x00\x00\x00goo"s}}; REQUIRE( bt_serialize(v) == "l1:a0:1:\0006:\x00\x00\x00gooe"sv ); std::array v2 = {"a"sv, ""sv, "\x00"sv, "\x00\x00\x00goo"sv}; REQUIRE( bt_serialize(v2) == "l1:a0:1:\0006:\x00\x00\x00gooe"sv ); } TEST_CASE("bt_value deserialization", "[bt][deserialization][bt_value]") { auto dna1 = bt_deserialize("i42e"); auto dna2 = bt_deserialize("i-42e"); REQUIRE( var::get(dna1) == 42 ); REQUIRE( var::get(dna2) == -42 ); REQUIRE_THROWS( var::get(dna1) ); REQUIRE_THROWS( var::get(dna2) ); REQUIRE( oxenmq::get_int(dna1) == 42 ); REQUIRE( oxenmq::get_int(dna2) == -42 ); REQUIRE( oxenmq::get_int(dna1) == 42 ); REQUIRE_THROWS( oxenmq::get_int(dna2) ); bt_value x = bt_deserialize("d3:barle3:foold1:ali1ei2ei3ee1:bleed1:cli-5ei4eeeee"); REQUIRE( std::holds_alternative(x) ); bt_dict& a = var::get(x); REQUIRE( a.count("bar") ); REQUIRE( a.count("foo") ); REQUIRE( a.size() == 2 ); bt_list& foo = var::get(a["foo"]); REQUIRE( foo.size() == 2 ); bt_dict& foo1 = var::get(foo.front()); bt_dict& foo2 = var::get(foo.back()); REQUIRE( foo1.size() == 2 ); REQUIRE( foo2.size() == 1 ); bt_list& foo1a = var::get(foo1.at("a")); bt_list& foo1b = var::get(foo1.at("b")); bt_list& foo2c = var::get(foo2.at("c")); std::list foo1a_vals, foo1b_vals, foo2c_vals; for (auto& v : foo1a) foo1a_vals.push_back(oxenmq::get_int(v)); for (auto& v : foo1b) foo1b_vals.push_back(oxenmq::get_int(v)); for (auto& v : foo2c) foo2c_vals.push_back(oxenmq::get_int(v)); REQUIRE( foo1a_vals == std::list{{1,2,3}} ); REQUIRE( foo1b_vals == std::list{} ); REQUIRE( foo2c_vals == std::list{{-5, 4}} ); REQUIRE( var::get(a.at("bar")).empty() ); } TEST_CASE("bt tuple serialization", "[bt][tuple][serialization]") { // Deserializing directly into a tuple: std::tuple> x{42, "hi", {{1,2,3,4,5}}}; REQUIRE( bt_serialize(x) == "li42e2:hili1ei2ei3ei4ei5eee" ); using Y = std::tuple>; REQUIRE( bt_deserialize("l5:hello3:omgd1:ai1e1:bi2eee") == Y{"hello", "omg", {{"a",1}, {"b",2}}} ); using Z = std::tuple, std::pair>; Z z{{3, "abc", "def"}, {4, 5}}; REQUIRE( bt_serialize(z) == "lli3e3:abc3:defeli4ei5eee" ); REQUIRE( bt_deserialize("lli6e3:ghi3:jkleli7ei8eee") == Z{{6, "ghi", "jkl"}, {7, 8}} ); using W = std::pair>; REQUIRE( bt_serialize(W{"zzzzzzzzzz", {42, 42}}) == "l10:zzzzzzzzzzli42ei42eee" ); REQUIRE_THROWS( bt_deserialize>("li1e") ); // missing closing e REQUIRE_THROWS( bt_deserialize>("li1ei-4e") ); // missing closing e REQUIRE_THROWS( bt_deserialize>("li1ei2ee") ); // too many elements REQUIRE_THROWS( bt_deserialize>("li1ei-2e0:e") ); // too many elements REQUIRE_THROWS( bt_deserialize>("li1ee") ); // too few elements REQUIRE_THROWS( bt_deserialize>("li1ee") ); // too few elements REQUIRE_THROWS( bt_deserialize>("li1ee") ); // wrong element type REQUIRE_THROWS( bt_deserialize>("li1ei8ee") ); // wrong element type REQUIRE_THROWS( bt_deserialize>("l1:x1:xe") ); // wrong element type // Converting from a generic bt_value/bt_list: bt_value a = bt_get("l5:hello3:omgi12345ee"); using V1 = std::tuple; REQUIRE( get_tuple(a) == V1{"hello", "omg"sv, 12345} ); bt_value b = bt_get("l5:hellod1:ai1e1:bi2eee"); using V2 = std::pair; REQUIRE( get_tuple(b) == V2{"hello", {{"a",1U}, {"b",2U}}} ); bt_value c = bt_get("l5:helloi-4ed1:ai-1e1:bi-2eee"); using V3 = std::tuple; REQUIRE( get_tuple(c) == V3{"hello", -4, {{"a",-1}, {"b",-2}}} ); REQUIRE_THROWS( get_tuple(bt_get("l5:hello3:omge")) ); // too few REQUIRE_THROWS( get_tuple(bt_get("l5:hello3:omgi1ei1ee")) ); // too many REQUIRE_THROWS( get_tuple(bt_get("l5:helloi1ei1ee")) ); // wrong type // Construct a bt_value from tuples: bt_value l{std::make_tuple(3, 4, "hi"sv)}; REQUIRE( bt_serialize(l) == "li3ei4e2:hie" ); bt_list m{{1, 2, std::make_tuple(3, 4, "hi"sv), std::make_pair("foo"s, "bar"sv), -4}}; REQUIRE( bt_serialize(m) == "li1ei2eli3ei4e2:hiel3:foo3:barei-4ee" ); } TEST_CASE("bt allocation-free consumer", "[bt][dict][list][consumer]") { // Consumer deserialization: bt_list_consumer lc{"li1ei2eli3ei4e2:hiel3:foo3:barei-4ee"}; REQUIRE( lc.consume_integer() == 1 ); REQUIRE( lc.consume_integer() == 2 ); REQUIRE( lc.consume_list>() == std::make_tuple(3, 4, "hi"s) ); REQUIRE( lc.consume_list>() == std::make_pair("foo"sv, "bar"sv) ); REQUIRE( lc.consume_integer() == -4 ); bt_dict_consumer dc{"d1:Ai0e1:ali1e3:omge1:bli1ei2ei3eee"}; REQUIRE( dc.key() == "A" ); REQUIRE( dc.skip_until("a") ); REQUIRE( dc.next_list>() == std::make_pair("a"sv, std::make_pair(int8_t{1}, "omg"sv)) ); REQUIRE( dc.next_list>() == std::make_pair("b"sv, std::make_tuple(1, 2, 3)) ); } TEST_CASE("bt allocation-free producer", "[bt][dict][list][producer]") { char smallbuf[16]; bt_list_producer toosmall{smallbuf, 16}; // le, total = 2 toosmall += 42; // i42e, total = 6 toosmall += "abcdefgh"; // 8:abcdefgh, total=16 CHECK( toosmall.view() == "li42e8:abcdefghe" ); CHECK_THROWS_AS( toosmall += "", std::length_error ); char buf[1024]; bt_list_producer lp{buf, sizeof(buf)}; CHECK( lp.view() == "le" ); CHECK( (void*) lp.end() == (void*) (buf + 2) ); lp.append("abc"); CHECK( lp.view() == "l3:abce" ); lp += 42; CHECK( lp.view() == "l3:abci42ee" ); std::vector randos = {{1, 17, -999}}; lp.append(randos.begin(), randos.end()); CHECK( lp.view() == "l3:abci42ei1ei17ei-999ee" ); { auto sublist = lp.append_list(); CHECK_THROWS_AS( lp.append(1), std::logic_error ); CHECK( sublist.view() == "le" ); CHECK( lp.view() == "l3:abci42ei1ei17ei-999elee" ); sublist.append(0); auto sublist2{std::move(sublist)}; sublist2 += ""; CHECK( sublist2.view() == "li0e0:e" ); CHECK( lp.view() == "l3:abci42ei1ei17ei-999eli0e0:ee" ); } lp.append_list().append_list().append_list() += "omg"s; CHECK( lp.view() == "l3:abci42ei1ei17ei-999eli0e0:elll3:omgeeee" ); { auto dict = lp.append_dict(); CHECK( dict.view() == "de" ); CHECK( lp.view() == "l3:abci42ei1ei17ei-999eli0e0:elll3:omgeeedee" ); CHECK_THROWS_AS( lp.append(1), std::logic_error ); dict.append("foo", "bar"); dict.append("g", 42); CHECK( dict.view() == "d3:foo3:bar1:gi42ee" ); CHECK( lp.view() == "l3:abci42ei1ei17ei-999eli0e0:elll3:omgeeed3:foo3:bar1:gi42eee" ); dict.append_list("h").append_dict().append_dict("a").append_list("A") += 999; CHECK( dict.view() == "d3:foo3:bar1:gi42e1:hld1:ad1:Ali999eeeeee" ); CHECK( lp.view() == "l3:abci42ei1ei17ei-999eli0e0:elll3:omgeeed3:foo3:bar1:gi42e1:hld1:ad1:Ali999eeeeeee" ); } } #ifdef OXENMQ_APPLE_TO_CHARS_WORKAROUND TEST_CASE("apple to_chars workaround test", "[bt][apple][sucks]") { char buf[20]; auto buf_view = [&](char* end) { return std::string_view{buf, static_cast(end - buf)}; }; CHECK( buf_view(oxenmq::apple_to_chars10(buf, 0)) == "0" ); CHECK( buf_view(oxenmq::apple_to_chars10(buf, 1)) == "1" ); CHECK( buf_view(oxenmq::apple_to_chars10(buf, 2)) == "2" ); CHECK( buf_view(oxenmq::apple_to_chars10(buf, 10)) == "10" ); CHECK( buf_view(oxenmq::apple_to_chars10(buf, 42)) == "42" ); CHECK( buf_view(oxenmq::apple_to_chars10(buf, 99)) == "99" ); CHECK( buf_view(oxenmq::apple_to_chars10(buf, 1234567890)) == "1234567890" ); CHECK( buf_view(oxenmq::apple_to_chars10(buf, -1)) == "-1" ); CHECK( buf_view(oxenmq::apple_to_chars10(buf, -2)) == "-2" ); CHECK( buf_view(oxenmq::apple_to_chars10(buf, -10)) == "-10" ); CHECK( buf_view(oxenmq::apple_to_chars10(buf, -99)) == "-99" ); CHECK( buf_view(oxenmq::apple_to_chars10(buf, -1234567890)) == "-1234567890" ); CHECK( buf_view(oxenmq::apple_to_chars10(buf, char{42})) == "42" ); CHECK( buf_view(oxenmq::apple_to_chars10(buf, (unsigned char){42})) == "42" ); CHECK( buf_view(oxenmq::apple_to_chars10(buf, short{42})) == "42" ); CHECK( buf_view(oxenmq::apple_to_chars10(buf, std::numeric_limits::min())) == "-128" ); CHECK( buf_view(oxenmq::apple_to_chars10(buf, std::numeric_limits::max())) == "127" ); CHECK( buf_view(oxenmq::apple_to_chars10(buf, (unsigned char){42})) == "42" ); CHECK( buf_view(oxenmq::apple_to_chars10(buf, std::numeric_limits::max())) == "18446744073709551615" ); CHECK( buf_view(oxenmq::apple_to_chars10(buf, int64_t{-1})) == "-1" ); CHECK( buf_view(oxenmq::apple_to_chars10(buf, std::numeric_limits::min())) == "-9223372036854775808" ); CHECK( buf_view(oxenmq::apple_to_chars10(buf, int64_t{-9223372036854775807})) == "-9223372036854775807" ); CHECK( buf_view(oxenmq::apple_to_chars10(buf, int64_t{9223372036854775807})) == "9223372036854775807" ); CHECK( buf_view(oxenmq::apple_to_chars10(buf, int64_t{9223372036854775806})) == "9223372036854775806" ); } #endif