Add basic cpp e2e tests (#27)

This commit is contained in:
Ivan FB
2026-05-19 12:43:34 +02:00
committed by GitHub
parent e12745e85c
commit 584e818ac9
33 changed files with 449 additions and 183 deletions
+13 -13
View File
@@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.14)
project(timer_cpp_bindings CXX C)
project(my_timer_cpp_bindings CXX C)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
@@ -25,11 +25,11 @@ get_filename_component(NIM_SRC
find_program(NIM_EXECUTABLE nim REQUIRED)
if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
set(NIM_LIB_FILE "${REPO_ROOT}/libtimer.dylib")
set(NIM_LIB_FILE "${REPO_ROOT}/libmy_timer.dylib")
elseif(CMAKE_SYSTEM_NAME STREQUAL "Windows")
set(NIM_LIB_FILE "${REPO_ROOT}/timer.dll")
set(NIM_LIB_FILE "${REPO_ROOT}/my_timer.dll")
else()
set(NIM_LIB_FILE "${REPO_ROOT}/libtimer.so")
set(NIM_LIB_FILE "${REPO_ROOT}/libmy_timer.so")
endif()
add_custom_command(
@@ -39,19 +39,19 @@ add_custom_command(
-d:chronicles_log_level=WARN
--app:lib
--noMain
"--nimMainPrefix:libtimer"
"--nimMainPrefix:libmy_timer"
"-o:${NIM_LIB_FILE}"
"${NIM_SRC}"
WORKING_DIRECTORY "${REPO_ROOT}"
DEPENDS "${NIM_SRC}"
COMMENT "Compiling Nim library libtimer"
COMMENT "Compiling Nim library libmy_timer"
VERBATIM
)
add_custom_target(nim_lib ALL DEPENDS "${NIM_LIB_FILE}")
add_library(timer SHARED IMPORTED GLOBAL)
set_target_properties(timer PROPERTIES IMPORTED_LOCATION "${NIM_LIB_FILE}")
add_dependencies(timer nim_lib)
add_library(my_timer SHARED IMPORTED GLOBAL)
set_target_properties(my_timer PROPERTIES IMPORTED_LOCATION "${NIM_LIB_FILE}")
add_dependencies(my_timer nim_lib)
# ── TinyCBOR (vendored at ffi/codegen/templates/cpp/vendor/tinycbor) ─────────
set(TINYCBOR_SRC_DIR "${REPO_ROOT}/ffi/codegen/templates/cpp/vendor")
@@ -69,12 +69,12 @@ target_include_directories(tinycbor PUBLIC
set_property(TARGET tinycbor PROPERTY C_STANDARD 99)
set_property(TARGET tinycbor PROPERTY POSITION_INDEPENDENT_CODE ON)
add_library(timer_headers INTERFACE)
target_include_directories(timer_headers INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}")
target_link_libraries(timer_headers INTERFACE timer tinycbor)
add_library(my_timer_headers INTERFACE)
target_include_directories(my_timer_headers INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}")
target_link_libraries(my_timer_headers INTERFACE my_timer tinycbor)
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/main.cpp")
add_executable(example main.cpp)
target_link_libraries(example PRIVATE timer_headers)
target_link_libraries(example PRIVATE my_timer_headers)
add_dependencies(example nim_lib)
endif()
+2 -2
View File
@@ -2,9 +2,9 @@
## Purpose
This folder contains **auto-generated C++ bindings** for the `timer` Nim library. It is generated from `../timer.nim` and provides:
This folder contains **auto-generated C++ bindings** for the `my_timer` Nim library. It is generated from `../timer.nim` and provides:
- `timer.hpp`: High-level C++ class (`TimerCtx`) wrapping the FFI interface
- `my_timer.hpp`: High-level C++ class (`MyTimerCtx`) wrapping the FFI interface
- `main.cpp`: Example executable demonstrating how to use the bindings
- `CMakeLists.txt`: Build configuration that compiles the Nim library and links the C++ example
+2 -2
View File
@@ -1,10 +1,10 @@
#include "timer.hpp"
#include "my_timer.hpp"
#include <iostream>
#include <future>
int main() {
try {
auto ctx = TimerCtx::create(TimerConfig{"cpp-demo"});
auto ctx = MyTimerCtx::create(TimerConfig{"cpp-demo"});
std::cout << "[1] Context created\n";
auto versionFuture = ctx.versionAsync();
@@ -478,10 +478,10 @@ inline CborError decode_cbor(CborValue& it, ScheduleResult& v) {
// Per-proc request envelopes (CBOR encoded on the wire)
// ============================================================
struct TimerCreateCtorReq {
struct MyTimerCreateCtorReq {
TimerConfig config;
};
inline CborError encode_cbor(CborEncoder& e, const TimerCreateCtorReq& v) {
inline CborError encode_cbor(CborEncoder& e, const MyTimerCreateCtorReq& v) {
CborEncoder m;
CborError err = cbor_encoder_create_map(&e, &m, 1);
if (err) return err;
@@ -489,7 +489,7 @@ inline CborError encode_cbor(CborEncoder& e, const TimerCreateCtorReq& v) {
err = encode_cbor(m, v.config); if (err) return err;
return cbor_encoder_close_container(&e, &m);
}
inline CborError decode_cbor(CborValue& it, TimerCreateCtorReq& v) {
inline CborError decode_cbor(CborValue& it, MyTimerCreateCtorReq& v) {
if (!cbor_value_is_map(&it)) return CborErrorImproperValue;
CborValue field;
CborError err;
@@ -499,10 +499,10 @@ inline CborError decode_cbor(CborValue& it, TimerCreateCtorReq& v) {
return cbor_value_advance(&it);
}
struct TimerEchoReq {
struct MyTimerEchoReq {
EchoRequest req;
};
inline CborError encode_cbor(CborEncoder& e, const TimerEchoReq& v) {
inline CborError encode_cbor(CborEncoder& e, const MyTimerEchoReq& v) {
CborEncoder m;
CborError err = cbor_encoder_create_map(&e, &m, 1);
if (err) return err;
@@ -510,7 +510,7 @@ inline CborError encode_cbor(CborEncoder& e, const TimerEchoReq& v) {
err = encode_cbor(m, v.req); if (err) return err;
return cbor_encoder_close_container(&e, &m);
}
inline CborError decode_cbor(CborValue& it, TimerEchoReq& v) {
inline CborError decode_cbor(CborValue& it, MyTimerEchoReq& v) {
if (!cbor_value_is_map(&it)) return CborErrorImproperValue;
CborValue field;
CborError err;
@@ -520,23 +520,23 @@ inline CborError decode_cbor(CborValue& it, TimerEchoReq& v) {
return cbor_value_advance(&it);
}
struct TimerVersionReq {
struct MyTimerVersionReq {
};
inline CborError encode_cbor(CborEncoder& e, const TimerVersionReq&) {
inline CborError encode_cbor(CborEncoder& e, const MyTimerVersionReq&) {
CborEncoder m;
CborError err = cbor_encoder_create_map(&e, &m, 0);
if (err) return err;
return cbor_encoder_close_container(&e, &m);
}
inline CborError decode_cbor(CborValue& it, TimerVersionReq&) {
inline CborError decode_cbor(CborValue& it, MyTimerVersionReq&) {
if (!cbor_value_is_map(&it)) return CborErrorImproperValue;
return cbor_value_advance(&it);
}
struct TimerComplexReq {
struct MyTimerComplexReq {
ComplexRequest req;
};
inline CborError encode_cbor(CborEncoder& e, const TimerComplexReq& v) {
inline CborError encode_cbor(CborEncoder& e, const MyTimerComplexReq& v) {
CborEncoder m;
CborError err = cbor_encoder_create_map(&e, &m, 1);
if (err) return err;
@@ -544,7 +544,7 @@ inline CborError encode_cbor(CborEncoder& e, const TimerComplexReq& v) {
err = encode_cbor(m, v.req); if (err) return err;
return cbor_encoder_close_container(&e, &m);
}
inline CborError decode_cbor(CborValue& it, TimerComplexReq& v) {
inline CborError decode_cbor(CborValue& it, MyTimerComplexReq& v) {
if (!cbor_value_is_map(&it)) return CborErrorImproperValue;
CborValue field;
CborError err;
@@ -554,12 +554,12 @@ inline CborError decode_cbor(CborValue& it, TimerComplexReq& v) {
return cbor_value_advance(&it);
}
struct TimerScheduleReq {
struct MyTimerScheduleReq {
JobSpec job;
RetryPolicy retry;
ScheduleConfig schedule;
};
inline CborError encode_cbor(CborEncoder& e, const TimerScheduleReq& v) {
inline CborError encode_cbor(CborEncoder& e, const MyTimerScheduleReq& v) {
CborEncoder m;
CborError err = cbor_encoder_create_map(&e, &m, 3);
if (err) return err;
@@ -571,7 +571,7 @@ inline CborError encode_cbor(CborEncoder& e, const TimerScheduleReq& v) {
err = encode_cbor(m, v.schedule); if (err) return err;
return cbor_encoder_close_container(&e, &m);
}
inline CborError decode_cbor(CborValue& it, TimerScheduleReq& v) {
inline CborError decode_cbor(CborValue& it, MyTimerScheduleReq& v) {
if (!cbor_value_is_map(&it)) return CborErrorImproperValue;
CborValue field;
CborError err;
@@ -594,12 +594,12 @@ inline CborError decode_cbor(CborValue& it, TimerScheduleReq& v) {
extern "C" {
typedef void (*FFICallback)(int ret, const char* msg, size_t len, void* user_data);
void* timer_create(const uint8_t* req_cbor, size_t req_cbor_len, FFICallback callback, void* user_data);
int timer_echo(void* ctx, FFICallback callback, void* user_data, const uint8_t* req_cbor, size_t req_cbor_len);
int timer_version(void* ctx, FFICallback callback, void* user_data, const uint8_t* req_cbor, size_t req_cbor_len);
int timer_complex(void* ctx, FFICallback callback, void* user_data, const uint8_t* req_cbor, size_t req_cbor_len);
int timer_schedule(void* ctx, FFICallback callback, void* user_data, const uint8_t* req_cbor, size_t req_cbor_len);
int timer_destroy(void* ctx);
void* my_timer_create(const uint8_t* req_cbor, size_t req_cbor_len, FFICallback callback, void* user_data);
int my_timer_echo(void* ctx, FFICallback callback, void* user_data, const uint8_t* req_cbor, size_t req_cbor_len);
int my_timer_version(void* ctx, FFICallback callback, void* user_data, const uint8_t* req_cbor, size_t req_cbor_len);
int my_timer_complex(void* ctx, FFICallback callback, void* user_data, const uint8_t* req_cbor, size_t req_cbor_len);
int my_timer_schedule(void* ctx, FFICallback callback, void* user_data, const uint8_t* req_cbor, size_t req_cbor_len);
int my_timer_destroy(void* ctx);
} // extern "C"
// ============================================================
@@ -659,29 +659,29 @@ inline std::vector<std::uint8_t> ffi_call_(std::function<int(FFICallback, void*)
// High-level C++ context class
// ============================================================
class TimerCtx {
class MyTimerCtx {
public:
static TimerCtx create(const TimerConfig& config, std::chrono::milliseconds timeout = std::chrono::seconds{30}) {
const auto ffi_req_ = TimerCreateCtorReq{config};
static MyTimerCtx create(const TimerConfig& config, std::chrono::milliseconds timeout = std::chrono::seconds{30}) {
const auto ffi_req_ = MyTimerCreateCtorReq{config};
const auto ffi_req_bytes_ = encodeCborFFI(ffi_req_);
const auto ffi_raw_ = ffi_call_([&](FFICallback cb, void* ud) {
(void)timer_create(ffi_req_bytes_.data(), ffi_req_bytes_.size(), cb, ud);
(void)my_timer_create(ffi_req_bytes_.data(), ffi_req_bytes_.size(), cb, ud);
return 0;
}, timeout);
const auto addr_str = decodeCborFFI<std::string>(ffi_raw_);
try {
const auto addr = std::stoull(addr_str);
return TimerCtx(reinterpret_cast<void*>(static_cast<uintptr_t>(addr)), timeout);
return MyTimerCtx(reinterpret_cast<void*>(static_cast<uintptr_t>(addr)), timeout);
} catch (const std::exception&) {
throw std::runtime_error("FFI create returned non-numeric address: " + addr_str);
}
}
static std::future<TimerCtx> createAsync(const TimerConfig& config, std::chrono::milliseconds timeout = std::chrono::seconds{30}) {
static std::future<MyTimerCtx> createAsync(const TimerConfig& config, std::chrono::milliseconds timeout = std::chrono::seconds{30}) {
return std::async(std::launch::async, [config, timeout]() { return create(config, timeout); });
}
// Rule of Five: because this class owns a raw resource (the timer
// Rule of Five: because this class owns a raw resource (the my_timer
// context pointer freed in the destructor), the compiler-generated copy
// and move special members would do the wrong thing — copies would
// double-free, and a default move would leave both objects pointing at
@@ -693,22 +693,22 @@ public:
// 5. move assignment — destroys the current context, then
// transfers ownership from `other`.
// See: https://en.cppreference.com/w/cpp/language/rule_of_three
~TimerCtx() {
~MyTimerCtx() {
if (ptr_) {
timer_destroy(ptr_);
my_timer_destroy(ptr_);
ptr_ = nullptr;
}
}
TimerCtx(const TimerCtx&) = delete;
TimerCtx& operator=(const TimerCtx&) = delete;
MyTimerCtx(const MyTimerCtx&) = delete;
MyTimerCtx& operator=(const MyTimerCtx&) = delete;
TimerCtx(TimerCtx&& other) noexcept : ptr_(other.ptr_), timeout_(other.timeout_) {
MyTimerCtx(MyTimerCtx&& other) noexcept : ptr_(other.ptr_), timeout_(other.timeout_) {
other.ptr_ = nullptr;
}
TimerCtx& operator=(TimerCtx&& other) noexcept {
MyTimerCtx& operator=(MyTimerCtx&& other) noexcept {
if (this != &other) {
if (ptr_) timer_destroy(ptr_);
if (ptr_) my_timer_destroy(ptr_);
ptr_ = other.ptr_;
timeout_ = other.timeout_;
other.ptr_ = nullptr;
@@ -717,10 +717,10 @@ public:
}
EchoResponse echo(const EchoRequest& req) const {
const auto ffi_req_ = TimerEchoReq{req};
const auto ffi_req_ = MyTimerEchoReq{req};
const auto ffi_req_bytes_ = encodeCborFFI(ffi_req_);
const auto ffi_raw_ = ffi_call_([&](FFICallback cb, void* ud) {
return timer_echo(ptr_, cb, ud, ffi_req_bytes_.data(), ffi_req_bytes_.size());
return my_timer_echo(ptr_, cb, ud, ffi_req_bytes_.data(), ffi_req_bytes_.size());
}, timeout_);
return decodeCborFFI<EchoResponse>(ffi_raw_);
}
@@ -730,10 +730,10 @@ public:
}
std::string version() const {
const auto ffi_req_ = TimerVersionReq{};
const auto ffi_req_ = MyTimerVersionReq{};
const auto ffi_req_bytes_ = encodeCborFFI(ffi_req_);
const auto ffi_raw_ = ffi_call_([&](FFICallback cb, void* ud) {
return timer_version(ptr_, cb, ud, ffi_req_bytes_.data(), ffi_req_bytes_.size());
return my_timer_version(ptr_, cb, ud, ffi_req_bytes_.data(), ffi_req_bytes_.size());
}, timeout_);
return decodeCborFFI<std::string>(ffi_raw_);
}
@@ -743,10 +743,10 @@ public:
}
ComplexResponse complex(const ComplexRequest& req) const {
const auto ffi_req_ = TimerComplexReq{req};
const auto ffi_req_ = MyTimerComplexReq{req};
const auto ffi_req_bytes_ = encodeCborFFI(ffi_req_);
const auto ffi_raw_ = ffi_call_([&](FFICallback cb, void* ud) {
return timer_complex(ptr_, cb, ud, ffi_req_bytes_.data(), ffi_req_bytes_.size());
return my_timer_complex(ptr_, cb, ud, ffi_req_bytes_.data(), ffi_req_bytes_.size());
}, timeout_);
return decodeCborFFI<ComplexResponse>(ffi_raw_);
}
@@ -756,10 +756,10 @@ public:
}
ScheduleResult schedule(const JobSpec& job, const RetryPolicy& retry, const ScheduleConfig& schedule) const {
const auto ffi_req_ = TimerScheduleReq{job, retry, schedule};
const auto ffi_req_ = MyTimerScheduleReq{job, retry, schedule};
const auto ffi_req_bytes_ = encodeCborFFI(ffi_req_);
const auto ffi_raw_ = ffi_call_([&](FFICallback cb, void* ud) {
return timer_schedule(ptr_, cb, ud, ffi_req_bytes_.data(), ffi_req_bytes_.size());
return my_timer_schedule(ptr_, cb, ud, ffi_req_bytes_.data(), ffi_req_bytes_.size());
}, timeout_);
return decodeCborFFI<ScheduleResult>(ffi_raw_);
}
@@ -771,5 +771,5 @@ public:
private:
void* ptr_;
std::chrono::milliseconds timeout_;
explicit TimerCtx(void* p, std::chrono::milliseconds t) : ptr_(p), timeout_(t) {}
explicit MyTimerCtx(void* p, std::chrono::milliseconds t) : ptr_(p), timeout_(t) {}
};