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 <[email protected]> Claude-Session: https://claude.ai/code/session_01HPPSGhiArbvQgwC2DNiURS
This commit is contained in:
@@ -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).
|
||||
@@ -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)
|
||||
@@ -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 <cstdio>
|
||||
#include <exception>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
namespace vt {
|
||||
|
||||
struct Case {
|
||||
const char *name;
|
||||
void (*fn)();
|
||||
};
|
||||
|
||||
inline std::vector<Case> ®istry() {
|
||||
static std::vector<Case> 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<int>(expr.size()), expr.data());
|
||||
if (!detail.empty())
|
||||
std::fprintf(stderr, " [%.*s]", static_cast<int>(detail.size()), detail.data());
|
||||
std::fprintf(stderr, "\n");
|
||||
if (fatal) {
|
||||
stats().current_fatal = true;
|
||||
throw FatalAbort{};
|
||||
}
|
||||
}
|
||||
|
||||
template <class T>
|
||||
std::string show(const T &v) {
|
||||
if constexpr (std::is_same_v<T, std::string> || std::is_same_v<T, std::string_view> ||
|
||||
std::is_same_v<T, const char *>) {
|
||||
return std::string("\"") + std::string(v) + "\"";
|
||||
} else if constexpr (std::is_same_v<T, bool>) {
|
||||
return v ? "true" : "false";
|
||||
} else if constexpr (std::is_arithmetic_v<T> || std::is_enum_v<T>) {
|
||||
return std::to_string(static_cast<long long>(
|
||||
static_cast<std::conditional_t<std::is_enum_v<T>, 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
|
||||
@@ -0,0 +1,4 @@
|
||||
// vtest_main.cpp — shared entry point for every CORE test binary.
|
||||
#include "vtest.hpp"
|
||||
|
||||
int main() { return ::vt::run_all(); }
|
||||
Reference in New Issue
Block a user