From 98d2559becb4231be97a59bc713f577a00e123ca Mon Sep 17 00:00:00 2001 From: mrabine Date: Fri, 13 Nov 2026 16:52:40 +0100 Subject: [PATCH] Msgpack float encoding and roundtrip tests --- .claude/rules/hot-path.md | 2 +- data/include/join/pack.hpp | 7 +- data/tests/CMakeLists.txt | 13 +- data/tests/json_reader_test.cpp | 99 ++++- ...dtrip_test.cpp => json_roundtrip_test.cpp} | 34 +- data/tests/pack_reader_test.cpp | 89 +++-- data/tests/pack_roundtrip_test.cpp | 346 ++++++++++++++++++ data/tests/pack_writer_test.cpp | 51 +++ data/tests/utf8_test.cpp | 177 +++++++-- 9 files changed, 743 insertions(+), 75 deletions(-) rename data/tests/{roundtrip_test.cpp => json_roundtrip_test.cpp} (89%) create mode 100644 data/tests/pack_roundtrip_test.cpp diff --git a/.claude/rules/hot-path.md b/.claude/rules/hot-path.md index a490d736..3c077b92 100644 --- a/.claude/rules/hot-path.md +++ b/.claude/rules/hot-path.md @@ -30,4 +30,4 @@ Targets: zero allocation, zero lock, bounded latency, low jitter. Data path: pro ## Determinism and proof - Loops bounded by input or constant; recursion depth-limited. No hidden syscalls, locale or `errno` side effects. `JOIN_UNLIKELY` on error branches, `JOIN_LIKELY` on the expected path, `JOIN_ALWAYS_INLINE` only on measured inner functions. -- Claim a gain only after measuring in `release` (skill `benchmark`); never trade correctness for speed without an equivalence test (`roundtrip_test.cpp`, `parse_test.cpp`, stress tests). Report what, how, before/after. +- Claim a gain only after measuring in `release` (skill `benchmark`); never trade correctness for speed without an equivalence test (`*_roundtrip_test.cpp`, `parse_test.cpp`, stress tests). Report what, how, before/after. diff --git a/data/include/join/pack.hpp b/data/include/join/pack.hpp index 7c7d41c5..466111dd 100644 --- a/data/include/join/pack.hpp +++ b/data/include/join/pack.hpp @@ -40,13 +40,18 @@ #include #include #include +#include #include +#include // C. #include namespace join { + static_assert (std::numeric_limits::is_iec559 && std::numeric_limits::is_iec559, + "MessagePack requires IEEE 754 floating point numbers"); + /** * @brief message pack writer class. */ @@ -296,7 +301,7 @@ namespace join int setDouble (double value) { float f = static_cast (value); - if (std::isfinite (value) && static_cast (f) == value) + if (std::bit_cast (static_cast (f)) == std::bit_cast (value)) { append (0xca); pack (f); diff --git a/data/tests/CMakeLists.txt b/data/tests/CMakeLists.txt index 4cb91ea9..92d14d07 100644 --- a/data/tests/CMakeLists.txt +++ b/data/tests/CMakeLists.txt @@ -42,6 +42,11 @@ target_link_libraries(pack_writer.gtest ${JOIN_DATA} GTest::gtest_main) add_test(NAME pack_writer.gtest COMMAND pack_writer.gtest) install(TARGETS pack_writer.gtest RUNTIME DESTINATION ${CMAKE_INSTALL_DATADIR}/${PROJECT_NAME}/test) +add_executable(pack_roundtrip.gtest pack_roundtrip_test.cpp) +target_link_libraries(pack_roundtrip.gtest ${JOIN_DATA} GTest::gtest_main) +add_test(NAME pack_roundtrip.gtest COMMAND pack_roundtrip.gtest) +install(TARGETS pack_roundtrip.gtest RUNTIME DESTINATION ${CMAKE_INSTALL_DATADIR}/${PROJECT_NAME}/test) + add_executable(json_error.gtest json_error_test.cpp) target_link_libraries(json_error.gtest ${JOIN_DATA} GTest::gtest_main) add_test(NAME json_error.gtest COMMAND json_error.gtest) @@ -62,10 +67,10 @@ target_link_libraries(json_canonicalizer.gtest ${JOIN_DATA} GTest::gtest_main) add_test(NAME json_canonicalizer.gtest COMMAND json_canonicalizer.gtest) install(TARGETS json_canonicalizer.gtest RUNTIME DESTINATION ${CMAKE_INSTALL_DATADIR}/${PROJECT_NAME}/test) -add_executable(roundtrip.gtest roundtrip_test.cpp) -target_link_libraries(roundtrip.gtest ${JOIN_DATA} GTest::gtest_main) -add_test(NAME roundtrip.gtest COMMAND roundtrip.gtest) -install(TARGETS roundtrip.gtest RUNTIME DESTINATION ${CMAKE_INSTALL_DATADIR}/${PROJECT_NAME}/test) +add_executable(json_roundtrip.gtest json_roundtrip_test.cpp) +target_link_libraries(json_roundtrip.gtest ${JOIN_DATA} GTest::gtest_main) +add_test(NAME json_roundtrip.gtest COMMAND json_roundtrip.gtest) +install(TARGETS json_roundtrip.gtest RUNTIME DESTINATION ${CMAKE_INSTALL_DATADIR}/${PROJECT_NAME}/test) add_executable(parse.gtest parse_test.cpp) target_link_libraries(parse.gtest ${JOIN_DATA} GTest::gtest_main) diff --git a/data/tests/json_reader_test.cpp b/data/tests/json_reader_test.cpp index f40adb24..88e90a65 100644 --- a/data/tests/json_reader_test.cpp +++ b/data/tests/json_reader_test.cpp @@ -1585,27 +1585,86 @@ TEST (JsonReader, encoding) ASSERT_TRUE (value.isObject ()); EXPECT_EQ (value["\xC3\xB6\xC3\xA9\xC3\xB6"].getString (), "\xE2\x82\xAC\n\xE2\x82\xAC"); - for (auto const& doc : {"[\"\xC0\xAF\"]", "[\"\xED\xA0\x80\"]", "[\"\xF5\x80\x80\x80\"]", "[\"\xE2\x28\xA1\"]", - "[\"\xE2\x82\"]", "[\"\xE2\x82\\n\"]", "{\"\xFF\":1}"}) - { - stream.clear (); - stream.str (doc); - ASSERT_EQ (BasicJsonReader ().deserialize (stream, value), -1); - EXPECT_EQ (join::lastError, JsonErrc::InvalidEncoding); - - stream.clear (); - stream.str (doc); - ASSERT_EQ (JsonReader ().deserialize (stream, value), 0) << join::lastError.message (); - } + stream.clear (); + stream.str ("[\"\xC0\xAF\"]"); + ASSERT_EQ (BasicJsonReader ().deserialize (stream, value), -1); + EXPECT_EQ (join::lastError, JsonErrc::InvalidEncoding); + stream.clear (); + stream.str ("[\"\xC0\xAF\"]"); + ASSERT_EQ (JsonReader ().deserialize (stream, value), 0) << join::lastError.message (); - for (auto const& doc : - {"[\"\\uZZZZ\"]", "[\"\\uDC00\"]", "[\"\\uD800x\"]", "[\"\\uD800\\uZZZZ\"]", "[\"\\uD800\\u0041\"]"}) - { - stream.clear (); - stream.str (doc); - ASSERT_EQ (JsonReader ().deserialize (stream, value), -1); - EXPECT_EQ (join::lastError, JsonErrc::InvalidEncoding); - } + stream.clear (); + stream.str ("[\"\xED\xA0\x80\"]"); + ASSERT_EQ (BasicJsonReader ().deserialize (stream, value), -1); + EXPECT_EQ (join::lastError, JsonErrc::InvalidEncoding); + stream.clear (); + stream.str ("[\"\xED\xA0\x80\"]"); + ASSERT_EQ (JsonReader ().deserialize (stream, value), 0) << join::lastError.message (); + + stream.clear (); + stream.str ("[\"\xF5\x80\x80\x80\"]"); + ASSERT_EQ (BasicJsonReader ().deserialize (stream, value), -1); + EXPECT_EQ (join::lastError, JsonErrc::InvalidEncoding); + stream.clear (); + stream.str ("[\"\xF5\x80\x80\x80\"]"); + ASSERT_EQ (JsonReader ().deserialize (stream, value), 0) << join::lastError.message (); + + stream.clear (); + stream.str ("[\"\xE2\x28\xA1\"]"); + ASSERT_EQ (BasicJsonReader ().deserialize (stream, value), -1); + EXPECT_EQ (join::lastError, JsonErrc::InvalidEncoding); + stream.clear (); + stream.str ("[\"\xE2\x28\xA1\"]"); + ASSERT_EQ (JsonReader ().deserialize (stream, value), 0) << join::lastError.message (); + + stream.clear (); + stream.str ("[\"\xE2\x82\"]"); + ASSERT_EQ (BasicJsonReader ().deserialize (stream, value), -1); + EXPECT_EQ (join::lastError, JsonErrc::InvalidEncoding); + stream.clear (); + stream.str ("[\"\xE2\x82\"]"); + ASSERT_EQ (JsonReader ().deserialize (stream, value), 0) << join::lastError.message (); + + stream.clear (); + stream.str ("[\"\xE2\x82\\n\"]"); + ASSERT_EQ (BasicJsonReader ().deserialize (stream, value), -1); + EXPECT_EQ (join::lastError, JsonErrc::InvalidEncoding); + stream.clear (); + stream.str ("[\"\xE2\x82\\n\"]"); + ASSERT_EQ (JsonReader ().deserialize (stream, value), 0) << join::lastError.message (); + + stream.clear (); + stream.str ("{\"\xFF\":1}"); + ASSERT_EQ (BasicJsonReader ().deserialize (stream, value), -1); + EXPECT_EQ (join::lastError, JsonErrc::InvalidEncoding); + stream.clear (); + stream.str ("{\"\xFF\":1}"); + ASSERT_EQ (JsonReader ().deserialize (stream, value), 0) << join::lastError.message (); + + stream.clear (); + stream.str ("[\"\\uZZZZ\"]"); + ASSERT_EQ (JsonReader ().deserialize (stream, value), -1); + EXPECT_EQ (join::lastError, JsonErrc::InvalidEncoding); + + stream.clear (); + stream.str ("[\"\\uDC00\"]"); + ASSERT_EQ (JsonReader ().deserialize (stream, value), -1); + EXPECT_EQ (join::lastError, JsonErrc::InvalidEncoding); + + stream.clear (); + stream.str ("[\"\\uD800x\"]"); + ASSERT_EQ (JsonReader ().deserialize (stream, value), -1); + EXPECT_EQ (join::lastError, JsonErrc::InvalidEncoding); + + stream.clear (); + stream.str ("[\"\\uD800\\uZZZZ\"]"); + ASSERT_EQ (JsonReader ().deserialize (stream, value), -1); + EXPECT_EQ (join::lastError, JsonErrc::InvalidEncoding); + + stream.clear (); + stream.str ("[\"\\uD800\\u0041\"]"); + ASSERT_EQ (JsonReader ().deserialize (stream, value), -1); + EXPECT_EQ (join::lastError, JsonErrc::InvalidEncoding); std::string str ("[\"caf\xC3\xA9\", \"\xE2\x82\"]"); ASSERT_EQ (BasicJsonReader ().deserialize (str, value), -1); diff --git a/data/tests/roundtrip_test.cpp b/data/tests/json_roundtrip_test.cpp similarity index 89% rename from data/tests/roundtrip_test.cpp rename to data/tests/json_roundtrip_test.cpp index 695a5c6d..26ac0c54 100644 --- a/data/tests/roundtrip_test.cpp +++ b/data/tests/json_roundtrip_test.cpp @@ -41,7 +41,7 @@ using join::JsonReader; /** * @brief Test JSON round trip. */ -TEST (JsonReader, roundtrip) +TEST (Json, roundtrip) { Value value; JsonReader reader; @@ -264,6 +264,38 @@ TEST (JsonReader, roundtrip) stream.clear (); ASSERT_NE (value.serialize (writer), -1) << join::lastError.message (); EXPECT_EQ (stream.str (), "[1.7976931348623157e308]"); + + stream.clear (); + stream.str ("[Inf]"); + ASSERT_NE (reader.deserialize (stream, value), -1) << join::lastError.message (); + stream.str (""); + stream.clear (); + ASSERT_NE (value.serialize (writer), -1) << join::lastError.message (); + EXPECT_EQ (stream.str (), "[Inf]"); + + stream.clear (); + stream.str ("[-Inf]"); + ASSERT_NE (reader.deserialize (stream, value), -1) << join::lastError.message (); + stream.str (""); + stream.clear (); + ASSERT_NE (value.serialize (writer), -1) << join::lastError.message (); + EXPECT_EQ (stream.str (), "[-Inf]"); + + stream.clear (); + stream.str ("[NaN]"); + ASSERT_NE (reader.deserialize (stream, value), -1) << join::lastError.message (); + stream.str (""); + stream.clear (); + ASSERT_NE (value.serialize (writer), -1) << join::lastError.message (); + EXPECT_EQ (stream.str (), "[NaN]"); + + stream.clear (); + stream.str ("[-NaN]"); + ASSERT_NE (reader.deserialize (stream, value), -1) << join::lastError.message (); + stream.str (""); + stream.clear (); + ASSERT_NE (value.serialize (writer), -1) << join::lastError.message (); + EXPECT_EQ (stream.str (), "[-NaN]"); } /** diff --git a/data/tests/pack_reader_test.cpp b/data/tests/pack_reader_test.cpp index 71f890a7..8024ced1 100644 --- a/data/tests/pack_reader_test.cpp +++ b/data/tests/pack_reader_test.cpp @@ -179,12 +179,46 @@ TEST (PackReader, deserialize) ASSERT_EQ (PackReader ().deserialize (std::string ({'\xDA', '\x00'}), value), -1); ASSERT_EQ (join::lastError, SaxErrc::Truncated); - for (char head : {'\xDD', '\xDC', '\xDF', '\xDE', '\xDB', '\xDA', '\xD9', '\xC6', '\xC5', '\xC4', - '\xD3', '\xCF', '\xCB', '\xD2', '\xCE', '\xCA', '\xD1', '\xCD', '\xD0', '\xCC'}) - { - ASSERT_EQ (PackReader ().deserialize (std::string (1, head), value), -1) << int (head & 0xFF); - ASSERT_EQ (join::lastError, SaxErrc::Truncated) << int (head & 0xFF); - } + ASSERT_EQ (PackReader ().deserialize (std::string (1, '\xDD'), value), -1); + ASSERT_EQ (join::lastError, SaxErrc::Truncated); + ASSERT_EQ (PackReader ().deserialize (std::string (1, '\xDC'), value), -1); + ASSERT_EQ (join::lastError, SaxErrc::Truncated); + ASSERT_EQ (PackReader ().deserialize (std::string (1, '\xDF'), value), -1); + ASSERT_EQ (join::lastError, SaxErrc::Truncated); + ASSERT_EQ (PackReader ().deserialize (std::string (1, '\xDE'), value), -1); + ASSERT_EQ (join::lastError, SaxErrc::Truncated); + ASSERT_EQ (PackReader ().deserialize (std::string (1, '\xDB'), value), -1); + ASSERT_EQ (join::lastError, SaxErrc::Truncated); + ASSERT_EQ (PackReader ().deserialize (std::string (1, '\xDA'), value), -1); + ASSERT_EQ (join::lastError, SaxErrc::Truncated); + ASSERT_EQ (PackReader ().deserialize (std::string (1, '\xD9'), value), -1); + ASSERT_EQ (join::lastError, SaxErrc::Truncated); + ASSERT_EQ (PackReader ().deserialize (std::string (1, '\xC6'), value), -1); + ASSERT_EQ (join::lastError, SaxErrc::Truncated); + ASSERT_EQ (PackReader ().deserialize (std::string (1, '\xC5'), value), -1); + ASSERT_EQ (join::lastError, SaxErrc::Truncated); + ASSERT_EQ (PackReader ().deserialize (std::string (1, '\xC4'), value), -1); + ASSERT_EQ (join::lastError, SaxErrc::Truncated); + ASSERT_EQ (PackReader ().deserialize (std::string (1, '\xD3'), value), -1); + ASSERT_EQ (join::lastError, SaxErrc::Truncated); + ASSERT_EQ (PackReader ().deserialize (std::string (1, '\xCF'), value), -1); + ASSERT_EQ (join::lastError, SaxErrc::Truncated); + ASSERT_EQ (PackReader ().deserialize (std::string (1, '\xCB'), value), -1); + ASSERT_EQ (join::lastError, SaxErrc::Truncated); + ASSERT_EQ (PackReader ().deserialize (std::string (1, '\xD2'), value), -1); + ASSERT_EQ (join::lastError, SaxErrc::Truncated); + ASSERT_EQ (PackReader ().deserialize (std::string (1, '\xCE'), value), -1); + ASSERT_EQ (join::lastError, SaxErrc::Truncated); + ASSERT_EQ (PackReader ().deserialize (std::string (1, '\xCA'), value), -1); + ASSERT_EQ (join::lastError, SaxErrc::Truncated); + ASSERT_EQ (PackReader ().deserialize (std::string (1, '\xD1'), value), -1); + ASSERT_EQ (join::lastError, SaxErrc::Truncated); + ASSERT_EQ (PackReader ().deserialize (std::string (1, '\xCD'), value), -1); + ASSERT_EQ (join::lastError, SaxErrc::Truncated); + ASSERT_EQ (PackReader ().deserialize (std::string (1, '\xD0'), value), -1); + ASSERT_EQ (join::lastError, SaxErrc::Truncated); + ASSERT_EQ (PackReader ().deserialize (std::string (1, '\xCC'), value), -1); + ASSERT_EQ (join::lastError, SaxErrc::Truncated); Throwing throwing; ASSERT_THROW (PackReader ().deserialize (std::string ({'\x91', '\xA1', 'a'}), throwing), std::range_error); @@ -1596,23 +1630,34 @@ TEST (PackReader, encoding) { using Validator = join::BasicPackReader; - for (const std::string& document : {std::string ({'\xa2', '\xc3', '\xa9'}), std::string ({'\xc4', '\x01', '\xff'})}) - { - std::stringstream stream (document); - join::SaxHandler handler; - ASSERT_EQ (Validator ().deserialize (document, handler), 0) << join::lastError.message (); - ASSERT_EQ (Validator ().deserialize (stream, handler), 0) << join::lastError.message (); - } + join::SaxHandler handler; + std::stringstream stream; - for (const std::string& document : {std::string ({'\xa1', '\xff'}), std::string ({'\x81', '\xa1', '\xff', '\x01'})}) - { - std::stringstream stream (document); - join::SaxHandler handler; - ASSERT_EQ (Validator ().deserialize (document, handler), -1); - ASSERT_EQ (join::lastError, SaxErrc::InvalidEncoding); - ASSERT_EQ (Validator ().deserialize (stream, handler), -1); - ASSERT_EQ (join::lastError, SaxErrc::InvalidEncoding); - } + ASSERT_EQ (Validator ().deserialize (std::string ({'\xa2', '\xc3', '\xa9'}), handler), 0) + << join::lastError.message (); + stream.clear (); + stream.str (std::string ({'\xa2', '\xc3', '\xa9'})); + ASSERT_EQ (Validator ().deserialize (stream, handler), 0) << join::lastError.message (); + + ASSERT_EQ (Validator ().deserialize (std::string ({'\xc4', '\x01', '\xff'}), handler), 0) + << join::lastError.message (); + stream.clear (); + stream.str (std::string ({'\xc4', '\x01', '\xff'})); + ASSERT_EQ (Validator ().deserialize (stream, handler), 0) << join::lastError.message (); + + ASSERT_EQ (Validator ().deserialize (std::string ({'\xa1', '\xff'}), handler), -1); + ASSERT_EQ (join::lastError, SaxErrc::InvalidEncoding); + stream.clear (); + stream.str (std::string ({'\xa1', '\xff'})); + ASSERT_EQ (Validator ().deserialize (stream, handler), -1); + ASSERT_EQ (join::lastError, SaxErrc::InvalidEncoding); + + ASSERT_EQ (Validator ().deserialize (std::string ({'\x81', '\xa1', '\xff', '\x01'}), handler), -1); + ASSERT_EQ (join::lastError, SaxErrc::InvalidEncoding); + stream.clear (); + stream.str (std::string ({'\x81', '\xa1', '\xff', '\x01'})); + ASSERT_EQ (Validator ().deserialize (stream, handler), -1); + ASSERT_EQ (join::lastError, SaxErrc::InvalidEncoding); Value value; ASSERT_EQ (PackReader ().deserialize (std::string ({'\xa1', '\xff'}), value), 0) << join::lastError.message (); diff --git a/data/tests/pack_roundtrip_test.cpp b/data/tests/pack_roundtrip_test.cpp new file mode 100644 index 00000000..e7fe105e --- /dev/null +++ b/data/tests/pack_roundtrip_test.cpp @@ -0,0 +1,346 @@ +/** + * MIT License + * + * Copyright (c) 2026 Mathieu Rabine + * + * 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. + */ + +// libjoin. +#include + +// libraries. +#include + +// C++. +#include +#include +#include + +using join::Value; +using join::PackReader; +using join::PackWriter; + +/** + * @brief convert hexadecimal digits to bytes. + * @param digits hexadecimal digits. + * @return bytes. + */ +static std::string hex (std::string_view digits) +{ + std::string bytes; + for (size_t i = 0; i + 1 < digits.size (); i += 2) + { + bytes += static_cast (std::stoi (std::string (digits.substr (i, 2)), nullptr, 16)); + } + return bytes; +} + +/** + * @brief Test MessagePack round trip. + */ +TEST (Pack, roundtrip) +{ + Value value; + PackReader reader; + + std::stringstream stream; + PackWriter writer (stream); + + stream.clear (); + stream.str (hex ("91c0")); + ASSERT_NE (reader.deserialize (stream, value), -1) << join::lastError.message (); + stream.str (""); + stream.clear (); + ASSERT_NE (value.serialize (writer), -1) << join::lastError.message (); + EXPECT_EQ (stream.str (), hex ("91c0")); + + stream.clear (); + stream.str (hex ("91c3")); + ASSERT_NE (reader.deserialize (stream, value), -1) << join::lastError.message (); + stream.str (""); + stream.clear (); + ASSERT_NE (value.serialize (writer), -1) << join::lastError.message (); + EXPECT_EQ (stream.str (), hex ("91c3")); + + stream.clear (); + stream.str (hex ("91c2")); + ASSERT_NE (reader.deserialize (stream, value), -1) << join::lastError.message (); + stream.str (""); + stream.clear (); + ASSERT_NE (value.serialize (writer), -1) << join::lastError.message (); + EXPECT_EQ (stream.str (), hex ("91c2")); + + stream.clear (); + stream.str (hex ("9100")); + ASSERT_NE (reader.deserialize (stream, value), -1) << join::lastError.message (); + stream.str (""); + stream.clear (); + ASSERT_NE (value.serialize (writer), -1) << join::lastError.message (); + EXPECT_EQ (stream.str (), hex ("9100")); + + stream.clear (); + stream.str (hex ("91a3666f6f")); + ASSERT_NE (reader.deserialize (stream, value), -1) << join::lastError.message (); + stream.str (""); + stream.clear (); + ASSERT_NE (value.serialize (writer), -1) << join::lastError.message (); + EXPECT_EQ (stream.str (), hex ("91a3666f6f")); + + stream.clear (); + stream.str (hex ("90")); + ASSERT_NE (reader.deserialize (stream, value), -1) << join::lastError.message (); + stream.str (""); + stream.clear (); + ASSERT_NE (value.serialize (writer), -1) << join::lastError.message (); + EXPECT_EQ (stream.str (), hex ("90")); + + stream.clear (); + stream.str (hex ("80")); + ASSERT_NE (reader.deserialize (stream, value), -1) << join::lastError.message (); + stream.str (""); + stream.clear (); + ASSERT_NE (value.serialize (writer), -1) << join::lastError.message (); + EXPECT_EQ (stream.str (), hex ("80")); + + stream.clear (); + stream.str (hex ("920001")); + ASSERT_NE (reader.deserialize (stream, value), -1) << join::lastError.message (); + stream.str (""); + stream.clear (); + ASSERT_NE (value.serialize (writer), -1) << join::lastError.message (); + EXPECT_EQ (stream.str (), hex ("920001")); + + stream.clear (); + stream.str (hex ("81a3666f6fa3626172")); + ASSERT_NE (reader.deserialize (stream, value), -1) << join::lastError.message (); + stream.str (""); + stream.clear (); + ASSERT_NE (value.serialize (writer), -1) << join::lastError.message (); + EXPECT_EQ (stream.str (), hex ("81a3666f6fa3626172")); + + stream.clear (); + stream.str (hex ("82a161c0a3666f6fa3626172")); + ASSERT_NE (reader.deserialize (stream, value), -1) << join::lastError.message (); + stream.str (""); + stream.clear (); + ASSERT_NE (value.serialize (writer), -1) << join::lastError.message (); + EXPECT_EQ (stream.str (), hex ("82a161c0a3666f6fa3626172")); + + stream.clear (); + stream.str (hex ("91ff")); + ASSERT_NE (reader.deserialize (stream, value), -1) << join::lastError.message (); + stream.str (""); + stream.clear (); + ASSERT_NE (value.serialize (writer), -1) << join::lastError.message (); + EXPECT_EQ (stream.str (), hex ("91ff")); + + stream.clear (); + stream.str (hex ("91d280000000")); + ASSERT_NE (reader.deserialize (stream, value), -1) << join::lastError.message (); + stream.str (""); + stream.clear (); + ASSERT_NE (value.serialize (writer), -1) << join::lastError.message (); + EXPECT_EQ (stream.str (), hex ("91d280000000")); + + stream.clear (); + stream.str (hex ("91d3eeddef0b82167eeb")); + ASSERT_NE (reader.deserialize (stream, value), -1) << join::lastError.message (); + stream.str (""); + stream.clear (); + ASSERT_NE (value.serialize (writer), -1) << join::lastError.message (); + EXPECT_EQ (stream.str (), hex ("91d3eeddef0b82167eeb")); + + stream.clear (); + stream.str (hex ("91d38000000000000000")); + ASSERT_NE (reader.deserialize (stream, value), -1) << join::lastError.message (); + stream.str (""); + stream.clear (); + ASSERT_NE (value.serialize (writer), -1) << join::lastError.message (); + EXPECT_EQ (stream.str (), hex ("91d38000000000000000")); + + stream.clear (); + stream.str (hex ("9101")); + ASSERT_NE (reader.deserialize (stream, value), -1) << join::lastError.message (); + stream.str (""); + stream.clear (); + ASSERT_NE (value.serialize (writer), -1) << join::lastError.message (); + EXPECT_EQ (stream.str (), hex ("9101")); + + stream.clear (); + stream.str (hex ("91ce7fffffff")); + ASSERT_NE (reader.deserialize (stream, value), -1) << join::lastError.message (); + stream.str (""); + stream.clear (); + ASSERT_NE (value.serialize (writer), -1) << join::lastError.message (); + EXPECT_EQ (stream.str (), hex ("91ce7fffffff")); + + stream.clear (); + stream.str (hex ("91ceffffffff")); + ASSERT_NE (reader.deserialize (stream, value), -1) << join::lastError.message (); + stream.str (""); + stream.clear (); + ASSERT_NE (value.serialize (writer), -1) << join::lastError.message (); + EXPECT_EQ (stream.str (), hex ("91ceffffffff")); + + stream.clear (); + stream.str (hex ("91cf112210f47de98115")); + ASSERT_NE (reader.deserialize (stream, value), -1) << join::lastError.message (); + stream.str (""); + stream.clear (); + ASSERT_NE (value.serialize (writer), -1) << join::lastError.message (); + EXPECT_EQ (stream.str (), hex ("91cf112210f47de98115")); + + stream.clear (); + stream.str (hex ("91cf7fffffffffffffff")); + ASSERT_NE (reader.deserialize (stream, value), -1) << join::lastError.message (); + stream.str (""); + stream.clear (); + ASSERT_NE (value.serialize (writer), -1) << join::lastError.message (); + EXPECT_EQ (stream.str (), hex ("91cf7fffffffffffffff")); + + stream.clear (); + stream.str (hex ("91cfffffffffffffffff")); + ASSERT_NE (reader.deserialize (stream, value), -1) << join::lastError.message (); + stream.str (""); + stream.clear (); + ASSERT_NE (value.serialize (writer), -1) << join::lastError.message (); + EXPECT_EQ (stream.str (), hex ("91cfffffffffffffffff")); + + stream.clear (); + stream.str (hex ("91ca00000000")); + ASSERT_NE (reader.deserialize (stream, value), -1) << join::lastError.message (); + stream.str (""); + stream.clear (); + ASSERT_NE (value.serialize (writer), -1) << join::lastError.message (); + EXPECT_EQ (stream.str (), hex ("91ca00000000")); + + stream.clear (); + stream.str (hex ("91ca80000000")); + ASSERT_NE (reader.deserialize (stream, value), -1) << join::lastError.message (); + stream.str (""); + stream.clear (); + ASSERT_NE (value.serialize (writer), -1) << join::lastError.message (); + EXPECT_EQ (stream.str (), hex ("91ca80000000")); + + stream.clear (); + stream.str (hex ("91cb3ff3c083126e978d")); + ASSERT_NE (reader.deserialize (stream, value), -1) << join::lastError.message (); + stream.str (""); + stream.clear (); + ASSERT_NE (value.serialize (writer), -1) << join::lastError.message (); + EXPECT_EQ (stream.str (), hex ("91cb3ff3c083126e978d")); + + stream.clear (); + stream.str (hex ("91cbbff3c083126e978d")); + ASSERT_NE (reader.deserialize (stream, value), -1) << join::lastError.message (); + stream.str (""); + stream.clear (); + ASSERT_NE (value.serialize (writer), -1) << join::lastError.message (); + EXPECT_EQ (stream.str (), hex ("91cbbff3c083126e978d")); + + stream.clear (); + stream.str (hex ("91cb0000000000000001")); + ASSERT_NE (reader.deserialize (stream, value), -1) << join::lastError.message (); + stream.str (""); + stream.clear (); + ASSERT_NE (value.serialize (writer), -1) << join::lastError.message (); + EXPECT_EQ (stream.str (), hex ("91cb0000000000000001")); + + stream.clear (); + stream.str (hex ("91cb000fffffffffffff")); + ASSERT_NE (reader.deserialize (stream, value), -1) << join::lastError.message (); + stream.str (""); + stream.clear (); + ASSERT_NE (value.serialize (writer), -1) << join::lastError.message (); + EXPECT_EQ (stream.str (), hex ("91cb000fffffffffffff")); + + stream.clear (); + stream.str (hex ("91cb0010000000000000")); + ASSERT_NE (reader.deserialize (stream, value), -1) << join::lastError.message (); + stream.str (""); + stream.clear (); + ASSERT_NE (value.serialize (writer), -1) << join::lastError.message (); + EXPECT_EQ (stream.str (), hex ("91cb0010000000000000")); + + stream.clear (); + stream.str (hex ("91cb7fefffffffffffff")); + ASSERT_NE (reader.deserialize (stream, value), -1) << join::lastError.message (); + stream.str (""); + stream.clear (); + ASSERT_NE (value.serialize (writer), -1) << join::lastError.message (); + EXPECT_EQ (stream.str (), hex ("91cb7fefffffffffffff")); + + stream.clear (); + stream.str (hex ("91c403010203")); + ASSERT_NE (reader.deserialize (stream, value), -1) << join::lastError.message (); + stream.str (""); + stream.clear (); + ASSERT_NE (value.serialize (writer), -1) << join::lastError.message (); + EXPECT_EQ (stream.str (), hex ("91c403010203")); + + stream.clear (); + stream.str (hex ("810102")); + ASSERT_NE (reader.deserialize (stream, value), -1) << join::lastError.message (); + stream.str (""); + stream.clear (); + ASSERT_NE (value.serialize (writer), -1) << join::lastError.message (); + EXPECT_EQ (stream.str (), hex ("810102")); + + stream.clear (); + stream.str (hex ("91ca7f800000")); + ASSERT_NE (reader.deserialize (stream, value), -1) << join::lastError.message (); + stream.str (""); + stream.clear (); + ASSERT_NE (value.serialize (writer), -1) << join::lastError.message (); + EXPECT_EQ (stream.str (), hex ("91ca7f800000")); + + stream.clear (); + stream.str (hex ("91caff800000")); + ASSERT_NE (reader.deserialize (stream, value), -1) << join::lastError.message (); + stream.str (""); + stream.clear (); + ASSERT_NE (value.serialize (writer), -1) << join::lastError.message (); + EXPECT_EQ (stream.str (), hex ("91caff800000")); + + stream.clear (); + stream.str (hex ("91ca7fc00000")); + ASSERT_NE (reader.deserialize (stream, value), -1) << join::lastError.message (); + stream.str (""); + stream.clear (); + ASSERT_NE (value.serialize (writer), -1) << join::lastError.message (); + EXPECT_EQ (stream.str (), hex ("91ca7fc00000")); + + stream.clear (); + stream.str (hex ("91caffc00000")); + ASSERT_NE (reader.deserialize (stream, value), -1) << join::lastError.message (); + stream.str (""); + stream.clear (); + ASSERT_NE (value.serialize (writer), -1) << join::lastError.message (); + EXPECT_EQ (stream.str (), hex ("91caffc00000")); +} + +/** + * @brief main function. + */ +int main (int argc, char** argv) +{ + testing::InitGoogleTest (&argc, argv); + return RUN_ALL_TESTS (); +} diff --git a/data/tests/pack_writer_test.cpp b/data/tests/pack_writer_test.cpp index 4350231d..a4f66194 100644 --- a/data/tests/pack_writer_test.cpp +++ b/data/tests/pack_writer_test.cpp @@ -32,7 +32,12 @@ // C++. #include #include +#include #include +#include + +// C. +#include using join::PackWriter; using join::Value; @@ -345,6 +350,52 @@ TEST (PackWriter, setDouble) stream.str (""); EXPECT_EQ (packWriter.setDouble (0.123456789e-12), 0); EXPECT_EQ (stream.str (), std::string ({'\xCB', '\x3D', '\x41', '\x5F', '\xFF', '\xE5', '\x3A', '\x68', '\x5D'})); + + stream.str (""); + EXPECT_EQ (packWriter.setDouble (std::numeric_limits::infinity ()), 0); + EXPECT_EQ (stream.str (), std::string ({'\xCA', '\x7F', '\x80', '\x00', '\x00'})); + + stream.str (""); + EXPECT_EQ (packWriter.setDouble (-std::numeric_limits::infinity ()), 0); + EXPECT_EQ (stream.str (), std::string ({'\xCA', '\xFF', '\x80', '\x00', '\x00'})); + + stream.str (""); + EXPECT_EQ (packWriter.setDouble (std::numeric_limits::quiet_NaN ()), 0); + EXPECT_EQ (stream.str (), std::string ({'\xCA', '\x7F', '\xC0', '\x00', '\x00'})); + + stream.str (""); + EXPECT_EQ (packWriter.setDouble (1e300), 0); + EXPECT_EQ (stream.str ().front (), '\xCB'); + + stream.str (""); + EXPECT_EQ (packWriter.setDouble (std::bit_cast (uint64_t (0x7ff8000000000001))), 0); + EXPECT_EQ (stream.str (), std::string ({'\xCB', '\x7F', '\xF8', '\x00', '\x00', '\x00', '\x00', '\x00', '\x01'})); + + stream.str (""); + EXPECT_EQ (packWriter.setDouble (std::numeric_limits::max ()), 0); + EXPECT_EQ (stream.str (), std::string ({'\xCA', '\x7F', '\x7F', '\xFF', '\xFF'})); + + stream.str (""); + EXPECT_EQ (packWriter.setDouble (std::nextafter (static_cast (std::numeric_limits::max ()), + std::numeric_limits::infinity ())), + 0); + EXPECT_EQ (stream.str (), std::string ({'\xCB', '\x47', '\xEF', '\xFF', '\xFF', '\xE0', '\x00', '\x00', '\x01'})); + + stream.str (""); + EXPECT_EQ (packWriter.setDouble (std::numeric_limits::denorm_min ()), 0); + EXPECT_EQ (stream.str (), std::string ({'\xCA', '\x00', '\x00', '\x00', '\x01'})); + + stream.str (""); + EXPECT_EQ (packWriter.setDouble (std::numeric_limits::denorm_min ()), 0); + EXPECT_EQ (stream.str (), std::string ({'\xCB', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x01'})); + + stream.str (""); + EXPECT_EQ (packWriter.setDouble (-std::numeric_limits::quiet_NaN ()), 0); + EXPECT_EQ (stream.str (), std::string ({'\xCA', '\xFF', '\xC0', '\x00', '\x00'})); + + stream.str (""); + EXPECT_EQ (packWriter.setDouble (std::bit_cast (uint64_t (0x7ff4000000000000))), 0); + EXPECT_EQ (stream.str (), std::string ({'\xCB', '\x7F', '\xF4', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'})); } /** diff --git a/data/tests/utf8_test.cpp b/data/tests/utf8_test.cpp index f46cf1f9..a59961ff 100644 --- a/data/tests/utf8_test.cpp +++ b/data/tests/utf8_test.cpp @@ -37,34 +37,159 @@ */ TEST (Utf8, decode) { - struct Valid - { - std::string in; - uint32_t codepoint; - }; + std::string in; + const char* cur = nullptr; + uint32_t codepoint = 0; - for (auto const& v : - {Valid{std::string (1, '\0'), 0x00}, Valid{"\x7F", 0x7F}, Valid{"\xC2\x80", 0x80}, Valid{"\xDF\xBF", 0x7FF}, - Valid{"\xE0\xA0\x80", 0x800}, Valid{"\xED\x9F\xBF", 0xD7FF}, Valid{"\xEE\x80\x80", 0xE000}, - Valid{"\xEF\xBF\xBF", 0xFFFF}, Valid{"\xF0\x90\x80\x80", 0x10000}, Valid{"\xF4\x8F\xBF\xBF", 0x10FFFF}}) - { - const char* cur = v.in.data (); - uint32_t codepoint = 0; - ASSERT_EQ (join::utf8::decode (cur, v.in.data () + v.in.size (), codepoint), 0); - EXPECT_EQ (codepoint, v.codepoint); - EXPECT_EQ (cur, v.in.data () + v.in.size ()); - } + in = std::string (1, '\0'); + cur = in.data (); + ASSERT_EQ (join::utf8::decode (cur, in.data () + in.size (), codepoint), 0); + EXPECT_EQ (codepoint, 0x00U); + EXPECT_EQ (cur, in.data () + in.size ()); - for (auto const& in : {"\x80", "\xBF", "\xC0\x80", "\xC1\xBF", "\xC2\x7F", "\xC2\xC0", "\xE0\x9F\xBF", - "\xED\xA0\x80", "\xED\xBF\xBF", "\xE1\x80\x7F", "\xF0\x8F\xBF\xBF", "\xF4\x90\x80\x80", - "\xF1\x80\x80\xC0", "\xF5\x80\x80\x80", "\xFF", "\xC2", "\xE1\x80", "\xF1\x80\x80"}) - { - std::string str (in); - const char* cur = str.data (); - uint32_t codepoint = 0; - EXPECT_EQ (join::utf8::decode (cur, str.data () + str.size (), codepoint), -1); - EXPECT_EQ (cur, str.data ()); - } + in = "\x7F"; + cur = in.data (); + ASSERT_EQ (join::utf8::decode (cur, in.data () + in.size (), codepoint), 0); + EXPECT_EQ (codepoint, 0x7FU); + EXPECT_EQ (cur, in.data () + in.size ()); + + in = "\xC2\x80"; + cur = in.data (); + ASSERT_EQ (join::utf8::decode (cur, in.data () + in.size (), codepoint), 0); + EXPECT_EQ (codepoint, 0x80U); + EXPECT_EQ (cur, in.data () + in.size ()); + + in = "\xDF\xBF"; + cur = in.data (); + ASSERT_EQ (join::utf8::decode (cur, in.data () + in.size (), codepoint), 0); + EXPECT_EQ (codepoint, 0x7FFU); + EXPECT_EQ (cur, in.data () + in.size ()); + + in = "\xE0\xA0\x80"; + cur = in.data (); + ASSERT_EQ (join::utf8::decode (cur, in.data () + in.size (), codepoint), 0); + EXPECT_EQ (codepoint, 0x800U); + EXPECT_EQ (cur, in.data () + in.size ()); + + in = "\xED\x9F\xBF"; + cur = in.data (); + ASSERT_EQ (join::utf8::decode (cur, in.data () + in.size (), codepoint), 0); + EXPECT_EQ (codepoint, 0xD7FFU); + EXPECT_EQ (cur, in.data () + in.size ()); + + in = "\xEE\x80\x80"; + cur = in.data (); + ASSERT_EQ (join::utf8::decode (cur, in.data () + in.size (), codepoint), 0); + EXPECT_EQ (codepoint, 0xE000U); + EXPECT_EQ (cur, in.data () + in.size ()); + + in = "\xEF\xBF\xBF"; + cur = in.data (); + ASSERT_EQ (join::utf8::decode (cur, in.data () + in.size (), codepoint), 0); + EXPECT_EQ (codepoint, 0xFFFFU); + EXPECT_EQ (cur, in.data () + in.size ()); + + in = "\xF0\x90\x80\x80"; + cur = in.data (); + ASSERT_EQ (join::utf8::decode (cur, in.data () + in.size (), codepoint), 0); + EXPECT_EQ (codepoint, 0x10000U); + EXPECT_EQ (cur, in.data () + in.size ()); + + in = "\xF4\x8F\xBF\xBF"; + cur = in.data (); + ASSERT_EQ (join::utf8::decode (cur, in.data () + in.size (), codepoint), 0); + EXPECT_EQ (codepoint, 0x10FFFFU); + EXPECT_EQ (cur, in.data () + in.size ()); + + in = "\x80"; + cur = in.data (); + EXPECT_EQ (join::utf8::decode (cur, in.data () + in.size (), codepoint), -1); + EXPECT_EQ (cur, in.data ()); + + in = "\xBF"; + cur = in.data (); + EXPECT_EQ (join::utf8::decode (cur, in.data () + in.size (), codepoint), -1); + EXPECT_EQ (cur, in.data ()); + + in = "\xC0\x80"; + cur = in.data (); + EXPECT_EQ (join::utf8::decode (cur, in.data () + in.size (), codepoint), -1); + EXPECT_EQ (cur, in.data ()); + + in = "\xC1\xBF"; + cur = in.data (); + EXPECT_EQ (join::utf8::decode (cur, in.data () + in.size (), codepoint), -1); + EXPECT_EQ (cur, in.data ()); + + in = "\xC2\x7F"; + cur = in.data (); + EXPECT_EQ (join::utf8::decode (cur, in.data () + in.size (), codepoint), -1); + EXPECT_EQ (cur, in.data ()); + + in = "\xC2\xC0"; + cur = in.data (); + EXPECT_EQ (join::utf8::decode (cur, in.data () + in.size (), codepoint), -1); + EXPECT_EQ (cur, in.data ()); + + in = "\xE0\x9F\xBF"; + cur = in.data (); + EXPECT_EQ (join::utf8::decode (cur, in.data () + in.size (), codepoint), -1); + EXPECT_EQ (cur, in.data ()); + + in = "\xED\xA0\x80"; + cur = in.data (); + EXPECT_EQ (join::utf8::decode (cur, in.data () + in.size (), codepoint), -1); + EXPECT_EQ (cur, in.data ()); + + in = "\xED\xBF\xBF"; + cur = in.data (); + EXPECT_EQ (join::utf8::decode (cur, in.data () + in.size (), codepoint), -1); + EXPECT_EQ (cur, in.data ()); + + in = "\xE1\x80\x7F"; + cur = in.data (); + EXPECT_EQ (join::utf8::decode (cur, in.data () + in.size (), codepoint), -1); + EXPECT_EQ (cur, in.data ()); + + in = "\xF0\x8F\xBF\xBF"; + cur = in.data (); + EXPECT_EQ (join::utf8::decode (cur, in.data () + in.size (), codepoint), -1); + EXPECT_EQ (cur, in.data ()); + + in = "\xF4\x90\x80\x80"; + cur = in.data (); + EXPECT_EQ (join::utf8::decode (cur, in.data () + in.size (), codepoint), -1); + EXPECT_EQ (cur, in.data ()); + + in = "\xF1\x80\x80\xC0"; + cur = in.data (); + EXPECT_EQ (join::utf8::decode (cur, in.data () + in.size (), codepoint), -1); + EXPECT_EQ (cur, in.data ()); + + in = "\xF5\x80\x80\x80"; + cur = in.data (); + EXPECT_EQ (join::utf8::decode (cur, in.data () + in.size (), codepoint), -1); + EXPECT_EQ (cur, in.data ()); + + in = "\xFF"; + cur = in.data (); + EXPECT_EQ (join::utf8::decode (cur, in.data () + in.size (), codepoint), -1); + EXPECT_EQ (cur, in.data ()); + + in = "\xC2"; + cur = in.data (); + EXPECT_EQ (join::utf8::decode (cur, in.data () + in.size (), codepoint), -1); + EXPECT_EQ (cur, in.data ()); + + in = "\xE1\x80"; + cur = in.data (); + EXPECT_EQ (join::utf8::decode (cur, in.data () + in.size (), codepoint), -1); + EXPECT_EQ (cur, in.data ()); + + in = "\xF1\x80\x80"; + cur = in.data (); + EXPECT_EQ (join::utf8::decode (cur, in.data () + in.size (), codepoint), -1); + EXPECT_EQ (cur, in.data ()); } /**