Files
vdm/core/tests/util/bytes_test.cpp
samiandClaude Sonnet 5 ddf36e848a core: add util layer — Result, Error taxonomy, bytes, event bus, pool, log
util/ carries no wire surface, so it lands before the contract freeze.

- error: enum class Error, the engine-wide failure taxonomy; is_retryable
  enumerates every value (no default:) so -Wswitch forces the retry
  decision on each future addition. ErrorInfo carries context/http_status.
- result: Result<T> over std::expected<T, ErrorInfo>, Result<void>,
  VDM_TRY / VDM_TRY_ASSIGN. Errors returned, never thrown, on the
  transfer path.
- bytes: span aliases, LE load_le/store_le (debug-asserted precondition,
  not input validation), and a bounds-checked latching ByteReader for the
  .veloxpart.meta reader.
- event_bus: typed thread-safe pub/sub; header states plainly that
  unsubscribe is not a quiesce point and download_task will need its own
  drain.
- thread_pool: std::jthread pool; dtor joins in the body before members
  die (fixed a use-after-destruction on cv_/mu_). Header notes shutdown is
  drain-only and DAEMON will need a cancel mode.
- log: sink interface (core does no I/O); DAEMON installs one.

Tested: -Werror clean, 6 binaries green under plain / ASan+UBSan / TSan.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01HPPSGhiArbvQgwC2DNiURS
2026-09-09 19:03:11 +04:00

95 lines
2.8 KiB
C++

#include "vdm/util/bytes.hpp"
#include <array>
#include <cstdint>
#include <cstring>
#include <string>
#include <string_view>
#include "vtest.hpp"
using vdm::as_bytes;
using vdm::as_chars;
using vdm::ByteReader;
using vdm::ByteSpan;
using vdm::ConstByteSpan;
using vdm::load_le;
using vdm::store_le;
VT_TEST(le_roundtrip_u32) {
std::array<std::byte, 4> buf{};
store_le<std::uint32_t>(buf, 0x01020304u);
// little-endian: least significant byte first
VT_CHECK_EQ(std::to_integer<int>(buf[0]), 0x04);
VT_CHECK_EQ(std::to_integer<int>(buf[1]), 0x03);
VT_CHECK_EQ(std::to_integer<int>(buf[2]), 0x02);
VT_CHECK_EQ(std::to_integer<int>(buf[3]), 0x01);
VT_CHECK_EQ(load_le<std::uint32_t>(buf), 0x01020304u);
}
VT_TEST(le_roundtrip_u64) {
std::array<std::byte, 8> buf{};
const std::uint64_t v = 0xDEADBEEF0BADF00Dull;
store_le<std::uint64_t>(buf, v);
VT_CHECK_EQ(load_le<std::uint64_t>(buf), v);
}
VT_TEST(as_bytes_as_chars_roundtrip) {
std::string_view s = "veloxpart";
ConstByteSpan b = as_bytes(s);
VT_CHECK_EQ(b.size(), s.size());
VT_CHECK_EQ(std::string(as_chars(b)), std::string(s));
}
VT_TEST(byte_reader_sequential_reads) {
// magic "VDMP", u16 version=2, u16 flags=0, u64 total=5
std::array<std::byte, 4 + 2 + 2 + 8> buf{};
ByteSpan w(buf);
std::memcpy(buf.data(), "VDMP", 4);
store_le<std::uint16_t>(w.subspan(4), 2);
store_le<std::uint16_t>(w.subspan(6), 0);
store_le<std::uint64_t>(w.subspan(8), 5);
ByteReader r{ConstByteSpan(buf)};
VT_CHECK_EQ(std::string(as_chars(r.raw(4))), std::string("VDMP"));
VT_CHECK_EQ(r.u16(), 2);
VT_CHECK_EQ(r.u16(), 0);
VT_CHECK_EQ(r.u64(), 5u);
VT_CHECK(!r.overran());
VT_CHECK_EQ(r.remaining(), 0u);
}
VT_TEST(byte_reader_latches_on_overrun) {
std::array<std::byte, 3> buf{};
ByteReader r{ConstByteSpan(buf)};
VT_CHECK_EQ(r.u16(), 0); // ok, 2 of 3 consumed
VT_CHECK(!r.overran());
VT_CHECK_EQ(r.u32(), 0u); // wants 4, only 1 left -> latch
VT_CHECK(r.overran());
VT_CHECK_EQ(r.remaining(), 0u);
// further reads stay zero and latched
VT_CHECK_EQ(r.u8(), 0);
VT_CHECK(r.overran());
}
VT_TEST(byte_reader_length_prefixed_string) {
std::array<std::byte, 4 + 5> buf{};
ByteSpan w(buf);
store_le<std::uint32_t>(w, 5);
std::memcpy(buf.data() + 4, "hello", 5);
ByteReader r{ConstByteSpan(buf)};
VT_CHECK_EQ(std::string(r.lp_string()), std::string("hello"));
VT_CHECK(!r.overran());
}
VT_TEST(byte_reader_length_prefixed_string_rejects_bogus_length) {
std::array<std::byte, 4 + 2> buf{};
ByteSpan w(buf);
store_le<std::uint32_t>(w, 0xFFFFFFFFu); // claims 4 GiB, only 2 bytes follow
ByteReader r{ConstByteSpan(buf)};
std::string_view s = r.lp_string();
VT_CHECK(s.empty());
VT_CHECK(r.overran());
}