Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: CI

on:
push:
pull_request:

jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
compiler: [gcc, clang]
steps:
- uses: actions/checkout@v4

- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y build-essential cmake ninja-build libboost-all-dev

- name: Configure
run: |
cmake -S . -B build -G Ninja -DRTACO_BUILD_TESTS=ON -DCMAKE_BUILD_TYPE=Release

- name: Build
run: cmake --build build -j

- name: Run tests
run: ctest --test-dir build --output-on-failure
6 changes: 6 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -103,4 +103,10 @@ if(RTACO_BUILD_DOCS)
add_subdirectory(docs)
endif()

option(RTACO_BUILD_TESTS "Build tests" OFF)
if(RTACO_BUILD_TESTS)
enable_testing()
add_subdirectory(tests)
endif()

message(STATUS "Config: llmx-rtaco project configured")
53 changes: 53 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
cmake_minimum_required(VERSION 3.22)

function(add_llmx_test target)
cmake_parse_arguments(
LLMX_TEST
""
"TEST_NAME"
""
${ARGN}
)

if(LLMX_TEST_UNPARSED_ARGUMENTS)
message(FATAL_ERROR "add_llmx_test does not accept source file arguments")
endif()

add_executable(${target} ${target}.cxx)
target_link_libraries(${target}
PRIVATE
GTest::gtest
${PROJECT_NAME}
)

if(LLMX_TEST_TEST_NAME)
set(test_name ${LLMX_TEST_TEST_NAME})
else()
set(test_name ${target})
endif()

add_test(NAME ${test_name} COMMAND ${target})
endfunction()


include(FetchContent)
FetchContent_Declare(
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG v1.15.2
)
FetchContent_MakeAvailable(googletest)

add_executable(test_rtaco
test_signal.cpp
test_requesttask_compile.cpp
test_socket.cpp
test_nl_common.cpp
)

target_link_libraries(test_rtaco PRIVATE llmx_rtaco GTest::gtest_main)

enable_testing()

include(GoogleTest)
gtest_discover_tests(test_rtaco)
36 changes: 36 additions & 0 deletions tests/test_nl_common.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#include <gtest/gtest.h>
#include <cstring>

#include "rtaco/core/nl_common.hxx"

using namespace llmx::rtaco;

TEST(NLCommonTest, TrimStringAndAttributeString) {
// trim_string should remove trailing nulls
std::string_view sv = "abc\0\0";
auto trimmed = trim_string(sv);
EXPECT_EQ(trimmed, "abc");

// Build an rtattr with payload string
const char* payload = "eth0";
const size_t payload_len = std::strlen(payload) + 1;
const size_t attr_len = RTA_LENGTH(payload_len);
std::vector<uint8_t> buf(attr_len);
std::memset(buf.data(), 0, buf.size());

auto attr = reinterpret_cast<rtattr*>(buf.data());
attr->rta_len = static_cast<unsigned short>(RTA_LENGTH(payload_len));
attr->rta_type = IFLA_IFNAME;
std::memcpy(RTA_DATA(attr), payload, payload_len);

auto s = attribute_string(*attr);
EXPECT_EQ(s, "eth0");
}

TEST(NLCommonTest, GetMsgPayloadShort) {
nlmsghdr short_hdr{};
short_hdr.nlmsg_len = NLMSG_LENGTH(sizeof(ifinfomsg)) - 1; // too small

auto ptr = get_msg_payload<ifinfomsg>(short_hdr);
EXPECT_EQ(ptr, nullptr);
}
23 changes: 23 additions & 0 deletions tests/test_requesttask_compile.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#include <gtest/gtest.h>

#include "rtaco/tasks/nl_request_task.hxx"

using namespace llmx::rtaco;

struct RequestBehaviorChecker {
void prepare_request() {}
std::span<const uint8_t> request_payload() const {
return {};
}
auto process_message(const nlmsghdr&)
-> std::optional<std::expected<int, std::error_code>> {
return std::nullopt;
}
};

static_assert(request_behavior<RequestBehaviorChecker, int>,
"RequestBehaviorChecker should satisfy request_behavior concept");

TEST(RequestTaskCompileTest, ConceptSatisfied) {
SUCCEED();
}
33 changes: 33 additions & 0 deletions tests/test_signal.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#include <gtest/gtest.h>
#include <boost/asio/io_context.hpp>
#include <boost/asio/executor_work_guard.hpp>
#include <thread>

#include "rtaco/core/nl_signal.hxx"

using namespace llmx::rtaco;

TEST(SignalTest, SyncAndAsyncSlots) {
boost::asio::io_context io;

Signal<int(int, int)> sig(io.get_executor());

auto conn1 = sig.connect([](int a, int b) { return a + b; }, ExecPolicy::Sync);

auto conn2 = sig.connect([](int a, int b) { return a * b; }, ExecPolicy::Async);

// Run the io_context on a background thread so async slots can execute
auto work = boost::asio::make_work_guard(io);
std::thread runner([&io] { io.run(); });

// Emit will block until all async slots complete (combiner collects values).
auto results = sig.emit(3, 4);

ASSERT_EQ(results.size(), 2);
EXPECT_EQ(results[0], 7);
EXPECT_EQ(results[1], 12);

work.reset();
io.stop();
runner.join();
}
29 changes: 29 additions & 0 deletions tests/test_socket.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#include <gtest/gtest.h>
#include <boost/asio/io_context.hpp>

#include "rtaco/socket/nl_socket.hxx"
#include "rtaco/socket/nl_socket_guard.hxx"

using namespace llmx::rtaco;

TEST(SocketTest, DefaultClosed) {
boost::asio::io_context io;
Socket s(io, "test-socket");

EXPECT_FALSE(s.is_open());

auto rc = s.close();
EXPECT_TRUE(static_cast<bool>(rc));

auto rc2 = s.cancel();
(void)rc2; // cancel() may fail on a non-open socket on some platforms; ensure it
// doesn't throw
}

TEST(SocketGuardTest, StopNoThrow) {
boost::asio::io_context io;
SocketGuard g(io, "test-guard");

// stop should be safe even if socket not open
EXPECT_NO_THROW(g.stop());
}