parselink-old/tests/msgpack/test_packer.cpp
Kurt Sassenrath 53665b8e70 Packer WIP
2023-12-08 08:46:48 -08:00

288 lines
10 KiB
C++

//-----------------------------------------------------------------------------
// ___ __ _ _
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
// / ___/ (_| | | \__ \ __/ /__| | | | | <
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
//
//-----------------------------------------------------------------------------
// Author: Kurt Sassenrath
// Module: msgpack
//
// Default packer tests.
//
// Copyright (c) 2023 Kurt Sassenrath.
//
// License TBD.
//-----------------------------------------------------------------------------
#include "parselink/msgpack/core/packer.h"
#include <fmt/format.h>
#include <fmt/ranges.h>
#include "rng.h"
#include <boost/ut.hpp>
#include <chrono>
using namespace boost::ut;
namespace {
template <typename... Bytes>
constexpr std::array<std::byte, sizeof...(Bytes)> make_bytes(Bytes&&... bytes) {
return {static_cast<std::byte>(std::forward<Bytes>(bytes))...};
}
constexpr auto equal(auto a, auto b) {
return std::equal(std::begin(a), std::end(a), std::begin(b), std::end(b));
}
template <typename T>
struct test_data {};
template <>
struct test_data<std::uint64_t> {
static constexpr auto values = std::to_array<std::uint64_t>({
0x00, // positive fixint
0x79, // positive fixint
0x80, // uint8
0xff, // uint8
0x100, // uint16
0xffff, // uint16
0x10000, // uint32
0xffffffff, // uint32
0x100000000, // uint64
0xffffffffffffffff // uint64
});
static constexpr auto payload = make_bytes(0x00, // positive fixint
0x79, // positive fixint
0xcc, 0x80, // uint8
0xcc, 0xff, // uint8
0xcd, 0x01, 0x00, // uint16
0xcd, 0xff, 0xff, // uint16
0xce, 0x00, 0x01, 0x00, 0x00, // uint32
0xce, 0xff, 0xff, 0xff, 0xff, // uint32
0xcf, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, // uint64
0xcf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff // uint64
);
static constexpr auto valid(auto value) noexcept {
return value <= std::numeric_limits<std::uint64_t>::max();
}
};
template <std::unsigned_integral T>
struct test_data<T> : test_data<std::uint64_t> {
static constexpr auto valid(auto value) noexcept {
return value <= std::numeric_limits<T>::max();
}
};
template <>
struct test_data<std::int64_t> {
static constexpr auto values = std::to_array<std::int64_t>({
-1, // negative fixint
-32, // negative fixint
-33, // int8
-128, // int8
0, // int8
127, // int8
128, // int16
-129, // int16
-32768, // int16
32767, // int16
-32769, // int32
32768, // int32
-2147483648, // int32
2147483647, // int32
-2147483649, // int64
2147483648, // int64
std::numeric_limits<std::int64_t>::lowest(), // int64
std::numeric_limits<std::int64_t>::max(), // int64
});
static constexpr auto payload = make_bytes(0xff, // negative fixint
0xe0, // negative fixint
0xd0, 0xdf, // int8
0xd0, 0x80, // int8
0xd0, 0x0, // int8
0xd0, 0x7f, // int8
0xd1, 0x00, 0x80, // int16
0xd1, 0xff, 0x7f, // int16
0xd1, 0x80, 0x00, // int16
0xd1, 0x7f, 0xff, // int16
0xd2, 0xff, 0xff, 0x7f, 0xff, // int32
0xd2, 0x00, 0x00, 0x80, 0x00, // int32
0xd2, 0x80, 0x00, 0x00, 0x00, // int32
0xd2, 0x7f, 0xff, 0xff, 0xff, // int32
0xd3, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xff, 0xff, 0xff, // int64
0xd3, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, // int64
0xd3, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // int64
0xd3, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff // int64
);
static constexpr auto valid(auto value) noexcept {
return value <= std::numeric_limits<std::int64_t>::max()
&& value >= std::numeric_limits<std::int64_t>::lowest();
}
};
template <std::convertible_to<std::string_view> T>
struct test_data<T> {
static constexpr auto values = std::to_array<T>({
"", // fixstr
"0", // fixstr
"0123456789abcdef0123456789abcde", // fixstr
"0123456789abcdef0123456789abcdef", // str8
});
static constexpr auto payload = make_bytes(0xa0, // fixstr
0xa1, 0x30, // fixstr
// fixstr
0xbf, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x30, 0x31, 0x32, 0x33, 0x34,
0x35, 0x36, 0x37, 0x38, 0x39, 0x61, 0x62, 0x63, 0x64, 0x65,
// str8
0xd9, 0x20, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x30, 0x31, 0x32, 0x33,
0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x61, 0x62, 0x63, 0x64, 0x65,
0x66);
static constexpr auto valid(T value) noexcept {
return std::string_view(value).size()
<= std::numeric_limits<std::uint32_t>::max();
}
};
template <>
struct test_data<std::span<std::byte const>> {
static constexpr auto test1 = make_bytes(0x02, 0x03);
static constexpr auto values =
std::to_array<std::span<std::byte const>>({test1});
static constexpr auto payload = make_bytes(0xc4, 0x02, 0x02, 0x03);
static constexpr auto valid(auto value) noexcept {
return value.size() <= std::numeric_limits<std::uint32_t>::max();
}
};
template <std::signed_integral T>
struct test_data<T> : test_data<std::int64_t> {
static constexpr auto valid(auto value) noexcept {
return value <= std::numeric_limits<T>::max()
&& value >= std::numeric_limits<T>::lowest();
}
};
template <typename T>
bool test_deduced() noexcept {
constexpr auto const& expected_payload = test_data<T>::payload;
std::array<std::byte, expected_payload.size()> payload;
msgpack::packer packer(payload);
for (auto const& value : test_data<T>::values) {
if (!test_data<T>::valid(value)) break;
expect(!!packer.pack(T(value)));
auto expect = std::span(expected_payload.begin(), packer.tell());
auto correct = equal(packer.subspan(), expect);
if (!correct) {
fmt::print("Deduction failed for '{}'\n", T(value));
fmt::print("\tActual: {::#04x}\n", packer.subspan());
fmt::print("\tExpect: {::#04x}\n", expect);
return false;
}
}
return true;
}
} // anonymous namespace
suite packer = [] {
"packer::pack<std::uint8_t>"_test = [] {
expect(test_deduced<std::uint8_t>());
};
"packer::pack<std::uint16_t>"_test = [] {
expect(test_deduced<std::uint16_t>());
};
"packer::pack<std::uint32_t>"_test = [] {
expect(test_deduced<std::uint32_t>());
};
"packer::pack<std::uint64_t>"_test = [] {
expect(test_deduced<std::uint64_t>());
};
"packer::pack<std::int8_t>"_test = [] {
expect(test_deduced<std::int8_t>());
};
"packer::pack<std::int16_t>"_test = [] {
expect(test_deduced<std::int16_t>());
};
"packer::pack<std::int32_t>"_test = [] {
expect(test_deduced<std::int32_t>());
};
"packer::pack<std::int64_t>"_test = [] {
expect(test_deduced<std::int64_t>());
};
"packer::pack<std::string_view>"_test = [] {
expect(test_deduced<std::string_view>());
};
"packer::pack<char const*>"_test = [] {
expect(test_deduced<char const*>());
};
"packer::pack<std::array<std::byte>>"_test = [] {
expect(test_deduced<std::span<std::byte const>>());
};
};
static constexpr auto num_bytes(std::uint64_t count) {
if (count <= 0x7f) return 0;
if (count < 0x100) return 1;
if (count < 0x10000) return 2;
if (count < 0x100000000) return 4;
return 8;
}
static constexpr std::size_t bit_count_bytes(std::uint64_t count) {
if (count <= 0x7f) return 0;
return std::bit_ceil(std::uint64_t((std::bit_width(count) + 7) >> 3));
}
suite perf = [] {
"test"_test = [] {
for (auto j = 0; j < 10; ++j) {
using namespace std::chrono_literals;
std::vector<std::uint64_t> a;
std::vector<std::uint64_t> r1;
std::vector<std::uint64_t> r2;
a.reserve(10000000);
r1.reserve(10000000);
r2.reserve(10000000);
rng x;
for (auto i = 0; i < 10000000; ++i) {
a.push_back(x.get<std::size_t>());
}
auto start2 = std::chrono::steady_clock::now();
for (auto v : a) {
r2.push_back(num_bytes(v));
}
auto end2 = std::chrono::steady_clock::now();
auto start = std::chrono::steady_clock::now();
for (auto v : a) {
r1.push_back(bit_count_bytes(v));
}
auto end = std::chrono::steady_clock::now();
fmt::print("Time taken: {}us vs {}us\n", (end - start) / 1us,
(end2 - start2) / 1us);
}
};
};