From eb7a02d5c43d7b43e7dd872afb6507cc1c61fbad Mon Sep 17 00:00:00 2001 From: Kurt Sassenrath Date: Fri, 12 Jan 2024 11:51:25 -0800 Subject: [PATCH] Refactor tests, add formatters, etc. --- .clang-format | 2 +- WORKSPACE | 4 +- include/parselink/logging/formatters.h | 1 - .../core/detail/builtin_unpackable_types.h | 260 ++++++++++++++++-- .../msgpack/core/detail/unpackable_concepts.h | 38 +-- include/parselink/msgpack/core/error.h | 1 + include/parselink/msgpack/core/format.h | 16 +- include/parselink/msgpack/core/unpacker.h | 4 + include/parselink/msgpack/extra/formatters.h | 121 ++++++++ source/server.cpp | 2 +- tests/msgpack/BUILD | 24 +- tests/msgpack/include/test_utils.h | 102 +++++++ tests/msgpack/test_utils.h | 100 ------- tests/msgpack/unpacker/BUILD | 9 + tests/msgpack/unpacker/bytes.cpp | 68 +++++ tests/msgpack/unpacker/signed.cpp | 105 +++++++ tests/msgpack/unpacker/simple_types.cpp | 61 ++++ tests/msgpack/unpacker/strings.cpp | 31 +++ tests/msgpack/unpacker/test_unpacker.h | 57 ++++ tests/msgpack/unpacker/unsigned.cpp | 131 +++++++++ 20 files changed, 970 insertions(+), 167 deletions(-) create mode 100644 include/parselink/msgpack/extra/formatters.h create mode 100644 tests/msgpack/include/test_utils.h delete mode 100644 tests/msgpack/test_utils.h create mode 100644 tests/msgpack/unpacker/BUILD create mode 100644 tests/msgpack/unpacker/bytes.cpp create mode 100644 tests/msgpack/unpacker/signed.cpp create mode 100644 tests/msgpack/unpacker/simple_types.cpp create mode 100644 tests/msgpack/unpacker/strings.cpp create mode 100644 tests/msgpack/unpacker/test_unpacker.h create mode 100644 tests/msgpack/unpacker/unsigned.cpp diff --git a/.clang-format b/.clang-format index 15e9f82..c2c8310 100644 --- a/.clang-format +++ b/.clang-format @@ -18,7 +18,7 @@ BreakConstructorInitializers: BeforeComma BreakInheritanceList: BeforeComma ConstructorInitializerIndentWidth: 8 ContinuationIndentWidth: 8 -IncludeBlocks: Regroup +#IncludeBlocks: Regroup IncludeCategories: - Regex: '^"parselink/' Priority: 1 diff --git a/WORKSPACE b/WORKSPACE index 9071cac..8b569cb 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -25,7 +25,7 @@ boost_deps() #------------------------------------------------------------------------------- # magic_enum: Used in logging implementation for enum names. #------------------------------------------------------------------------------- -magic_enum_version = "0.8.2" +magic_enum_version = "0.9.5" magic_enum_base_url = \ "https://github.com/Neargye/magic_enum/archive/refs/tags/v" magic_enum_sha256 = \ @@ -33,7 +33,7 @@ magic_enum_sha256 = \ http_archive( name = "magic_enum", - sha256 = magic_enum_sha256, + # sha256 = magic_enum_sha256, url = magic_enum_base_url + magic_enum_version + ".zip", strip_prefix = "magic_enum-" + magic_enum_version, ) diff --git a/include/parselink/logging/formatters.h b/include/parselink/logging/formatters.h index 668bed4..0dbcb03 100644 --- a/include/parselink/logging/formatters.h +++ b/include/parselink/logging/formatters.h @@ -118,7 +118,6 @@ struct fmt::formatter : fmt::formatter { } }; -// TODO(ksassenrath): Re-enable when expected has been integrated template struct fmt::formatter> { template diff --git a/include/parselink/msgpack/core/detail/builtin_unpackable_types.h b/include/parselink/msgpack/core/detail/builtin_unpackable_types.h index 932a7e1..f342a2f 100644 --- a/include/parselink/msgpack/core/detail/builtin_unpackable_types.h +++ b/include/parselink/msgpack/core/detail/builtin_unpackable_types.h @@ -32,6 +32,20 @@ namespace msgpack { namespace detail { +// Generic mechanism for reading a number of bytes out of an interator. +template +constexpr inline decltype(auto) read_bytes(Itr& inp) noexcept { + std::array out; + auto const end = inp + N; + auto dst = out.begin(); + while (inp != end) { + *dst = std::byte{*inp}; + ++dst; + ++inp; + } + return out; +} + // This is a generic helper function for writing integral bytes template constexpr decltype(auto) unpack_integral(Itr& inp) noexcept { @@ -48,6 +62,83 @@ template concept byte_range = std::ranges::contiguous_range && std::same_as, std::byte>; +template +constexpr inline bool accept_format(Iter& itr) noexcept { + if constexpr (is_fixtype) { + // Don't advance the iterator -- part of the format is locked away + // within the format byte. + return (std::byte{*itr} & ~F::mask) == F::marker; + } else { + // Advance the iterator past the format byte. + return std::byte{*itr++} == F::marker; + } +} + +// This helper structure provides a customization point for converting an +// iterator to some other type (e.g. a pointer) in the event that the +// final format does not have a suitable constructor. It's only currently +// used for string_view, but may be overridden on platforms that provide +// their own types. +template +struct iterator_adapter { + static constexpr auto convert(Iter itr) { return itr; } +}; + +template +struct iterator_adapter { + static constexpr auto convert(Iter itr) { + return ::detail::value_cast(&*itr); + } +}; + +// Using information expressed within each format type, this singular template +// can instantiate the unpacking of most formats. Notable exceptions are +// the array/map types and extension types. +template +constexpr inline auto unpack_format(auto& unpacker) noexcept + -> tl::expected { + if (!accept_format(unpacker.in)) { + return tl::make_unexpected(error::wrong_type); + } + + // First thing's first. Read the format's "first_type", which is either + // the payload or the length of the payload. Ensure we have enough data + // from the span before we do so, though. + using first_type = typename F::first_type; + using value_type = typename F::value_type; + using diff_type = decltype(unpacker.remaining()); + if (unpacker.remaining() < diff_type{sizeof(first_type)}) { + return tl::make_unexpected(error::incomplete_message); + } + + auto f = unpack_integral(unpacker.in); + if constexpr (is_fixtype && (F::flags & format::flag::apply_mask)) { + f &= decltype(f)(F::mask); + } + + if constexpr (F::payload_type == format::payload::fixed) { + // Prefer constructions that utilize the unpacked value, but allow + // tag types (e.g. invalid, nil) to be directly constructed. + if constexpr (std::constructible_from) { + return value_type(f); + } else { + return value_type{}; + } + } else { + // We're reading a variable length payload. `f` is the length of the + // payload. Ensure that the span has enough data and construct the + // value_type accordingly. + if (unpacker.remaining() < diff_type(f)) { + return tl::make_unexpected(error::incomplete_message); + } + + using adapt = iterator_adapter; + auto value = value_type(adapt::convert(unpacker.in), f); + unpacker.in += f; + return value; + } +} + } // namespace detail template @@ -58,64 +149,179 @@ struct builtin_unpacker; //////////////////////////////////////////////////////////////////////////////// template <> struct builtin_unpacker { - static constexpr bool accept(std::byte marker) noexcept { - return marker == format::nil::marker; - } - static constexpr auto unpack(auto& unpacker) noexcept { - ++unpacker.in; - return invalid{}; + return detail::unpack_format(unpacker); } }; template <> struct builtin_unpacker { - static constexpr bool accept(std::byte marker) noexcept { - return marker == format::nil::marker; - } - static constexpr auto unpack(auto& unpacker) noexcept { - ++unpacker.in; - return nil{}; + return detail::unpack_format(unpacker); } }; template <> struct builtin_unpacker { - static constexpr bool accept(std::byte marker) noexcept { - return (marker & ~format::boolean::mask) == format::boolean::marker; - } - static constexpr auto unpack(auto& unpacker) noexcept { - // In this case, we will not run out of room - return static_cast((*(unpacker.in)++) & ~format::boolean::mask); + return detail::unpack_format(unpacker); } }; // This unpacker will accept any uintX, positive_fixint types. struct unsigned_int_unpacker { - static constexpr bool accept(std::byte marker) noexcept { - if (!(marker & 0x80)) { - return true; + static constexpr auto unpack(auto& unpacker) noexcept + -> tl::expected { + auto marker = *(unpacker.in); + if ((marker & std::byte{0x80}) == std::byte{}) { + return detail::unpack_format(unpacker); } switch (marker) { case format::uint8::marker: + return detail::unpack_format(unpacker); case format::uint16::marker: + return detail::unpack_format(unpacker); case format::uint32::marker: + return detail::unpack_format(unpacker); case format::uint64::marker: - return true; - default: - break; + return detail::unpack_format(unpacker); } - return false; + return tl::make_unexpected(msgpack::error::wrong_type); + } +}; + +template +struct builtin_unpacker { + static constexpr auto max = std::numeric_limits::max(); + + static constexpr auto unpack(auto& unpacker) noexcept { + constexpr auto convert = + [](auto value) -> tl::expected { + if (value > max) { + return tl::make_unexpected(error::will_truncate); + } + return static_cast(value); + }; + return unsigned_int_unpacker::unpack(unpacker).and_then(convert); + } +}; + +struct signed_int_unpacker { + static constexpr auto unpack(auto& unpacker) noexcept + -> tl::expected { + auto marker = *(unpacker.in); + if ((marker & std::byte{0x80}) == std::byte{}) { + return detail::unpack_format(unpacker); + } else if ((marker & std::byte{0xe0}) == std::byte{0xe0}) { + return detail::unpack_format(unpacker); + } + switch (marker) { + case format::int8::marker: + return detail::unpack_format(unpacker); + case format::int16::marker: + return detail::unpack_format(unpacker); + case format::int32::marker: + return detail::unpack_format(unpacker); + case format::int64::marker: + return detail::unpack_format(unpacker); + } + return tl::make_unexpected(msgpack::error::wrong_type); } }; template struct builtin_unpacker { - static constexpr bool accept(std::byte marker) noexcept { + static constexpr auto max = std::numeric_limits::max(); + static constexpr auto min = std::numeric_limits::min(); + + static constexpr auto unpack(auto& unpacker) noexcept { + constexpr auto convert = + [](auto value) -> tl::expected { + if (value > max || value < min) { + return tl::make_unexpected(error::will_truncate); + } + return static_cast(value); + }; + return signed_int_unpacker::unpack(unpacker).and_then(convert); } -} +}; + +template <> +struct builtin_unpacker { + static constexpr auto unpack(auto& unpacker) noexcept + -> tl::expected { + auto marker = *(unpacker.in); + if ((marker & ~format::fixstr::mask) == format::fixstr::marker) { + return detail::unpack_format(unpacker); + } + switch (marker) { + case format::str8::marker: + return detail::unpack_format(unpacker); + case format::str16::marker: + return detail::unpack_format(unpacker); + case format::str32::marker: + return detail::unpack_format(unpacker); + } + return tl::make_unexpected(msgpack::error::wrong_type); + } +}; + +template + requires(std::same_as>) +struct builtin_unpacker> { + template + using expected_type = tl::expected, msgpack::error>; + + static constexpr auto unpack_bytes(auto& unpacker) noexcept + -> expected_type { + auto marker = *(unpacker.in); + switch (marker) { + case format::bin8::marker: + return detail::unpack_format(unpacker); + case format::bin16::marker: + return detail::unpack_format(unpacker); + case format::bin32::marker: + return detail::unpack_format(unpacker); + } + return tl::make_unexpected(msgpack::error::wrong_type); + } + + static constexpr expected_type<> unpack(auto& unpacker) noexcept { + auto ensure_extent = [](std::span value) noexcept + -> expected_type<> { + if constexpr (Extent != std::dynamic_extent) { + if (value.size() != Extent) { + return tl::make_unexpected(msgpack::error::wrong_length); + } + return value.template first(); + } else { + return value; + } + }; + return unpack_bytes(unpacker).and_then(ensure_extent); + } +}; + +template + requires(std::same_as> && Extent > 0) +struct builtin_unpacker> { + using expected_type = tl::expected, msgpack::error>; + + static constexpr expected_type unpack(auto& unpacker) noexcept { + constexpr auto copy_to_array = [](auto span) noexcept -> expected_type { + if (span.size() != Extent) { + return tl::make_unexpected(msgpack::error::wrong_length); + } else { + std::array dest; + std::copy(span.begin(), span.end(), dest.begin()); + return dest; + } + }; + + return builtin_unpacker>::unpack(unpacker) + .and_then(copy_to_array); + } +}; } // namespace msgpack diff --git a/include/parselink/msgpack/core/detail/unpackable_concepts.h b/include/parselink/msgpack/core/detail/unpackable_concepts.h index 835f620..9277dbe 100644 --- a/include/parselink/msgpack/core/detail/unpackable_concepts.h +++ b/include/parselink/msgpack/core/detail/unpackable_concepts.h @@ -18,6 +18,10 @@ #ifndef msgpack_core_detail_unpackable_concepts_cf142e37b34a598f #define msgpack_core_detail_unpackable_concepts_cf142e37b34a598f +#include "parselink/msgpack/core/error.h" + +#include + #include #include #include @@ -29,6 +33,8 @@ namespace detail { // whether a particular type is considered packable or not. struct unpacker_context_model { std::byte* in; + + std::size_t remaining() { return 1; } }; } // namespace detail @@ -42,15 +48,15 @@ struct builtin_unpacker; template struct unpacker_adapter; +template +concept is_any_of = (std::same_as || ...); + template typename Unpacker, typename Context = detail::unpacker_context_model> concept unpackable_with = requires(T&& value, Context& dest) { - //{ - //Unpacker>::total_size(value) - //} -> std::same_as; { Unpacker>::unpack(dest) - } -> std::same_as; + } -> is_any_of>; }; template @@ -64,21 +70,21 @@ concept supports_unpack = builtin_unpackable || custom_unpackable; namespace detail { - template - struct unpacker_impl_s; +template +struct unpacker_impl_s; - template - struct unpacker_impl_s { - using type = unpacker_adapter; - }; +template +struct unpacker_impl_s { + using type = unpacker_adapter; +}; - template - struct unpacker_impl_s { - using type = builtin_unpacker; - }; +template +struct unpacker_impl_s { + using type = builtin_unpacker; +}; - template - using unpacker_impl = unpacker_impl_s::type; +template +using unpacker_impl = unpacker_impl_s::type; } // namespace detail } // namespace msgpack diff --git a/include/parselink/msgpack/core/error.h b/include/parselink/msgpack/core/error.h index 07c8a88..681c15b 100644 --- a/include/parselink/msgpack/core/error.h +++ b/include/parselink/msgpack/core/error.h @@ -29,6 +29,7 @@ enum class error { wrong_type, // The format is incompatible with requested type. bad_value, // Value does not fit within constraints. will_truncate, // Integer return type is smaller than stored value. + wrong_length, // Variable-length format does not match desired length. }; } // namespace msgpack diff --git a/include/parselink/msgpack/core/format.h b/include/parselink/msgpack/core/format.h index 845b1f8..29ba6a2 100644 --- a/include/parselink/msgpack/core/format.h +++ b/include/parselink/msgpack/core/format.h @@ -31,14 +31,14 @@ namespace msgpack { // Supporting types/tags for formats //----------------------------------------------------------------------------- struct nil { - nil() = default; - // This constructor is used by the reader implementation. - nil(auto){}; + constexpr nil() noexcept = default; + constexpr bool operator==(nil const& other) const noexcept = default; }; -struct invalid {}; - -using boolean = bool; +struct invalid { + constexpr invalid() noexcept = default; + constexpr bool operator==(invalid const& other) const noexcept = default; +}; struct array_desc { constexpr array_desc() noexcept = default; @@ -225,12 +225,12 @@ struct int64 : format<0xd3, std::int64_t> {}; */ struct nil : fixtype<0xc0, 0x00> { using value_type = msgpack::nil; - constexpr static flag flags{flag::fixed_size | flag::apply_mask}; + constexpr static flag flags{flag::fixed_size}; }; struct invalid : fixtype<0xc1, 0x00> { using value_type = msgpack::invalid; - constexpr static flag flags{flag::fixed_size | flag::apply_mask}; + constexpr static flag flags{flag::fixed_size}; }; struct boolean : fixtype<0xc2, 0x01> { diff --git a/include/parselink/msgpack/core/unpacker.h b/include/parselink/msgpack/core/unpacker.h index d118ed0..0a11dd9 100644 --- a/include/parselink/msgpack/core/unpacker.h +++ b/include/parselink/msgpack/core/unpacker.h @@ -79,6 +79,10 @@ public: return ret; } + constexpr auto remaining() noexcept { + return std::ranges::distance(curr_, end_); + } + private: BufferType buff_; decltype(std::begin(buff_)) curr_; diff --git a/include/parselink/msgpack/extra/formatters.h b/include/parselink/msgpack/extra/formatters.h new file mode 100644 index 0000000..9227220 --- /dev/null +++ b/include/parselink/msgpack/extra/formatters.h @@ -0,0 +1,121 @@ +//----------------------------------------------------------------------------- +// ___ __ _ _ +// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __ +// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ / +// / ___/ (_| | | \__ \ __/ /__| | | | | < +// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ . +// +//----------------------------------------------------------------------------- +// Author: Kurt Sassenrath +// Module: msgpack +// +// Extra functionality: fmt formatters for msgpack types. +// +// Copyright (c) 2023 Kurt Sassenrath. +// +// License TBD. +//----------------------------------------------------------------------------- +#ifndef msgpack_extra_formatters_d5fd427a7f749dcb +#define msgpack_extra_formatters_d5fd427a7f749dcb + +#define FMT_VERSION + +#ifdef FMT_VERSION + +#include "parselink/msgpack/core/format.h" + +#include + +template <> +struct fmt::formatter : fmt::formatter { + template + auto format(msgpack::nil const&, FormatContext& ctx) const noexcept { + return fmt::format_to(ctx.out(), "{{msgpack::invalid}}"); + }; +}; + +template <> +struct fmt::formatter : fmt::formatter { + template + auto format(msgpack::nil const&, FormatContext& ctx) const { + return fmt::format_to(ctx.out(), "{{msgpack::nil}}"); + } +}; + +template <> +struct fmt::formatter : fmt::formatter { + template + auto format(msgpack::map_desc const& value, FormatContext& ctx) const { + return fmt::format_to( + ctx.out(), "{{msgpack::map_desc: {}}}", value.count); + } +}; + +template <> +struct fmt::formatter : fmt::formatter { + template + auto format(msgpack::array_desc const& value, FormatContext& ctx) const { + return fmt::format_to( + ctx.out(), "{{msgpack::array_desc: {}}}", value.count); + } +}; + +#ifdef PARSELINK_MSGPACK_TOKEN_API + +template <> +struct fmt::formatter { + template + constexpr auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { + return ctx.begin(); + } + + template + auto format(msgpack::token const& v, FormatContext& ctx) const { + using parselink::logging::themed_arg; + auto out = fmt::format_to( + ctx.out(), "()))); + break; + case msgpack::format::type::signed_int: + out = fmt::format_to( + out, "{}", themed_arg(*(v.get()))); + break; + case msgpack::format::type::boolean: + out = fmt::format_to(out, "{}", themed_arg(*(v.get()))); + break; + case msgpack::format::type::string: + out = fmt::format_to( + out, "{}", themed_arg(*(v.get()))); + break; + case msgpack::format::type::binary: + out = fmt::format_to(out, "{}", + themed_arg(*(v.get>()))); + break; + case msgpack::format::type::map: + out = fmt::format_to(out, "(arity: {})", + themed_arg(v.get()->count)); + break; + case msgpack::format::type::array: + out = fmt::format_to(out, "(arity: {})", + themed_arg(v.get()->count)); + break; + case msgpack::format::type::nil: + out = fmt::format_to(out, "(nil)"); + break; + case msgpack::format::type::invalid: + out = fmt::format_to(out, "(invalid)"); + break; + default: break; + } + return fmt::format_to(out, ">"); + } +}; + +#endif // PARSELINK_MSGPACK_TOKEN_API + +#endif // FMT_VERSION + +#endif // msgpack_extra_formatters_d5fd427a7f749dcb diff --git a/source/server.cpp b/source/server.cpp index 4f26ede..9d1c682 100644 --- a/source/server.cpp +++ b/source/server.cpp @@ -365,7 +365,7 @@ monolithic_server::load_keys() noexcept { }; return utility::file::open(filename, O_RDWR | O_CREAT) - .and_then(utility::file::read) + .and_then(utility::file::read) .and_then(load_key) .or_else(generate_keys); } diff --git a/tests/msgpack/BUILD b/tests/msgpack/BUILD index 225c7e3..58eb902 100644 --- a/tests/msgpack/BUILD +++ b/tests/msgpack/BUILD @@ -1,10 +1,11 @@ cc_library( - name = "test_deps", + name = "common", srcs = [ "test_main.cpp", - "rng.h", ], + includes = ["include"], + hdrs = glob(["include/*.h"]), deps = [ "//include/parselink:msgpack", "@expected", @@ -12,6 +13,7 @@ cc_library( "@magic_enum", "@ut", ], + visibility = ["__subpackages__"], ) cc_test( @@ -21,7 +23,7 @@ cc_test( "test_reader_relaxed.cpp", "test_reader_strict.cpp", ], - deps = ["test_deps"], + deps = ["common"], ) cc_test( @@ -30,7 +32,7 @@ cc_test( srcs = [ "test_packer.cpp", ], - deps = ["test_deps"], + deps = ["common"], ) cc_test( @@ -39,7 +41,7 @@ cc_test( srcs = [ "test_packer_rc.cpp", ], - deps = ["test_deps", "@rapidcheck"], + deps = ["common", "@rapidcheck"], ) cc_test( @@ -48,7 +50,7 @@ cc_test( srcs = [ "test_writer.cpp", ], - deps = ["test_deps"], + deps = ["common"], ) cc_test( @@ -57,7 +59,7 @@ cc_test( srcs = [ "test_token.cpp", ], - deps = ["test_deps"], + deps = ["common"], ) cc_test( @@ -66,7 +68,7 @@ cc_test( srcs = [ "test_token_reader.cpp", ], - deps = ["test_deps"], + deps = ["common"], ) cc_test( @@ -75,7 +77,7 @@ cc_test( srcs = [ "test_token_views.cpp", ], - deps = ["test_deps"], + deps = ["common"], ) cc_binary( @@ -83,7 +85,7 @@ cc_binary( srcs = [ "test_speed.cpp", ], - deps = ["test_deps"], + deps = ["common"], ) cc_binary( @@ -91,5 +93,5 @@ cc_binary( srcs = [ "test_code.cpp", ], - deps = ["test_deps"], + deps = ["common"], ) diff --git a/tests/msgpack/include/test_utils.h b/tests/msgpack/include/test_utils.h new file mode 100644 index 0000000..2e2812a --- /dev/null +++ b/tests/msgpack/include/test_utils.h @@ -0,0 +1,102 @@ +#ifndef msgpack_test_utils_4573e6627d8efe78 +#define msgpack_test_utils_4573e6627d8efe78 + +// clang-format off : Must include fmt/format before extra/formatters. +#include + +#include "parselink/msgpack/extra/formatters.h" +// clang-format on + +#include + +#include +#include +#include + +#include + +namespace test { + +template +inline constexpr std::array as_bytes(T&& t) { + return std::bit_cast>(std::forward(t)); +} + +template +inline constexpr std::array make_bytes( + Bytes&&... bytes) noexcept { + return {std::byte(std::forward(bytes))...}; +} + +} // namespace test + +// This formatter allows expected values to be formatted for both its expected +// and unexpected types. +template +struct fmt::formatter> { + int width = 0; + + fmt::formatter t_fmtr; + fmt::formatter e_fmtr; + + constexpr auto parse(auto& ctx) { + using char_type = + typename std::remove_reference_t::char_type; + auto delim = char_type{'|'}; + auto close_brace = char_type{'}'}; + std::array fmtbuf; + + if (ctx.begin() == ctx.end() || *ctx.begin() == '}') { + return ctx.begin(); + } + + bool t_parsed = false; + + auto itr = ctx.begin(); + auto buf = fmtbuf.begin(); + + while (*itr != close_brace) { + if (*itr == delim) { + if (t_parsed) { + fmt::throw_format_error("multiple delims encountered"); + } + t_parsed = true; + *buf = close_brace; + auto str = basic_string_view( + fmtbuf.data(), std::distance(fmtbuf.begin(), buf)); + basic_format_parse_context subparser(str, 0); + t_fmtr.parse(subparser); + buf = fmtbuf.begin(); + } else { + *buf = *itr; + ++buf; + } + ++itr; + } + + *buf = close_brace; + auto str = basic_string_view( + fmtbuf.data(), std::distance(fmtbuf.begin(), buf)); + basic_format_parse_context subparser(str, 0); + if (t_parsed) { + e_fmtr.parse(subparser); + } else { + t_fmtr.parse(subparser); + } + + ctx.advance_to(itr); + + return itr; + } + + auto format(tl::expected const& v, auto& ctx) const { + if (v) { + // return fmt::format_to(ctx.out(), "{}", v.value()); + return t_fmtr.format(v.value(), ctx); + } else { + return e_fmtr.format(v.error(), ctx); + } + } +}; + +#endif // msgpack_test_utils_4573e6627d8efe78 diff --git a/tests/msgpack/test_utils.h b/tests/msgpack/test_utils.h deleted file mode 100644 index 8b13270..0000000 --- a/tests/msgpack/test_utils.h +++ /dev/null @@ -1,100 +0,0 @@ -#ifndef msgpack_test_utils_4573e6627d8efe78 -#define msgpack_test_utils_4573e6627d8efe78 - -#include -#include - -#include - -template -constexpr bool operator==(std::span a, std::span b) noexcept { - return std::equal(a.begin(), a.end(), b.begin(), b.end()); -} - -template -constexpr std::array as_bytes(T&& t) { - return std::bit_cast>(std::forward(t)); -} - -template -constexpr std::array make_bytes(Bytes&&... bytes) { - return {std::byte(std::forward(bytes))...}; -} - -template -struct oversized_array { - std::array data; - std::size_t size; -}; - -constexpr auto to_bytes_array_oversized(auto const& container) { - using value_type = std::decay_t; - oversized_array arr; - std::copy(std::begin(container), std::end(container), std::begin(arr.data)); - arr.size = std::distance(std::begin(container), std::end(container)); - return arr; -} - -consteval auto generate_bytes(auto callable) { - constexpr auto oversized = to_bytes_array_oversized(callable()); - using value_type = std::decay_t; - std::array out; - std::copy(std::begin(oversized.data), - std::next(std::begin(oversized.data), oversized.size), - std::begin(out)); - return out; -} - -consteval auto build_string(auto callable) { - constexpr auto string_array = generate_bytes(callable); - return string_array; -} - -template -constexpr auto cat(std::array const&... a) noexcept { - std::array out; - std::size_t index{}; - ((std::copy_n(a.begin(), Sizes, out.begin() + index), index += Sizes), ...); - return out; -} - -constexpr auto repeat(std::span sv, std::size_t count) { - std::vector range; - range.reserve(sv.size() * count); - for (decltype(count) i = 0; i < count; ++i) { - std::copy_n(sv.begin(), sv.size(), std::back_inserter(range)); - } - return range; -} - -constexpr auto repeat(std::string_view sv, std::size_t count) { - std::vector range; - range.reserve(sv.size() * count); - for (decltype(count) i = 0; i < count; ++i) { - std::copy_n(sv.begin(), sv.size(), std::back_inserter(range)); - } - return range; -} - -constexpr auto from_string_view(std::string_view sv) { - std::vector range; - range.resize(sv.size()); - auto itr = range.begin(); - for (auto c : sv) { - *itr = std::byte(c); - ++itr; - } - return range; -} - -template -std::ostream& operator<<(std::ostream& os, tl::expected const& exp) { - if (exp.has_value()) { - os << "Value: '" << *exp << "'"; - } else { - os << "Error"; - } - return os; -} - -#endif // msgpack_test_utils_4573e6627d8efe78 diff --git a/tests/msgpack/unpacker/BUILD b/tests/msgpack/unpacker/BUILD new file mode 100644 index 0000000..ba9fa0a --- /dev/null +++ b/tests/msgpack/unpacker/BUILD @@ -0,0 +1,9 @@ +cc_test( + name = "unpacker", + size = "small", + srcs = glob([ + "*.cpp", + "*.h", + ]), + deps = ["//tests/msgpack:common", "@rapidcheck"], +) diff --git a/tests/msgpack/unpacker/bytes.cpp b/tests/msgpack/unpacker/bytes.cpp new file mode 100644 index 0000000..633baaa --- /dev/null +++ b/tests/msgpack/unpacker/bytes.cpp @@ -0,0 +1,68 @@ +//----------------------------------------------------------------------------- +// ___ __ _ _ +// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __ +// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ / +// / ___/ (_| | | \__ \ __/ /__| | | | | < +// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ . +// +//----------------------------------------------------------------------------- +// Author: Kurt Sassenrath +// Module: msgpack +// +// Unpacker tests for bytes. +// +// Copyright (c) 2023 Kurt Sassenrath. +// +// License TBD. +//----------------------------------------------------------------------------- +#include "test_unpacker.h" + +using namespace boost::ut; + +suite unpack_byte_types = [] { + // TODO: Make non-const version? + "unpacker::unpack>"_test = [] { + constexpr auto payload = + test::make_bytes(0xc4, 0x05, 0x01, 0x02, 0x03, 0x04, 0x05); + constexpr auto expected = + test::make_bytes(0x01, 0x02, 0x03, 0x04, 0x05); + { + msgpack::unpacker unpacker(payload); + auto actual = unpacker.unpack>(); + expect(std::ranges::equal(*actual, expected)); + } + { + // Test extent + msgpack::unpacker unpacker(payload); + auto actual = unpacker.unpack>(); + expect(std::ranges::equal(*actual, expected)); + } + { + msgpack::unpacker unpacker(payload); + auto actual = unpacker.unpack>(); + expect(actual == tl::make_unexpected(msgpack::error::wrong_length)); + auto actual2 = unpacker.unpack>(); + expect(std::ranges::equal(*actual2, expected)); + } + }; + + // TODO: Make non-const version? + "unpacker::unpack>"_test = [] { + constexpr auto payload = + test::make_bytes(0xc4, 0x05, 0x01, 0x02, 0x03, 0x04, 0x06); + constexpr auto expected = + test::make_bytes(0x01, 0x02, 0x03, 0x04, 0x06); + { + msgpack::unpacker unpacker(payload); + auto actual = unpacker.unpack>(); + expect(std::ranges::equal(*actual, expected)); + } + { + msgpack::unpacker unpacker(payload); + auto actual = unpacker.unpack>(); + expect(actual == tl::make_unexpected(msgpack::error::wrong_length)); + auto actual2 = unpacker.unpack>(); + expect(std::ranges::equal(*actual2, expected)); + } + }; +}; diff --git a/tests/msgpack/unpacker/signed.cpp b/tests/msgpack/unpacker/signed.cpp new file mode 100644 index 0000000..ad92b70 --- /dev/null +++ b/tests/msgpack/unpacker/signed.cpp @@ -0,0 +1,105 @@ +//----------------------------------------------------------------------------- +// ___ __ _ _ +// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __ +// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ / +// / ___/ (_| | | \__ \ __/ /__| | | | | < +// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ . +// +//----------------------------------------------------------------------------- +// Author: Kurt Sassenrath +// Module: msgpack +// +// Unpacker tests for signed integer values +// +// Copyright (c) 2023 Kurt Sassenrath. +// +// License TBD. +//----------------------------------------------------------------------------- +#include "test_unpacker.h" + +#include + +using namespace boost::ut; + +namespace { + +template +struct get_marker; + +template <> +struct get_marker { + static constexpr std::byte marker = msgpack::format::int8::marker; +}; + +template <> +struct get_marker { + static constexpr std::byte marker = msgpack::format::int16::marker; +}; + +template <> +struct get_marker { + static constexpr std::byte marker = msgpack::format::int32::marker; +}; + +template <> +struct get_marker { + static constexpr std::byte marker = msgpack::format::int64::marker; +}; + +// Utilize rapidcheck to generate random packed payloads, and unpacking the +// correct value +template +auto check_unpack_integer() noexcept { + auto result = rc::check([](PackType value) { + std::vector payload = {get_marker::marker}; + auto bytes = host_to_be( + std::bit_cast>(value)); + std::copy(bytes.begin(), bytes.end(), std::back_inserter(payload)); + msgpack::unpacker unpacker(payload); + auto unpacked_value = unpacker.unpack(); + if (std::numeric_limits::max() < value + || std::numeric_limits::min() > value) { + return unpacked_value + == tl::make_unexpected(msgpack::error::will_truncate); + } else { + return unpacked_value.has_value() && unpacked_value == value; + } + }); + if (!result) { + fmt::print("Failed on {}\n", + std::source_location::current().function_name()); + } + return result; +} + +} // anonymous namespace + +suite unpack_signed_types = [] { + "unpacker::unpack"_test = [] { + expect(check_unpack_integer()); + expect(check_unpack_integer()); + expect(check_unpack_integer()); + expect(check_unpack_integer()); + }; + + "unpacker::unpack"_test = [] { + expect(check_unpack_integer()); + expect(check_unpack_integer()); + expect(check_unpack_integer()); + expect(check_unpack_integer()); + }; + + "unpacker::unpack"_test = [] { + expect(check_unpack_integer()); + expect(check_unpack_integer()); + expect(check_unpack_integer()); + expect(check_unpack_integer()); + }; + + "unpacker::unpack"_test = [] { + expect(check_unpack_integer()); + expect(check_unpack_integer()); + expect(check_unpack_integer()); + expect(check_unpack_integer()); + }; +}; diff --git a/tests/msgpack/unpacker/simple_types.cpp b/tests/msgpack/unpacker/simple_types.cpp new file mode 100644 index 0000000..ae099ba --- /dev/null +++ b/tests/msgpack/unpacker/simple_types.cpp @@ -0,0 +1,61 @@ +//----------------------------------------------------------------------------- +// ___ __ _ _ +// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __ +// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ / +// / ___/ (_| | | \__ \ __/ /__| | | | | < +// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ . +// +//----------------------------------------------------------------------------- +// Author: Kurt Sassenrath +// Module: msgpack +// +// Default packer tests. +// +// Copyright (c) 2023 Kurt Sassenrath. +// +// License TBD. +//----------------------------------------------------------------------------- + +#include "test_unpacker.h" + +#include + +using namespace boost::ut; + +namespace {} // anonymous namespace + +suite unpacker_simple_types = [] { + "unpacker::unpack"_test = [] { + tl::expected x( + tl::make_unexpected(msgpack::error::end_of_message)); + static constexpr auto payload = test::make_bytes(0xc1, 0xc1); + msgpack::unpacker unpacker(payload); + expect(type_check_unpacker(unpacker)); + expect(unpacker.unpack() == msgpack::invalid{}); + expect(unpacker.unpack() == msgpack::invalid{}); + expect(unpacker.unpack() + == tl::make_unexpected(msgpack::error::end_of_message)); + }; + + "unpacker::unpack"_test = [] { + static constexpr auto payload = test::make_bytes(0xc0, 0xc0); + msgpack::unpacker unpacker(payload); + expect(type_check_unpacker(unpacker)); + expect(unpacker.unpack() == msgpack::nil{}); + expect(unpacker.unpack() == msgpack::nil{}); + expect(unpacker.unpack() + == tl::make_unexpected(msgpack::error::end_of_message)); + }; + + "unpacker::unpack"_test = [] { + constexpr auto payload = test::make_bytes(0xc3, 0xc2); + msgpack::unpacker unpacker(payload); + expect(type_check_unpacker(unpacker)); + auto value = unpacker.unpack(); + expect(value && *value == true); + value = unpacker.unpack(); + expect(value && *value == false); + expect(unpacker.unpack() + == tl::make_unexpected(msgpack::error::end_of_message)); + }; +}; diff --git a/tests/msgpack/unpacker/strings.cpp b/tests/msgpack/unpacker/strings.cpp new file mode 100644 index 0000000..79ddd1b --- /dev/null +++ b/tests/msgpack/unpacker/strings.cpp @@ -0,0 +1,31 @@ +//----------------------------------------------------------------------------- +// ___ __ _ _ +// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __ +// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ / +// / ___/ (_| | | \__ \ __/ /__| | | | | < +// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ . +// +//----------------------------------------------------------------------------- +// Author: Kurt Sassenrath +// Module: msgpack +// +// Unpacker tests for strings. +// +// Copyright (c) 2023 Kurt Sassenrath. +// +// License TBD. +//----------------------------------------------------------------------------- +#include "test_unpacker.h" + +using namespace boost::ut; + +suite unpack_string_types = [] { + "unpacker::unpack"_test = [] { + static constexpr auto payload = test::make_bytes(0xa2, 'H', 'i'); + msgpack::unpacker unpacker(payload); + expect(type_check_unpacker(unpacker)); + expect(unpacker.unpack() == "Hi"); + expect(unpacker.unpack() + == tl::make_unexpected(msgpack::error::end_of_message)); + }; +}; diff --git a/tests/msgpack/unpacker/test_unpacker.h b/tests/msgpack/unpacker/test_unpacker.h new file mode 100644 index 0000000..64f19fc --- /dev/null +++ b/tests/msgpack/unpacker/test_unpacker.h @@ -0,0 +1,57 @@ +//----------------------------------------------------------------------------- +// ___ __ _ _ +// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __ +// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ / +// / ___/ (_| | | \__ \ __/ /__| | | | | < +// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ . +// +//----------------------------------------------------------------------------- +// Author: Kurt Sassenrath +// Module: msgpack +// +// Unpacker tests, common code. +// +// Copyright (c) 2023 Kurt Sassenrath. +// +// License TBD. +//----------------------------------------------------------------------------- +#ifndef tests_msgpack_unpacker_25b118dd14e2b1b4 +#define tests_msgpack_unpacker_25b118dd14e2b1b4 + +#include "parselink/msgpack/core/unpacker.h" + +#include "test_utils.h" + +// Helper template to check that a type is either equal to itself, or verify +// that it won't unpack correctly. +template +constexpr bool unpack_type_check(msgpack::unpacker unpacker) noexcept { + auto wrong_type = tl::make_unexpected(msgpack::error::wrong_type); + auto compatible_type = std::same_as + || (std::is_unsigned_v && std::is_unsigned_v) + || (std::is_signed_v && std::is_signed_v); + auto result = compatible_type || unpacker.unpack() == wrong_type; + if (!result) { + fmt::print("Unpack_type_check failed: {}\n", + std::source_location::current().function_name()); + } + return result; +} + +// Validate the wrong types do not unpack correctly, or fail. +template +constexpr bool try_unpacking_types(msgpack::unpacker unpacker) noexcept { + return ((unpack_type_check(unpacker)) && ...); +} + +#define SUPPORTED_TYPES \ + msgpack::nil, msgpack::invalid, bool, std::uint8_t, std::uint16_t, \ + std::uint32_t, std::uint64_t, std::int8_t, std::int16_t, \ + std::int32_t, std::int64_t, std::string_view + +template +constexpr bool type_check_unpacker(msgpack::unpacker unpacker) noexcept { + return try_unpacking_types(unpacker); +} + +#endif // tests_msgpack_unpacker_25b118dd14e2b1b4 diff --git a/tests/msgpack/unpacker/unsigned.cpp b/tests/msgpack/unpacker/unsigned.cpp new file mode 100644 index 0000000..2e3d98e --- /dev/null +++ b/tests/msgpack/unpacker/unsigned.cpp @@ -0,0 +1,131 @@ +//----------------------------------------------------------------------------- +// ___ __ _ _ +// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __ +// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ / +// / ___/ (_| | | \__ \ __/ /__| | | | | < +// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ . +// +//----------------------------------------------------------------------------- +// Author: Kurt Sassenrath +// Module: msgpack +// +// Default packer tests. +// +// Copyright (c) 2023 Kurt Sassenrath. +// +// License TBD. +//----------------------------------------------------------------------------- +#include "test_unpacker.h" + +#include + +using namespace boost::ut; + +namespace { + +constexpr auto equal(auto a, auto b) { + return std::equal(std::begin(a), std::end(a), std::begin(b), std::end(b)); +} + +template +constexpr std::array make_bytes(Bytes&&... bytes) { + return {std::byte(std::forward(bytes))...}; +} + +template +constexpr bool test_unpack(msgpack::unpacker unpacker) noexcept { + auto wrong_type = tl::make_unexpected(msgpack::error::wrong_type); + return std::same_as || unpacker.unpack() == wrong_type; +} + +template +constexpr bool test_types(msgpack::unpacker unpacker) noexcept { + return ((test_unpack(unpacker)) && ...); +} + +template +constexpr bool expect_invalid(msgpack::unpacker unpacker) noexcept { + return test_types(unpacker); +} + +template +struct get_marker; + +template <> +struct get_marker { + static constexpr std::byte marker = msgpack::format::uint8::marker; +}; + +template <> +struct get_marker { + static constexpr std::byte marker = msgpack::format::uint16::marker; +}; + +template <> +struct get_marker { + static constexpr std::byte marker = msgpack::format::uint32::marker; +}; + +template <> +struct get_marker { + static constexpr std::byte marker = msgpack::format::uint64::marker; +}; + +// Utilize rapidcheck to generate random packed payloads, and unpacking the +// correct value +template +auto check_unpack_integer() noexcept { + auto result = rc::check([](PackType value) { + std::vector payload = {get_marker::marker}; + auto bytes = host_to_be( + std::bit_cast>(value)); + std::copy(bytes.begin(), bytes.end(), std::back_inserter(payload)); + msgpack::unpacker unpacker(payload); + auto unpacked_value = unpacker.unpack(); + if (std::numeric_limits::max() < value + || std::numeric_limits::min() > value) { + return unpacked_value + == tl::make_unexpected(msgpack::error::will_truncate); + } else { + return unpacked_value.has_value() && unpacked_value == value; + } + }); + if (!result) { + fmt::print("Failed on {}\n", + std::source_location::current().function_name()); + } + return result; +} + +} // anonymous namespace + +suite unpack_unsigned_types = [] { + "unpacker::unpack"_test = [] { + expect(check_unpack_integer()); + expect(check_unpack_integer()); + expect(check_unpack_integer()); + expect(check_unpack_integer()); + }; + + "unpacker::unpack"_test = [] { + expect(check_unpack_integer()); + expect(check_unpack_integer()); + expect(check_unpack_integer()); + expect(check_unpack_integer()); + }; + + "unpacker::unpack"_test = [] { + expect(check_unpack_integer()); + expect(check_unpack_integer()); + expect(check_unpack_integer()); + expect(check_unpack_integer()); + }; + + "unpacker::unpack"_test = [] { + expect(check_unpack_integer()); + expect(check_unpack_integer()); + expect(check_unpack_integer()); + expect(check_unpack_integer()); + }; +};