Refactor tests, add formatters, etc.

This commit is contained in:
Kurt Sassenrath 2024-01-12 11:51:25 -08:00
parent db601fb770
commit eb7a02d5c4
20 changed files with 970 additions and 167 deletions

View File

@ -18,7 +18,7 @@ BreakConstructorInitializers: BeforeComma
BreakInheritanceList: BeforeComma
ConstructorInitializerIndentWidth: 8
ContinuationIndentWidth: 8
IncludeBlocks: Regroup
#IncludeBlocks: Regroup
IncludeCategories:
- Regex: '^"parselink/'
Priority: 1

View File

@ -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,
)

View File

@ -118,7 +118,6 @@ struct fmt::formatter<T> : fmt::formatter<void const*> {
}
};
// TODO(ksassenrath): Re-enable when expected has been integrated
template <typename T, typename Err>
struct fmt::formatter<tl::expected<T, Err>> {
template <typename ParseContext>

View File

@ -32,6 +32,20 @@
namespace msgpack {
namespace detail {
// Generic mechanism for reading a number of bytes out of an interator.
template <std::size_t N, typename Itr>
constexpr inline decltype(auto) read_bytes(Itr& inp) noexcept {
std::array<std::byte, N> 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 <std::integral T, typename Itr>
constexpr decltype(auto) unpack_integral(Itr& inp) noexcept {
@ -48,6 +62,83 @@ template <typename T>
concept byte_range = std::ranges::contiguous_range<T>
&& std::same_as<std::ranges::range_value_t<T>, std::byte>;
template <format_type F, typename Iter>
constexpr inline bool accept_format(Iter& itr) noexcept {
if constexpr (is_fixtype<F>) {
// 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 <typename T, typename Iter>
struct iterator_adapter {
static constexpr auto convert(Iter itr) { return itr; }
};
template <typename Iter>
struct iterator_adapter<std::string_view, Iter> {
static constexpr auto convert(Iter itr) {
return ::detail::value_cast<std::string_view::pointer>(&*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 <format_type F>
constexpr inline auto unpack_format(auto& unpacker) noexcept
-> tl::expected<typename F::value_type, error> {
if (!accept_format<F>(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<first_type>(unpacker.in);
if constexpr (is_fixtype<F> && (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<value_type, decltype(f)>) {
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<value_type, decltype(unpacker.in)>;
auto value = value_type(adapt::convert(unpacker.in), f);
unpacker.in += f;
return value;
}
}
} // namespace detail
template <typename>
@ -58,64 +149,179 @@ struct builtin_unpacker;
////////////////////////////////////////////////////////////////////////////////
template <>
struct builtin_unpacker<invalid> {
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<format::invalid>(unpacker);
}
};
template <>
struct builtin_unpacker<nil> {
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<format::nil>(unpacker);
}
};
template <>
struct builtin_unpacker<bool> {
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<bool>((*(unpacker.in)++) & ~format::boolean::mask);
return detail::unpack_format<format::boolean>(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<std::uint64_t, msgpack::error> {
auto marker = *(unpacker.in);
if ((marker & std::byte{0x80}) == std::byte{}) {
return detail::unpack_format<format::positive_fixint>(unpacker);
}
switch (marker) {
case format::uint8::marker:
return detail::unpack_format<format::uint8>(unpacker);
case format::uint16::marker:
return detail::unpack_format<format::uint16>(unpacker);
case format::uint32::marker:
return detail::unpack_format<format::uint32>(unpacker);
case format::uint64::marker:
return true;
default:
break;
return detail::unpack_format<format::uint64>(unpacker);
}
return false;
return tl::make_unexpected(msgpack::error::wrong_type);
}
};
template <std::unsigned_integral T>
struct builtin_unpacker<T> {
static constexpr auto max = std::numeric_limits<T>::max();
static constexpr auto unpack(auto& unpacker) noexcept {
constexpr auto convert =
[](auto value) -> tl::expected<T, msgpack::error> {
if (value > max) {
return tl::make_unexpected(error::will_truncate);
}
return static_cast<T>(value);
};
return unsigned_int_unpacker::unpack(unpacker).and_then(convert);
}
};
struct signed_int_unpacker {
static constexpr auto unpack(auto& unpacker) noexcept
-> tl::expected<std::int64_t, msgpack::error> {
auto marker = *(unpacker.in);
if ((marker & std::byte{0x80}) == std::byte{}) {
return detail::unpack_format<format::positive_fixint>(unpacker);
} else if ((marker & std::byte{0xe0}) == std::byte{0xe0}) {
return detail::unpack_format<format::negative_fixint>(unpacker);
}
switch (marker) {
case format::int8::marker:
return detail::unpack_format<format::int8>(unpacker);
case format::int16::marker:
return detail::unpack_format<format::int16>(unpacker);
case format::int32::marker:
return detail::unpack_format<format::int32>(unpacker);
case format::int64::marker:
return detail::unpack_format<format::int64>(unpacker);
}
return tl::make_unexpected(msgpack::error::wrong_type);
}
};
template <std::signed_integral T>
struct builtin_unpacker<T> {
static constexpr bool accept(std::byte marker) noexcept {
static constexpr auto max = std::numeric_limits<T>::max();
static constexpr auto min = std::numeric_limits<T>::min();
static constexpr auto unpack(auto& unpacker) noexcept {
constexpr auto convert =
[](auto value) -> tl::expected<T, msgpack::error> {
if (value > max || value < min) {
return tl::make_unexpected(error::will_truncate);
}
return static_cast<T>(value);
};
return signed_int_unpacker::unpack(unpacker).and_then(convert);
}
}
};
template <>
struct builtin_unpacker<std::string_view> {
static constexpr auto unpack(auto& unpacker) noexcept
-> tl::expected<std::string_view, msgpack::error> {
auto marker = *(unpacker.in);
if ((marker & ~format::fixstr::mask) == format::fixstr::marker) {
return detail::unpack_format<format::fixstr>(unpacker);
}
switch (marker) {
case format::str8::marker:
return detail::unpack_format<format::str8>(unpacker);
case format::str16::marker:
return detail::unpack_format<format::str16>(unpacker);
case format::str32::marker:
return detail::unpack_format<format::str32>(unpacker);
}
return tl::make_unexpected(msgpack::error::wrong_type);
}
};
template <typename T, std::size_t Extent>
requires(std::same_as<std::byte, std::remove_const_t<T>>)
struct builtin_unpacker<std::span<T, Extent>> {
template <std::size_t E = Extent>
using expected_type = tl::expected<std::span<T, E>, msgpack::error>;
static constexpr auto unpack_bytes(auto& unpacker) noexcept
-> expected_type<std::dynamic_extent> {
auto marker = *(unpacker.in);
switch (marker) {
case format::bin8::marker:
return detail::unpack_format<format::bin8>(unpacker);
case format::bin16::marker:
return detail::unpack_format<format::bin16>(unpacker);
case format::bin32::marker:
return detail::unpack_format<format::bin32>(unpacker);
}
return tl::make_unexpected(msgpack::error::wrong_type);
}
static constexpr expected_type<> unpack(auto& unpacker) noexcept {
auto ensure_extent = [](std::span<std::byte const> 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<Extent>();
} else {
return value;
}
};
return unpack_bytes(unpacker).and_then(ensure_extent);
}
};
template <typename T, std::size_t Extent>
requires(std::same_as<std::byte, std::remove_const_t<T>> && Extent > 0)
struct builtin_unpacker<std::array<T, Extent>> {
using expected_type = tl::expected<std::array<T, Extent>, 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<std::byte, Extent> dest;
std::copy(span.begin(), span.end(), dest.begin());
return dest;
}
};
return builtin_unpacker<std::span<std::byte const>>::unpack(unpacker)
.and_then(copy_to_array);
}
};
} // namespace msgpack

View File

@ -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 <tl/expected.hpp>
#include <concepts>
#include <cstddef>
#include <type_traits>
@ -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 <typename>
struct unpacker_adapter;
template <typename T, typename... U>
concept is_any_of = (std::same_as<T, U> || ...);
template <typename T, template <typename> typename Unpacker,
typename Context = detail::unpacker_context_model>
concept unpackable_with = requires(T&& value, Context& dest) {
//{
//Unpacker<std::remove_cvref_t<T>>::total_size(value)
//} -> std::same_as<std::size_t>;
{
Unpacker<std::remove_cvref_t<T>>::unpack(dest)
} -> std::same_as<T>;
} -> is_any_of<T, tl::expected<T, msgpack::error>>;
};
template <typename T>
@ -64,21 +70,21 @@ concept supports_unpack = builtin_unpackable<T> || custom_unpackable<T>;
namespace detail {
template <typename>
struct unpacker_impl_s;
template <typename>
struct unpacker_impl_s;
template <custom_unpackable T>
struct unpacker_impl_s<T> {
using type = unpacker_adapter<T>;
};
template <custom_unpackable T>
struct unpacker_impl_s<T> {
using type = unpacker_adapter<T>;
};
template <builtin_unpackable T>
struct unpacker_impl_s<T> {
using type = builtin_unpacker<T>;
};
template <builtin_unpackable T>
struct unpacker_impl_s<T> {
using type = builtin_unpacker<T>;
};
template <typename T>
using unpacker_impl = unpacker_impl_s<T>::type;
template <typename T>
using unpacker_impl = unpacker_impl_s<T>::type;
} // namespace detail
} // namespace msgpack

View File

@ -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

View File

@ -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> {

View File

@ -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_;

View File

@ -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 <fmt/format.h>
template <>
struct fmt::formatter<msgpack::invalid> : fmt::formatter<std::string_view> {
template <typename FormatContext>
auto format(msgpack::nil const&, FormatContext& ctx) const noexcept {
return fmt::format_to(ctx.out(), "{{msgpack::invalid}}");
};
};
template <>
struct fmt::formatter<msgpack::nil> : fmt::formatter<std::string_view> {
template <typename FormatContext>
auto format(msgpack::nil const&, FormatContext& ctx) const {
return fmt::format_to(ctx.out(), "{{msgpack::nil}}");
}
};
template <>
struct fmt::formatter<msgpack::map_desc> : fmt::formatter<std::string_view> {
template <typename FormatContext>
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<msgpack::array_desc> : fmt::formatter<std::string_view> {
template <typename FormatContext>
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<msgpack::token> {
template <typename ParseContext>
constexpr auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
}
template <typename FormatContext>
auto format(msgpack::token const& v, FormatContext& ctx) const {
using parselink::logging::themed_arg;
auto out = fmt::format_to(
ctx.out(), "<msgpack {} = ", themed_arg(v.type()));
switch (v.type()) {
case msgpack::format::type::unsigned_int:
fmt::format_to(
out, "{}", themed_arg(*(v.get<std::uint64_t>())));
break;
case msgpack::format::type::signed_int:
out = fmt::format_to(
out, "{}", themed_arg(*(v.get<std::uint64_t>())));
break;
case msgpack::format::type::boolean:
out = fmt::format_to(out, "{}", themed_arg(*(v.get<bool>())));
break;
case msgpack::format::type::string:
out = fmt::format_to(
out, "{}", themed_arg(*(v.get<std::string_view>())));
break;
case msgpack::format::type::binary:
out = fmt::format_to(out, "{}",
themed_arg(*(v.get<std::span<std::byte const>>())));
break;
case msgpack::format::type::map:
out = fmt::format_to(out, "(arity: {})",
themed_arg(v.get<msgpack::map_desc>()->count));
break;
case msgpack::format::type::array:
out = fmt::format_to(out, "(arity: {})",
themed_arg(v.get<msgpack::array_desc>()->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

View File

@ -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<std::byte>)
.and_then(load_key)
.or_else(generate_keys);
}

View File

@ -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"],
)

View File

@ -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 <fmt/format.h>
#include "parselink/msgpack/extra/formatters.h"
// clang-format on
#include <tl/expected.hpp>
#include <boost/ut.hpp>
#include <magic_enum.hpp>
#include <magic_enum_format.hpp>
#include <source_location>
namespace test {
template <typename T>
inline constexpr std::array<std::byte, sizeof(T)> as_bytes(T&& t) {
return std::bit_cast<std::array<std::byte, sizeof(T)>>(std::forward<T>(t));
}
template <typename... Bytes>
inline constexpr std::array<std::byte, sizeof...(Bytes)> make_bytes(
Bytes&&... bytes) noexcept {
return {std::byte(std::forward<Bytes>(bytes))...};
}
} // namespace test
// This formatter allows expected values to be formatted for both its expected
// and unexpected types.
template <typename T, typename E>
struct fmt::formatter<tl::expected<T, E>> {
int width = 0;
fmt::formatter<T> t_fmtr;
fmt::formatter<E> e_fmtr;
constexpr auto parse(auto& ctx) {
using char_type =
typename std::remove_reference_t<decltype(ctx)>::char_type;
auto delim = char_type{'|'};
auto close_brace = char_type{'}'};
std::array<char_type, 32> 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<char_type>(
fmtbuf.data(), std::distance(fmtbuf.begin(), buf));
basic_format_parse_context<char_type> subparser(str, 0);
t_fmtr.parse(subparser);
buf = fmtbuf.begin();
} else {
*buf = *itr;
++buf;
}
++itr;
}
*buf = close_brace;
auto str = basic_string_view<char_type>(
fmtbuf.data(), std::distance(fmtbuf.begin(), buf));
basic_format_parse_context<char_type> 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<T, E> 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

View File

@ -1,100 +0,0 @@
#ifndef msgpack_test_utils_4573e6627d8efe78
#define msgpack_test_utils_4573e6627d8efe78
#include <boost/ut.hpp>
#include <magic_enum.hpp>
#include <fmt/format.h>
template <typename T>
constexpr bool operator==(std::span<T const> a, std::span<T const> b) noexcept {
return std::equal(a.begin(), a.end(), b.begin(), b.end());
}
template <typename T>
constexpr std::array<std::byte, sizeof(T)> as_bytes(T&& t) {
return std::bit_cast<std::array<std::byte, sizeof(T)>>(std::forward<T>(t));
}
template <typename... Bytes>
constexpr std::array<std::byte, sizeof...(Bytes)> make_bytes(Bytes&&... bytes) {
return {std::byte(std::forward<Bytes>(bytes))...};
}
template <typename T, std::size_t C = 1024 * 1024>
struct oversized_array {
std::array<T, C> data;
std::size_t size;
};
constexpr auto to_bytes_array_oversized(auto const& container) {
using value_type = std::decay_t<decltype(container[0])>;
oversized_array<value_type> 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<decltype(oversized.data[0])>;
std::array<value_type, oversized.size> 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 <std::size_t... Sizes>
constexpr auto cat(std::array<std::byte, Sizes> const&... a) noexcept {
std::array<std::byte, (Sizes + ...)> out;
std::size_t index{};
((std::copy_n(a.begin(), Sizes, out.begin() + index), index += Sizes), ...);
return out;
}
constexpr auto repeat(std::span<std::byte const> sv, std::size_t count) {
std::vector<std::byte> 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<char> 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<std::byte> range;
range.resize(sv.size());
auto itr = range.begin();
for (auto c : sv) {
*itr = std::byte(c);
++itr;
}
return range;
}
template <typename T, typename E>
std::ostream& operator<<(std::ostream& os, tl::expected<T, E> const& exp) {
if (exp.has_value()) {
os << "Value: '" << *exp << "'";
} else {
os << "Error";
}
return os;
}
#endif // msgpack_test_utils_4573e6627d8efe78

View File

@ -0,0 +1,9 @@
cc_test(
name = "unpacker",
size = "small",
srcs = glob([
"*.cpp",
"*.h",
]),
deps = ["//tests/msgpack:common", "@rapidcheck"],
)

View File

@ -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<std::span<std::byte const>>"_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<std::span<std::byte const>>();
expect(std::ranges::equal(*actual, expected));
}
{
// Test extent
msgpack::unpacker unpacker(payload);
auto actual = unpacker.unpack<std::span<std::byte const, 5>>();
expect(std::ranges::equal(*actual, expected));
}
{
msgpack::unpacker unpacker(payload);
auto actual = unpacker.unpack<std::span<std::byte const, 4>>();
expect(actual == tl::make_unexpected(msgpack::error::wrong_length));
auto actual2 = unpacker.unpack<std::span<std::byte const>>();
expect(std::ranges::equal(*actual2, expected));
}
};
// TODO: Make non-const version?
"unpacker::unpack<std::array<std::byte, N>>"_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<std::array<std::byte, 5>>();
expect(std::ranges::equal(*actual, expected));
}
{
msgpack::unpacker unpacker(payload);
auto actual = unpacker.unpack<std::array<std::byte, 4>>();
expect(actual == tl::make_unexpected(msgpack::error::wrong_length));
auto actual2 = unpacker.unpack<std::span<std::byte const, 5>>();
expect(std::ranges::equal(*actual2, expected));
}
};
};

View File

@ -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 <rapidcheck.h>
using namespace boost::ut;
namespace {
template <typename T>
struct get_marker;
template <>
struct get_marker<std::int8_t> {
static constexpr std::byte marker = msgpack::format::int8::marker;
};
template <>
struct get_marker<std::int16_t> {
static constexpr std::byte marker = msgpack::format::int16::marker;
};
template <>
struct get_marker<std::int32_t> {
static constexpr std::byte marker = msgpack::format::int32::marker;
};
template <>
struct get_marker<std::int64_t> {
static constexpr std::byte marker = msgpack::format::int64::marker;
};
// Utilize rapidcheck to generate random packed payloads, and unpacking the
// correct value
template <typename PackType, typename UnpackType>
auto check_unpack_integer() noexcept {
auto result = rc::check([](PackType value) {
std::vector<std::byte> payload = {get_marker<PackType>::marker};
auto bytes = host_to_be(
std::bit_cast<std::array<std::byte, sizeof(PackType)>>(value));
std::copy(bytes.begin(), bytes.end(), std::back_inserter(payload));
msgpack::unpacker unpacker(payload);
auto unpacked_value = unpacker.unpack<UnpackType>();
if (std::numeric_limits<UnpackType>::max() < value
|| std::numeric_limits<UnpackType>::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<std::int8_t>"_test = [] {
expect(check_unpack_integer<std::int8_t, std::int8_t>());
expect(check_unpack_integer<std::int16_t, std::int8_t>());
expect(check_unpack_integer<std::int32_t, std::int8_t>());
expect(check_unpack_integer<std::int64_t, std::int8_t>());
};
"unpacker::unpack<std::int16_t>"_test = [] {
expect(check_unpack_integer<std::int8_t, std::int16_t>());
expect(check_unpack_integer<std::int16_t, std::int16_t>());
expect(check_unpack_integer<std::int32_t, std::int16_t>());
expect(check_unpack_integer<std::int64_t, std::int16_t>());
};
"unpacker::unpack<std::int32_t>"_test = [] {
expect(check_unpack_integer<std::int8_t, std::int32_t>());
expect(check_unpack_integer<std::int16_t, std::int32_t>());
expect(check_unpack_integer<std::int32_t, std::int32_t>());
expect(check_unpack_integer<std::int64_t, std::int32_t>());
};
"unpacker::unpack<std::int64_t>"_test = [] {
expect(check_unpack_integer<std::int8_t, std::int64_t>());
expect(check_unpack_integer<std::int32_t, std::int64_t>());
expect(check_unpack_integer<std::int32_t, std::int64_t>());
expect(check_unpack_integer<std::int64_t, std::int64_t>());
};
};

View File

@ -0,0 +1,61 @@
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: msgpack
//
// Default packer tests.
//
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
#include "test_unpacker.h"
#include <fmt/format.h>
using namespace boost::ut;
namespace {} // anonymous namespace
suite unpacker_simple_types = [] {
"unpacker::unpack<invalid>"_test = [] {
tl::expected<int, msgpack::error> 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<msgpack::invalid>(unpacker));
expect(unpacker.unpack<msgpack::invalid>() == msgpack::invalid{});
expect(unpacker.unpack<msgpack::invalid>() == msgpack::invalid{});
expect(unpacker.unpack<msgpack::invalid>()
== tl::make_unexpected(msgpack::error::end_of_message));
};
"unpacker::unpack<nil>"_test = [] {
static constexpr auto payload = test::make_bytes(0xc0, 0xc0);
msgpack::unpacker unpacker(payload);
expect(type_check_unpacker<msgpack::nil>(unpacker));
expect(unpacker.unpack<msgpack::nil>() == msgpack::nil{});
expect(unpacker.unpack<msgpack::nil>() == msgpack::nil{});
expect(unpacker.unpack<msgpack::nil>()
== tl::make_unexpected(msgpack::error::end_of_message));
};
"unpacker::unpack<bool>"_test = [] {
constexpr auto payload = test::make_bytes(0xc3, 0xc2);
msgpack::unpacker unpacker(payload);
expect(type_check_unpacker<bool>(unpacker));
auto value = unpacker.unpack<bool>();
expect(value && *value == true);
value = unpacker.unpack<bool>();
expect(value && *value == false);
expect(unpacker.unpack<bool>()
== tl::make_unexpected(msgpack::error::end_of_message));
};
};

View File

@ -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<std::string_view>"_test = [] {
static constexpr auto payload = test::make_bytes(0xa2, 'H', 'i');
msgpack::unpacker unpacker(payload);
expect(type_check_unpacker<std::string_view>(unpacker));
expect(unpacker.unpack<std::string_view>() == "Hi");
expect(unpacker.unpack<msgpack::invalid>()
== tl::make_unexpected(msgpack::error::end_of_message));
};
};

View File

@ -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 <typename T, typename U>
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<T, U>
|| (std::is_unsigned_v<T> && std::is_unsigned_v<U>)
|| (std::is_signed_v<T> && std::is_signed_v<U>);
auto result = compatible_type || unpacker.unpack<U>() == 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 <typename Expected, typename... OtherTypes>
constexpr bool try_unpacking_types(msgpack::unpacker unpacker) noexcept {
return ((unpack_type_check<Expected, OtherTypes>(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 <typename Expected>
constexpr bool type_check_unpacker(msgpack::unpacker unpacker) noexcept {
return try_unpacking_types<Expected, SUPPORTED_TYPES>(unpacker);
}
#endif // tests_msgpack_unpacker_25b118dd14e2b1b4

View File

@ -0,0 +1,131 @@
//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: msgpack
//
// Default packer tests.
//
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
#include "test_unpacker.h"
#include <rapidcheck.h>
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 <typename... Bytes>
constexpr std::array<std::byte, sizeof...(Bytes)> make_bytes(Bytes&&... bytes) {
return {std::byte(std::forward<Bytes>(bytes))...};
}
template <typename T, typename U>
constexpr bool test_unpack(msgpack::unpacker unpacker) noexcept {
auto wrong_type = tl::make_unexpected(msgpack::error::wrong_type);
return std::same_as<T, U> || unpacker.unpack<U>() == wrong_type;
}
template <typename Expected, typename... OtherTypes>
constexpr bool test_types(msgpack::unpacker unpacker) noexcept {
return ((test_unpack<Expected, OtherTypes>(unpacker)) && ...);
}
template <typename Expected>
constexpr bool expect_invalid(msgpack::unpacker unpacker) noexcept {
return test_types<Expected, msgpack::nil, msgpack::invalid, bool,
std::uint8_t, std::string_view>(unpacker);
}
template <typename T>
struct get_marker;
template <>
struct get_marker<std::uint8_t> {
static constexpr std::byte marker = msgpack::format::uint8::marker;
};
template <>
struct get_marker<std::uint16_t> {
static constexpr std::byte marker = msgpack::format::uint16::marker;
};
template <>
struct get_marker<std::uint32_t> {
static constexpr std::byte marker = msgpack::format::uint32::marker;
};
template <>
struct get_marker<std::uint64_t> {
static constexpr std::byte marker = msgpack::format::uint64::marker;
};
// Utilize rapidcheck to generate random packed payloads, and unpacking the
// correct value
template <typename PackType, typename UnpackType>
auto check_unpack_integer() noexcept {
auto result = rc::check([](PackType value) {
std::vector<std::byte> payload = {get_marker<PackType>::marker};
auto bytes = host_to_be(
std::bit_cast<std::array<std::byte, sizeof(PackType)>>(value));
std::copy(bytes.begin(), bytes.end(), std::back_inserter(payload));
msgpack::unpacker unpacker(payload);
auto unpacked_value = unpacker.unpack<UnpackType>();
if (std::numeric_limits<UnpackType>::max() < value
|| std::numeric_limits<UnpackType>::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<std::uint8_t>"_test = [] {
expect(check_unpack_integer<std::uint8_t, std::uint8_t>());
expect(check_unpack_integer<std::uint16_t, std::uint8_t>());
expect(check_unpack_integer<std::uint32_t, std::uint8_t>());
expect(check_unpack_integer<std::uint64_t, std::uint8_t>());
};
"unpacker::unpack<std::uint16_t>"_test = [] {
expect(check_unpack_integer<std::uint8_t, std::uint16_t>());
expect(check_unpack_integer<std::uint16_t, std::uint16_t>());
expect(check_unpack_integer<std::uint32_t, std::uint16_t>());
expect(check_unpack_integer<std::uint64_t, std::uint16_t>());
};
"unpacker::unpack<std::uint32_t>"_test = [] {
expect(check_unpack_integer<std::uint8_t, std::uint32_t>());
expect(check_unpack_integer<std::uint16_t, std::uint32_t>());
expect(check_unpack_integer<std::uint32_t, std::uint32_t>());
expect(check_unpack_integer<std::uint64_t, std::uint32_t>());
};
"unpacker::unpack<std::uint64_t>"_test = [] {
expect(check_unpack_integer<std::uint8_t, std::uint64_t>());
expect(check_unpack_integer<std::uint16_t, std::uint64_t>());
expect(check_unpack_integer<std::uint32_t, std::uint64_t>());
expect(check_unpack_integer<std::uint64_t, std::uint64_t>());
};
};