Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 23 additions & 5 deletions include/msgpack23/msgpack23.h
Original file line number Diff line number Diff line change
Expand Up @@ -458,8 +458,12 @@ namespace msgpack23 {
throw std::length_error("String is too long to be serialized.");
}

std::copy(reinterpret_cast<B const * const>(value.data()),
reinterpret_cast<B const * const>(value.data() + value.size()), store_);
auto const *src = reinterpret_cast<B const *>(value.data());
if constexpr (requires { store_.write(src, value.size()); }) {
store_.write(src, value.size());
} else {
std::copy(src, src + value.size(), store_);
}
}

void pack_type(std::vector<B> const &value) {
Expand All @@ -473,8 +477,12 @@ namespace msgpack23 {
} else {
throw std::length_error("Vector is too long to be serialized.");
}
std::copy(reinterpret_cast<B const * const>(value.data()),
reinterpret_cast<B const * const>(value.data() + value.size()), store_);
auto const *src = reinterpret_cast<B const *>(value.data());
if constexpr (requires { store_.write(src, value.size()); }) {
store_.write(src, value.size());
} else {
std::copy(src, src + value.size(), store_);
}
}

template<std::size_t E>
Expand All @@ -490,7 +498,11 @@ namespace msgpack23 {
throw std::length_error("Span is too long to be serialized.");
}
auto const *src = reinterpret_cast<B const *>(value.data());
std::copy(src, src + value.size(), store_);
if constexpr (requires { store_.write(src, value.size()); }) {
store_.write(src, value.size());
} else {
std::copy(src, src + value.size(), store_);
}
}
};

Expand All @@ -499,6 +511,12 @@ namespace msgpack23 {
Packer(std::back_insert_iterator<Container>) ->
Packer<typename Container::value_type, std::back_insert_iterator<Container> >;

template<typename Iter>
requires requires { typename Iter::value_type; }
&& byte_type<typename Iter::value_type>
&& std::output_iterator<Iter, typename Iter::value_type>
Packer(Iter) -> Packer<typename Iter::value_type, Iter>;

template<typename T, typename P>
concept packable_object = requires(T t, P p)
{
Expand Down
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ include(GoogleTest)
add_executable(
msgpack23_tests
array_tests.cpp
bulk_write_tests.cpp
byte_type_tests.cpp
chrono_tests.cpp
exception_tests.cpp
Expand Down
98 changes: 98 additions & 0 deletions tests/bulk_write_tests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
//
// Created by Neara Software on 06/03/2026.
//

#include <gtest/gtest.h>
#include <msgpack23/msgpack23.h>

namespace {

// Counts individual byte emplace calls (operator=) and bulk write() calls.
// test_container holds a raw pointer so that all copies (the Packer stores
// the iterator by value) update the same counts object.
struct counts {
std::size_t emplace_count{0};
std::size_t write_count{0};
};

struct test_container {
using value_type = std::uint8_t;
using difference_type = std::ptrdiff_t;

counts *c;

test_container &operator*() { return *this; }
test_container &operator++() { return *this; }
test_container operator++(int) { return *this; }

test_container &operator=(std::uint8_t) {
++c->emplace_count;
return *this;
}

void write(const std::uint8_t *, std::size_t) {
++c->write_count;
}
};

template<typename T>
counts pack(T const &value) {
counts c{};
test_container iter{&c};
msgpack23::Packer<std::uint8_t, test_container> packer{iter};
packer(value);
return c;
}

// ── int8_t(42) ────────────────────────────────────────────────────────────
// 42 > 31: int8 format byte + value byte, no write.
TEST(msgpack23_bulk_write, Int8UsesNoBulkWrite) {
auto c = pack(std::int8_t{42});
EXPECT_EQ(c.emplace_count, 2u);
EXPECT_EQ(c.write_count, 0u);
}

// ── uint32_t(70000) ───────────────────────────────────────────────────────
// 70000 > 0xFFFF: uint32 format byte + 4 bytes via std::copy, no write.
// emplace_integral's requires-check on sizeof(T) does not match write().
TEST(msgpack23_bulk_write, Uint32UsesNoBulkWrite) {
auto c = pack(std::uint32_t{70'000});
EXPECT_EQ(c.emplace_count, 5u);
EXPECT_EQ(c.write_count, 0u);
}

// ── std::string{"hello"} ─────────────────────────────────────────────────
// fixstr header byte (emplace) + body via write().
TEST(msgpack23_bulk_write, StringUsesBulkWrite) {
auto c = pack(std::string{"hello"});
EXPECT_EQ(c.emplace_count, 1u);
EXPECT_EQ(c.write_count, 1u);
}

// ── std::vector<uint8_t> ─────────────────────────────────────────────────
// bin8 format byte + length byte (emplace) + body via write().
TEST(msgpack23_bulk_write, VectorUsesBulkWrite) {
auto c = pack(std::vector<std::uint8_t>{1, 2, 3, 4, 5});
EXPECT_EQ(c.emplace_count, 2u);
EXPECT_EQ(c.write_count, 1u);
}

// ── std::span<const uint8_t> ─────────────────────────────────────────────
// Same bin8 layout as vector.
TEST(msgpack23_bulk_write, SpanUsesBulkWrite) {
std::vector<std::uint8_t> storage{10, 20, 30};
auto c = pack(std::span<const std::uint8_t>{storage});
EXPECT_EQ(c.emplace_count, 2u);
EXPECT_EQ(c.write_count, 1u);
}

// ── std::vector<std::string>{"foo","bar","baz"} ───────────────────────────
// fixarray header byte + per element: fixstr header byte + write().
TEST(msgpack23_bulk_write, VectorOfStringsUsesBulkWritePerElement) {
std::vector<std::string> strings{"foo", "bar", "baz"};
auto c = pack(strings);
EXPECT_EQ(c.emplace_count, 4u);
EXPECT_EQ(c.write_count, 3u);
}

} // namespace