From 5ac74ecbd90d28c701df92f24a1ceb0e2a9c7ef6 Mon Sep 17 00:00:00 2001 From: sami Date: Wed, 9 Sep 2026 19:03:00 +0400 Subject: [PATCH] core: add build skeleton and provisional test harness Self-contained core/CMakeLists.txt (veloxcore STATIC + velox::core alias), warnings at target scope so the sanitizer presets' CMAKE_CXX_FLAGS override doesn't drop -Werror. vtest: ~150-line header-only harness (VT_TEST / VT_CHECK / VT_REQUIRE / VT_CHECK_EQ) behind a one-function vdm_add_test(), so the swap to a real framework once PKG picks one is mechanical. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01HPPSGhiArbvQgwC2DNiURS --- core/CMakeLists.txt | 45 +++++++++ core/tests/.gitkeep | 0 core/tests/CMakeLists.txt | 22 +++++ core/tests/support/vtest.hpp | 155 ++++++++++++++++++++++++++++++ core/tests/support/vtest_main.cpp | 4 + 5 files changed, 226 insertions(+) create mode 100644 core/CMakeLists.txt delete mode 100644 core/tests/.gitkeep create mode 100644 core/tests/CMakeLists.txt create mode 100644 core/tests/support/vtest.hpp create mode 100644 core/tests/support/vtest_main.cpp diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt new file mode 100644 index 0000000..affb361 --- /dev/null +++ b/core/CMakeLists.txt @@ -0,0 +1,45 @@ +# libveloxcore — the download engine. Lane CORE. +# +# No JSON, no SQL, no Qt, no RPC in this tree (CLAUDE.md §3, AGENT-CORE brief). +# This file is self-contained; it is wired into the build by PKG uncommenting +# `add_subdirectory(core)` in the root CMakeLists.txt (see core/docs/pkg-requests-m1.md). + +find_package(Threads REQUIRED) + +add_library(veloxcore STATIC + src/util/error.cpp + src/util/log.cpp + src/util/thread_pool.cpp +) +add_library(velox::core ALIAS veloxcore) + +target_include_directories(veloxcore PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/include +) + +target_compile_features(veloxcore PUBLIC cxx_std_23) + +# Warnings are set at target scope, not via CMAKE_CXX_FLAGS: the dev/tsan presets +# overwrite that cache variable wholesale (see core/docs/pkg-requests-m1.md P4). +target_compile_options(veloxcore PRIVATE + -Wall -Wextra -Wpedantic -Werror +) + +target_link_libraries(veloxcore PUBLIC Threads::Threads) + +# Later stages add: find_package(CURL 8.0) for net/, find_package(OpenSSL) for meta/. + +if(VELOX_BUILD_TESTS) + add_subdirectory(tests) +endif() + +# libFuzzer is clang-only; a GCC configure with -DVELOX_BUILD_FUZZ=ON (the `ci` preset) +# must not hard-fail. No fuzz targets exist yet — they arrive with net/probe (stage 3) +# and meta/veloxpart (stage 5). +if(VELOX_BUILD_FUZZ AND NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang") + message(STATUS "veloxcore: VELOX_BUILD_FUZZ set but compiler is " + "${CMAKE_CXX_COMPILER_ID}; fuzz targets need Clang and will be skipped.") +endif() +# When fuzz targets land (stage 3: Content-Disposition, URL; stage 5: .veloxpart.meta), +# they are added here under `if(VELOX_BUILD_FUZZ AND CMAKE_CXX_COMPILER_ID MATCHES "Clang")` +# and live in ${CMAKE_SOURCE_DIR}/tools/fuzz (owned by lane CORE). diff --git a/core/tests/.gitkeep b/core/tests/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/core/tests/CMakeLists.txt b/core/tests/CMakeLists.txt new file mode 100644 index 0000000..886f9fe --- /dev/null +++ b/core/tests/CMakeLists.txt @@ -0,0 +1,22 @@ +# CORE unit tests. +# +# Harness is the provisional header-only vtest (support/vtest.hpp); PKG owns the final +# framework choice (core/docs/pkg-requests-m1.md P2). vdm_add_test() is the only thing +# test files touch, so swapping harnesses is a one-function edit. + +add_library(vtest_main STATIC support/vtest_main.cpp) +target_include_directories(vtest_main PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/support) +target_compile_features(vtest_main PUBLIC cxx_std_23) + +function(vdm_add_test name) + add_executable(${name} ${ARGN}) + target_link_libraries(${name} PRIVATE veloxcore vtest_main) + target_compile_options(${name} PRIVATE -Wall -Wextra -Wpedantic -Werror) + add_test(NAME ${name} COMMAND ${name}) +endfunction() + +vdm_add_test(veloxcore_result_test util/result_test.cpp) +vdm_add_test(veloxcore_event_bus_test util/event_bus_test.cpp) +vdm_add_test(veloxcore_thread_pool_test util/thread_pool_test.cpp) +vdm_add_test(veloxcore_bytes_test util/bytes_test.cpp) +vdm_add_test(veloxcore_log_test util/log_test.cpp) diff --git a/core/tests/support/vtest.hpp b/core/tests/support/vtest.hpp new file mode 100644 index 0000000..0c8e9bf --- /dev/null +++ b/core/tests/support/vtest.hpp @@ -0,0 +1,155 @@ +// vtest.hpp — provisional micro test harness for lane CORE. +// +// PROVISIONAL. PKG/QA owns the framework decision (see core/docs/pkg-requests-m1.md P2); +// this exists only so CORE isn't blocked. API is intentionally minimal so the swap to +// GoogleTest/Catch2/doctest is mechanical: +// +// VT_TEST(name) { ... } -- define a test +// VT_CHECK(cond) -- non-fatal assertion +// VT_REQUIRE(cond) -- fatal (aborts this test) +// VT_CHECK_EQ(a, b) / VT_CHECK_NE -- comparison with values printed on failure +// VT_FAIL("msg") -- unconditional non-fatal failure +// +// Link one translation unit that also defines VT_MAIN before including this header, or +// just link vtest_main.cpp. Each test binary returns non-zero if any check failed. + +#ifndef VDM_TESTS_VTEST_HPP +#define VDM_TESTS_VTEST_HPP + +#include +#include +#include +#include +#include +#include + +namespace vt { + +struct Case { + const char *name; + void (*fn)(); +}; + +inline std::vector ®istry() { + static std::vector r; + return r; +} + +struct Stats { + int checks = 0; + int failures = 0; + bool current_fatal = false; +}; + +inline Stats &stats() { + static Stats s; + return s; +} + +struct FatalAbort : std::exception {}; + +struct Registrar { + Registrar(const char *name, void (*fn)()) { registry().push_back({name, fn}); } +}; + +inline void report(const char *file, int line, std::string_view expr, + std::string_view detail, bool fatal) { + stats().failures++; + std::fprintf(stderr, " FAIL %s:%d %.*s", file, line, + static_cast(expr.size()), expr.data()); + if (!detail.empty()) + std::fprintf(stderr, " [%.*s]", static_cast(detail.size()), detail.data()); + std::fprintf(stderr, "\n"); + if (fatal) { + stats().current_fatal = true; + throw FatalAbort{}; + } +} + +template +std::string show(const T &v) { + if constexpr (std::is_same_v || std::is_same_v || + std::is_same_v) { + return std::string("\"") + std::string(v) + "\""; + } else if constexpr (std::is_same_v) { + return v ? "true" : "false"; + } else if constexpr (std::is_arithmetic_v || std::is_enum_v) { + return std::to_string(static_cast( + static_cast, long long, T>>(v))); + } else { + return ""; + } +} + +inline int run_all() { + int failed_cases = 0; + for (const auto &c : registry()) { + int before = stats().failures; + stats().current_fatal = false; + std::fprintf(stderr, "[ RUN ] %s\n", c.name); + try { + c.fn(); + } catch (const FatalAbort &) { + // reported already + } catch (const std::exception &e) { + stats().failures++; + std::fprintf(stderr, " FAIL uncaught exception: %s\n", e.what()); + } catch (...) { + stats().failures++; + std::fprintf(stderr, " FAIL uncaught non-std exception\n"); + } + bool ok = stats().failures == before; + std::fprintf(stderr, "[ %s ] %s\n", ok ? "PASS" : "FAIL", c.name); + if (!ok) + failed_cases++; + } + std::fprintf(stderr, "\n%zu cases, %d failed, %d checks, %d check failures\n", + registry().size(), failed_cases, stats().checks, stats().failures); + return failed_cases == 0 ? 0 : 1; +} + +} // namespace vt + +#define VT_TEST(NAME) \ + static void NAME##_impl(); \ + static ::vt::Registrar NAME##_reg(#NAME, &NAME##_impl); \ + static void NAME##_impl() + +#define VT_CHECK(COND) \ + do { \ + ::vt::stats().checks++; \ + if (!(COND)) \ + ::vt::report(__FILE__, __LINE__, #COND, {}, /*fatal=*/false); \ + } while (0) + +#define VT_REQUIRE(COND) \ + do { \ + ::vt::stats().checks++; \ + if (!(COND)) \ + ::vt::report(__FILE__, __LINE__, #COND, {}, /*fatal=*/true); \ + } while (0) + +#define VT_CHECK_EQ(A, B) \ + do { \ + ::vt::stats().checks++; \ + auto &&_a = (A); \ + auto &&_b = (B); \ + if (!(_a == _b)) \ + ::vt::report(__FILE__, __LINE__, #A " == " #B, \ + ::vt::show(_a) + " vs " + ::vt::show(_b), false); \ + } while (0) + +#define VT_CHECK_NE(A, B) \ + do { \ + ::vt::stats().checks++; \ + auto &&_a = (A); \ + auto &&_b = (B); \ + if (!(_a != _b)) \ + ::vt::report(__FILE__, __LINE__, #A " != " #B, \ + ::vt::show(_a) + " vs " + ::vt::show(_b), false); \ + } while (0) + +#define VT_FAIL(MSG) \ + ::vt::report(__FILE__, __LINE__, "VT_FAIL", (MSG), /*fatal=*/false) + +#endif // VDM_TESTS_VTEST_HPP diff --git a/core/tests/support/vtest_main.cpp b/core/tests/support/vtest_main.cpp new file mode 100644 index 0000000..c26b763 --- /dev/null +++ b/core/tests/support/vtest_main.cpp @@ -0,0 +1,4 @@ +// vtest_main.cpp — shared entry point for every CORE test binary. +#include "vtest.hpp" + +int main() { return ::vt::run_all(); }