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
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
#include "vdm/util/result.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "vtest.hpp"
|
||||
|
||||
using vdm::Err;
|
||||
using vdm::Error;
|
||||
using vdm::ErrorInfo;
|
||||
using vdm::Result;
|
||||
|
||||
VT_TEST(result_holds_value) {
|
||||
Result<int> r = 42;
|
||||
VT_REQUIRE(r.has_value());
|
||||
VT_CHECK(static_cast<bool>(r));
|
||||
VT_CHECK_EQ(r.value(), 42);
|
||||
VT_CHECK_EQ(*r, 42);
|
||||
VT_CHECK_EQ(r.code(), Error::ok);
|
||||
}
|
||||
|
||||
VT_TEST(result_holds_error) {
|
||||
Result<int> r = Err{Error::timeout, "HEAD stalled"};
|
||||
VT_REQUIRE(!r.has_value());
|
||||
VT_CHECK_EQ(r.code(), Error::timeout);
|
||||
VT_CHECK_EQ(r.error().code, Error::timeout);
|
||||
VT_CHECK(r.error().retryable);
|
||||
VT_CHECK_EQ(r.value_or(-1), -1);
|
||||
}
|
||||
|
||||
VT_TEST(result_from_bare_error_code) {
|
||||
Result<std::string> r = Error::not_found;
|
||||
VT_REQUIRE(!r.has_value());
|
||||
VT_CHECK_EQ(r.error().code, Error::not_found);
|
||||
VT_CHECK_EQ(r.error().http_status, 0);
|
||||
}
|
||||
|
||||
VT_TEST(result_void_ok) {
|
||||
Result<void> r = vdm::ok();
|
||||
VT_CHECK(r.has_value());
|
||||
VT_CHECK_EQ(r.code(), Error::ok);
|
||||
}
|
||||
|
||||
VT_TEST(result_void_error) {
|
||||
Result<void> r = Err{Error::disk_full, "temp dir"};
|
||||
VT_REQUIRE(!r.has_value());
|
||||
VT_CHECK_EQ(r.code(), Error::disk_full);
|
||||
VT_CHECK(!r.error().retryable);
|
||||
}
|
||||
|
||||
VT_TEST(result_transform_chains_on_success) {
|
||||
Result<int> r = 21;
|
||||
auto doubled = r.transform([](int v) { return v * 2; });
|
||||
VT_REQUIRE(doubled.has_value());
|
||||
VT_CHECK_EQ(doubled.value(), 42);
|
||||
}
|
||||
|
||||
VT_TEST(result_and_then_short_circuits_on_error) {
|
||||
Result<int> r = Err{Error::connection_reset, "peer RST"};
|
||||
int calls = 0;
|
||||
auto next = r.and_then([&](int v) -> std::expected<int, ErrorInfo> {
|
||||
++calls;
|
||||
return v + 1;
|
||||
});
|
||||
VT_CHECK_EQ(calls, 0);
|
||||
VT_REQUIRE(!next.has_value());
|
||||
VT_CHECK_EQ(next.error().code, Error::connection_reset);
|
||||
}
|
||||
|
||||
namespace {
|
||||
Result<int> parse_positive(int n) {
|
||||
if (n < 0)
|
||||
return Err{Error::internal, "negative"};
|
||||
return n;
|
||||
}
|
||||
|
||||
Result<int> add_two_positives(int a, int b) {
|
||||
VDM_TRY_ASSIGN(auto x, parse_positive(a));
|
||||
VDM_TRY_ASSIGN(auto y, parse_positive(b));
|
||||
return x + y;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
VT_TEST(vdm_try_assign_propagates) {
|
||||
auto good = add_two_positives(2, 3);
|
||||
VT_REQUIRE(good.has_value());
|
||||
VT_CHECK_EQ(good.value(), 5);
|
||||
|
||||
auto bad = add_two_positives(2, -1);
|
||||
VT_REQUIRE(!bad.has_value());
|
||||
VT_CHECK_EQ(bad.error().code, Error::internal);
|
||||
}
|
||||
|
||||
VT_TEST(error_info_to_string) {
|
||||
ErrorInfo e{Error::http_server_error, "upstream", 503};
|
||||
VT_CHECK_EQ(e.to_string(), std::string("http_server_error: upstream (HTTP 503)"));
|
||||
VT_CHECK(e.retryable);
|
||||
|
||||
ErrorInfo bare{Error::canceled};
|
||||
VT_CHECK_EQ(bare.to_string(), std::string("canceled"));
|
||||
}
|
||||
|
||||
VT_TEST(error_name_and_retryable_cover_enum) {
|
||||
VT_CHECK_EQ(vdm::error_name(Error::server_file_changed),
|
||||
std::string_view("server_file_changed"));
|
||||
VT_CHECK(!vdm::is_retryable(Error::server_file_changed));
|
||||
VT_CHECK(!vdm::is_retryable(Error::checksum_mismatch));
|
||||
VT_CHECK(vdm::is_retryable(Error::timeout));
|
||||
}
|
||||
Reference in New Issue
Block a user